ElasticSearch的入門使用

ElasticSearch概述

Elaticsearch,簡稱爲es, es是一個開源的高擴展的分佈式全文檢索引擎,它可以近乎實時的存儲、檢索數據;本身擴展性很好,可以擴展到上百臺服務器,處理PB級別(大數據時代)的數據。es也使用Java開發並使用Lucene作爲其核心來實現所有索引和搜索的功能,但是它的目的是通過簡單的RESTful API來隱藏Lucene的複雜性,從而讓全文搜索變得簡單。

ElasticSearch的使用場景

  • 維基百科,類似百度百科,全文檢索,高亮,搜索推薦/2 (權重,百度!)
  • The Guardian(國外新聞網站),類似搜狐新聞,用戶行爲日誌(點擊,瀏覽,收藏,評論)+社交網絡數據(對某某新聞的相關看法),數據分析,給到每篇新聞文章的作者,讓他知道他的文章的公衆反饋(好,壞,熱門,垃圾,鄙視,崇拜)
  • Stack Overflow(國外的程序異常討論論壇),IT問題,程序的報錯,提交上去,有人會跟你討論和回答,全文檢索,搜索相關問題和答案,程序報錯了,就會將報錯信息粘貼到裏面去,搜索有沒有對應的答案
  • GitHub(開源代碼管理),搜索上千億行代碼
  • 電商網站,檢索商品
  • 日誌數據分析,logstash採集日誌,ES進行復雜的數據分析,ELK技術,elasticsearch+logstash+kibana
  • 商品價格監控網站,用戶設定某商品的價格閾值,當低於該閾值的時候,發送通知消息給用戶,比如說訂閱牙膏的監控,如果高露潔牙膏的家庭套裝低於50塊錢,就通知我,我就去買。
  • BI系統,商業智能,Business Intelligence。比如說有個大型商場集團,BI,分析一下某某區域最近3年的用戶消費金額的趨勢以及用戶羣體的組成構成,產出相關的數張報表,**區,最近3年,每年消費金額呈現100%的增長,而且用戶羣體85%是高級白領,開一個新商場。ES執行數據分析和挖掘,Kibana進行數據可視化
  • 國內:站內搜索(電商,招聘,門戶,等等),IT系統搜索(OA,CRM,ERP,等等),數據分析(ES熱門的一個使用場景)

ES和solr的差別

  • es基本是開箱即用(解壓就可以用 ! )非常簡單。Solr安裝略微複雜一點點!
  • Solr 利用 Zookeeper 進行分佈式管理,而 Elasticsearch 自身帶有分佈式協調管理功能。
  • Solr 支持更多格式的數據,比如JSON、XML、CSV,而 Elasticsearch 僅支持json文件格式。
  • Solr 官方提供的功能更多,而 Elasticsearch 本身更注重於核心功能,高級功能多有第三方插件提供,例如圖形化界面需要kibana友好支撐~!
  • Solr 查詢快,但更新索引時慢(即插入刪除慢),用於電商等查詢多的應用;
  • ES建立索引快(即查詢慢),即實時性查詢快,用於facebook新浪等搜索。
  • Solr 是傳統搜索應用的有力解決方案,但 Elasticsearch 更適用於新興的實時搜索應用。
  • Solr比較成熟,有一個更大,更成熟的用戶、開發和貢獻者社區,而 Elasticsearch相對開發維護者較少,更新太快,學習使用成本較高。(趨勢!)

ElasticSearch安裝

官網下載慢,可以從華爲雲的鏡像去下載

ElasticSearch: https://mirrors.huaweicloud.com/elasticsearch/?C=N&O=D
logstash: https://mirrors.huaweicloud.com/logstash/?C=N&O=D
kibana: https://mirrors.huaweicloud.com/kibana/?C=N&O=D

ElasticSearch的安裝

啓動bin目錄下的elasticsearch.bat,瀏覽器訪問localhost:9200 效果如下

在這裏插入圖片描述

可視化界面 es head的插件的安裝

  1. 首先配置es可跨域

config目錄中的elasticsearch.yml文件中配置

http.cors.enabled: true
http.cors.allow-origin: "*"
  1. 使用git拉取https://github.com/mobz/elasticsearch-head/

  2. 啓動

npm install
npm run start
  1. 訪問http://localhost:9100

也可直接安裝chrome插件,免去以上拉取啓動步驟
在這裏插入圖片描述

瞭解 ELK

ELK是Elasticsearch、Logstash、Kibana三大開源框架首字母大寫簡稱。市面上也被成爲Elastic Stack。其中Elasticsearch是一個基於Lucene、分佈式、通過Restful方式進行交互的近實時搜索平臺框架。像類似百度、谷歌這種大數據全文搜索引擎的場景都可以使用Elasticsearch作爲底層支持框架,可見Elasticsearch提供的搜索能力確實強大,市面上很多時候我們簡稱Elasticsearch爲es。Logstash是ELK的中央數據流引擎,用於從不同目標(文件/數據存儲/MQ)收集的不同格式數據,經過過濾後支持輸出 到不同目的地(文件/MQ/redis/elasticsearch/kafka等)。Kibana可以將elasticsearch的數據通過友好的頁面展示出來,提供實時分析的功能。
市面上很多開發只要提到ELK能夠一致說出它是一個日誌分析架構技術棧總稱,但實際上ELK不僅僅適用於日誌分析,它還可以支持其它任何數據分析和收集的場景,日誌分析和收集只是更具有代表性。並非唯一性。

在這裏插入圖片描述

安裝Kibana

Kibana是一個針對Elasticsearch的開源分析及可視化平臺,用來搜索、查看交互存儲在Elasticsearch索引中的數據。使用Kibana,可以通過各種圖表進行高級數據分析及展示。Kibana讓海量數據更容易理解。它操作簡單,基於瀏覽器的用戶界面可以快速創建儀表板(dashboard)實時顯示Elasticsearch查詢動態。設置Kibana非常簡單。無需編碼或者額外的基礎架構,幾分鐘內就可以完成Kibana安裝並啓動Elasticsearch索引監測。

Kibana 版本要和 es 一致!

漢化設置: 修改config目錄下的kibana.yml文件,在文件末尾添加i18n.locale: "zh-CN"

啓動: 打開bin目錄下的kibana.bat訪問http://localhost:5601

在這裏插入圖片描述

ES核心概念

elasticsearch是面向文檔,關係行數據庫 和 elasticsearch 客觀的對比!一切都是JSON!

Relational DB Elasticsearch
數據庫(database) 索引(indices)
表(tables) types
行(rows) documents
字段(columns) fields

elasticsearch(集羣)中可以包含多個索引(數據庫),每個索引中可以包含多個類型(表),每個類型下又包含多個文檔(行),每個文檔中又包含多個字段(列)。

物理設計:
elasticsearch 在後臺把每個索引劃分成多個分片,每分分片可以在集羣中的不同服務器間遷移一個人就是一個集羣!默認的集羣名稱就是 elaticsearh

邏輯設計:
一個索引類型中,包含多個文檔,比如說文檔1,文檔2。 當我們索引一篇文檔時,可以通過這樣的一各順序找到 它: 索引==> 類型 ==> 文檔ID ,通過這個組合我們就能索引到某個具體的文檔。 注意:ID不必是整數,實際上它是個字符串。

文檔

就是我們的一條條數據

user
  zhangsan 18
  lisi 19

之前說elasticsearch是面向文檔的,那麼就意味着索引和搜索數據的最小單位是文檔,elasticsearch 中,文檔有幾個重要屬性 :

  • 自我包含: 一篇文檔同時包含字段和對應的值,也就是同時包含 key:value
  • 可以是層次型的,一個文檔中包含自文檔,複雜的邏輯實體就是這麼來的! {就是一個json對象!fastjson進行自動轉換!}
  • 靈活的結構,文檔不依賴預先定義的模式,我們知道關係型數據庫中,要提前定義字段才能使用,在elasticsearch中,對於字段是非常靈活的,有時候,我們可以忽略該字段,或者動態的添加一個新的字段。

儘管我們可以隨意的新增或者忽略某個字段,但是,每個字段的類型非常重要,比如一個年齡字段類型,可以是字符串也可以是整形。因爲elasticsearch會保存字段和類型之間的映射及其他的設置。這種映射具體到每個映射的每種類型,這也是爲什麼在elasticsearch中,類型有時候也稱爲映射類型。

類型

類型是文檔的邏輯容器,就像關係型數據庫一樣,表格是行的容器。 類型中對於字段的定義稱爲映射, 比如 name 映射爲字符串類型。 我們說文檔是無模式的,它們不需要擁有映射中所定義的所有字段, 比如新增一個字段,elasticsearch會自動的將新字段加入映射,但是這個字段的不確定它是什麼類型,elasticsearch就開始猜,如果這個值是18,那麼elasticsearch會認爲它是整形。 但是elasticsearch也可能猜不對, 所以最安全的方式就是提前定義好所需要的映射,這點跟關係型數據庫殊途同歸了,先定義好字段,然後再使用。

索引

就是數據庫!
索引是映射類型的容器,elasticsearch中的索引是一個非常大的文檔集合。索引存儲了映射類型的字段和其他設置。 然後它們被存儲到了各個分片上了。

物理設計 :節點和分片 如何工作

在這裏插入圖片描述

一個集羣至少有一個節點,而一個節點就是一個elasricsearch進程,節點可以有多個索引默認的,如果你創建索引,那麼索引將會有個5個分片 ( primary shard ,又稱主分片 ) 構成的,每一個主分片會有一個副本 ( replica shard ,又稱複製分片 )

在這裏插入圖片描述

上圖是一個有3個節點的集羣,可以看到主分片和對應的複製分片都不會在同一個節點內,這樣有利於某個節點掛掉了,數據也不至於丟失。 實際上,一個分片是一個Lucene索引,一個包含倒排索引的文件目錄,倒排索引的結構使得elasticsearch在不掃描全部文檔的情況下,就能告訴你哪些文檔包含特定的關鍵字。

倒排索引

elasticsearch使用的是一種稱爲倒排索引的結構,採用Lucene倒排索作爲底層。這種結構適用於快速的全文搜索, 一個索引由文檔中所有不重複的列表構成,對於每一個詞,都有一個包含它的文檔列表。 例如,現在有兩個文檔, 每個文檔包含如下內容:

Study every day, good good up to forever	# 文檔1包含的內容
To forever, study every day, good good up   # 文檔2包含的內容

爲了創建倒排索引,我們首先要將每個文檔拆分成獨立的詞(或稱爲詞條或者tokens),然後創建一個包含所有不重 復的詞條的排序列表,然後列出每個詞條出現在哪個文檔 :

term doc_1 doc_2
Study ×
To x ×
every
forever
day
study ×
good
every
to ×
up

現在,我們試圖搜索 to forever,只需要查看包含每個詞條的文檔 score

term doc_1 doc_2
to ×
forever
total 2 1

兩個文檔都匹配,但是第一個文檔比第二個匹配程度更高。如果沒有別的條件,現在,這兩個包含關鍵字的文檔都將返回。

再來看一個示例,比如我們通過博客標籤來搜索博客文章。那麼倒排索引列表就是這樣的一個結構 :

在這裏插入圖片描述

如果要搜索含有 python 標籤的文章,那相對於查找所有原始數據而言,查找倒排索引後的數據將會快的多。只需要查看標籤這一欄,然後獲取相關的文章ID即可。完全過濾掉無關的所有數據,提高效率!

IK分詞器插件

  1. 下載 github地址:https://github.com/medcl/elasticsearch-analysis-ik/releases

    由於github下載較慢, 這裏提供 藍奏雲地址:https://manaphy.lanzous.com/igd0qe2ssgd

  2. 下載完畢之後,放入到我們的elasticsearch 插件即可

  3. 重啓es

在這裏插入圖片描述

  1. 通過elasticsearch-plugin list這個命令來查看加載進來的插件

在這裏插入圖片描述

  1. 使用kibana測試

查看不同的分詞效果

ik_smart爲最少切分

在這裏插入圖片描述

ik_max_word爲最細粒度劃分!窮盡詞庫的可能字典!

在這裏插入圖片描述

添加自己需要的詞到分詞器的字典中

問題如下: 時拉比被拆分了

在這裏插入圖片描述

在分詞器config文件夾下創建my.dic文件 文件裏添加我們需要的詞 在IKAnalyzer.cfg.xml中配置

在這裏插入圖片描述

重啓es

在這裏插入圖片描述

Rest風格說明

一種軟件架構風格,而不是標準,只是提供了一組設計原則和約束條件。它主要用於客戶端和服務器交互類的軟件。基於這個風格設計的軟件可以更簡潔,更有層次,更易於實現緩存等機制。

基本Rest命令說明:

method URL地址 描述
PUT localhost:9200/索引名稱/類型名稱/文檔id 創建文檔(指定文檔id)
POST localhost:9200/索引名稱/類型名稱 創建文檔(隨機文檔id)
POST localhost:9200/索引名稱/類型名稱/文檔id/_update 修改文檔
DELETE localhost:9200/索引名稱/類型名稱/文檔id 刪除文檔
GET localhost:9200/索引名稱/類型名稱/文檔id 查詢文檔通過文檔id
POST localhost:9200/索引名稱/類型名稱/_search 查詢所有數據

關於索引的基本操作

創建一個索引

PUT	/索引名/~類型名~/文檔id
{請求體}

在這裏插入圖片描述

在 es head 中查看

在這裏插入圖片描述

字段的類型

指定字段的類型

創建規則完後 可以通過 GET 請求獲取具體的信息

在這裏插入圖片描述

查看默認的信息

在這裏插入圖片描述

如果自己的文檔字段沒有指定,那麼es 就會給我們默認配置字段類型!

擴展: 通過GET _cat/ 可以獲得es的當前的很多信息!

在這裏插入圖片描述

修改索引內容

以前的方法: 還是使用PUT命令

在這裏插入圖片描述

現在推薦使用以下方法 POST

在這裏插入圖片描述

刪除索引

通過DELETE 命令實現刪除、 根據你的請求來判斷是刪除索引還是刪除文檔記錄!

在這裏插入圖片描述

關於文檔的基本操作

添加數據

在這裏插入圖片描述

獲取數據

在這裏插入圖片描述

修改數據

在這裏插入圖片描述

簡單搜索

在這裏插入圖片描述

複雜操作搜索

在這裏插入圖片描述

過濾結果

在這裏插入圖片描述

排序

在這裏插入圖片描述

分頁

在這裏插入圖片描述

與數據庫的分頁查詢類似

布爾值查詢

must (and),類似 where id = 1 and name = xxx

在這裏插入圖片描述

should (or), 類似 where id = 1 or name = xxx

在這裏插入圖片描述

must_not (not)

在這裏插入圖片描述

過濾器 filter

在這裏插入圖片描述

匹配多個條件

在這裏插入圖片描述

多個條件使用空格隔開 只要滿足其中一個結果就可以被查出

高亮查詢

在這裏插入圖片描述

在這裏插入圖片描述

Spring boot 集成 ElasticSearch

引入依賴

<properties>
    <!--自定義 es 版本依賴 保證和本地一致-->
    <elasticsearch.version>7.8.0</elasticsearch.version>
</properties>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>${fastjson.version}</version>
</dependency>

配置ElasticSearch

@Configuration
public class ElasticSearchClientConfig {

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        return new RestHighLevelClient(
                RestClient.builder(new HttpHost("localhost", 9200, "http"))
        );
    }
}

測試索引的創建和刪除

@Resource
private RestHighLevelClient client;

/**
 * 創建索引的測試
 */
@Test
void createIndexTest() throws IOException {
    //1.創建索引請求
    CreateIndexRequest request = new CreateIndexRequest("chen_index");
    //2.客戶端執行請求 IndicesClient,請求後獲得響應
    CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
    System.out.println(createIndexResponse);
}
/**
 * 索引存在測試,判斷索引是否存在
 */
@Test
void existIndexTest() throws IOException {
    GetIndexRequest request = new GetIndexRequest("chen_index");
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    System.out.println(exists);
}

/**
 * 刪除索引測試
 */
@Test
void deleteIndexTest() throws IOException {
    DeleteIndexRequest request = new DeleteIndexRequest("chen_index");
    AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
    System.out.println(response.isAcknowledged());
}

測試文檔的CRUD

/**
 * 添加文檔測試
 */
@Test
void addDocument() throws IOException {
    //創建對象和索引(執行創建索引Test)
    User user = new User("Manaphy", 3);
    //創建請求
    IndexRequest request = new IndexRequest("chen_index");
    //規則 put /chen_index/_doc/1
    request.id("1");
    //設置超時時間
    request.timeout(TimeValue.timeValueSeconds(1));
//        request.timeout("1s");//同上
    //將數據放入請求 json
    request.source(JSON.toJSONString(user), XContentType.JSON);
    //客戶端發送請求
    IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
    System.out.println(indexResponse.toString());//IndexResponse[index=chen_index,type=_doc,id=1,version=1,result=created,seqNo=0,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]
    System.out.println(indexResponse.status());//對應我們命令返回的狀態 CREATED
}

/**
 * 獲取文檔 get /chen_index/_doc/1
 */
@Test
void isExist() throws IOException {
    GetRequest getRequest = new GetRequest("chen_index", "1");
    //不獲取返回的 _source 的上下文
    getRequest.fetchSourceContext(new FetchSourceContext(false));
    getRequest.storedFields("_none_");
    boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
    System.out.println(exists);
}

/**
 * 獲得文檔的信息
 */
@Test
void getDocument() throws IOException {
    GetRequest getRequest = new GetRequest("chen_index", "1");
    GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
    //打印文檔內容
    System.out.println(getResponse.getSourceAsString());//{"age":3,"name":"Manaphy"}
    System.out.println(getResponse);//{"_index":"chen_index","_type":"_doc","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{"age":3,"name":"Manaphy"}}
}

/**
 * 更新文檔的信息
 */
@Test
void updateDocument() throws IOException {
    UpdateRequest updateRequest = new UpdateRequest("chen_index", "1");
    updateRequest.timeout("1s");
    User user = new User("Manaphy Chen", 3);
    updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
    UpdateResponse response = client.update(updateRequest, RequestOptions.DEFAULT);
    System.out.println(response);//UpdateResponse[index=chen_index,type=_doc,id=1,version=2,seqNo=1,primaryTerm=1,result=updated,shards=ShardInfo{total=2, successful=1, failures=[]}]
    System.out.println(response.status());//OK
}

/**
 * 刪除文檔記錄
 */
@Test
void deleteDocument() throws IOException {
    DeleteRequest request = new DeleteRequest("chen_index", "1");
    request.timeout("1s");
    DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
    System.out.println(response.status());//OK
}

/**
 * 批量插入數據
 */
@Test
void bulkRequest() throws IOException {
    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout(TimeValue.timeValueMinutes(1));

    ArrayList<User> userList = new ArrayList<User>() {{
        add(new User("chen1", 3));
        add(new User("chen2", 3));
        add(new User("chen3", 2));
        add(new User("chen4", 3));
        add(new User("chen5", 4));
    }};

    //批處理請求
    for (int i = 0; i < userList.size(); i++) {
        //批量更新和批量刪除 就在這裏修改對應的請求即可
//            bulkRequest.add(DeleteRequest deleteRequest)
//            bulkRequest.add(UpdateRequest updateRequest)
        bulkRequest.add(
                new IndexRequest("chen_index")
                        .id("" + (i + 1))//如果這裏不指定id 就會生成一個隨機的id 如 5fZb9XIBR6G61hgle73t
                        .source(JSON.toJSONString(userList.get(i)), XContentType.JSON));
    }
    BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
    System.out.println(response.hasFailures());//是否失敗,返回 false 代表成功
}

搜索

/**
 * 查詢
 * SearchRequest 搜索請求
 * SearchSourceBuilder 條件構造
 * HighlightBuilder 構建高亮
 * TermQueryBuilder 精確查詢
 * QueryBuilders 構建查詢條件
 */
@Test
void search() throws IOException {
    SearchRequest searchRequest = new SearchRequest("chen_index");
    //構建搜索條件
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    //查詢條件, 使用 QueryBuilders 工具來實現
    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "chen1");

    sourceBuilder.query(termQueryBuilder);
    sourceBuilder.timeout(TimeValue.timeValueMinutes(1));

    //高亮
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.field("name");
    highlightBuilder.preTags("<span style='color:red'>");
    highlightBuilder.postTags("</span>");
    sourceBuilder.highlighter(highlightBuilder);

    searchRequest.source(sourceBuilder);
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    SearchHits hits = searchResponse.getHits();
    System.out.println(JSON.toJSONString(hits));
    System.out.println("------------------------------------------");
    for (SearchHit hit : hits.getHits()) {
        //解析高亮字段
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        HighlightField name = highlightFields.get("name");
        Map<String, Object> sourceAsMap = hit.getSourceAsMap();//原來的結果
        //解析高亮的字段 將原來的字段替換爲我們高亮的字段即可
        if (name != null) {
            Text[] fragments = name.fragments();
            StringBuilder newName = new StringBuilder();
            for (Text fragment : fragments) {
                newName.append(fragment);
            }
            sourceAsMap.put("name", newName);
        }

        System.out.println(sourceAsMap);//{name=<span style='color:red'>chen1</span>, age=3}
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章