1. 全文檢索
1.1 全文檢索流程
- 主要分兩大部分:建立索引和查詢索引
1.2 全文檢索概念
- 在MySQL中查詢某條記錄使用的一定是列名,也就是在建立資料庫時已經寫死的列名名稱。在查詢時,使用該列名去比對使用者輸入的查詢内容。
- 在ES中,建立索引庫的時候,不是簡單的寫死列名,而是由一套流程根據原始的文檔進行分詞後建立的索引。在查詢時,隻需要将使用者輸入的索引與建立索引庫時生成的那些索引進行比對,若比對則傳回這個或者這些索引對應的原始文檔。
- 這種先建立索引,再對索引進行搜尋的過程就叫全文檢索(Full-text Search)。
1.3 新術語解釋
1.3.1 索引庫
- 存儲了索引資訊以及這些索引對應的文檔對象(包含原始資料内容)
1.3.2 文檔對象
- 文檔中包括一個一個的域(Field),域中存儲原始資料内容。每個文檔都有一個唯一的編号,就是文檔id。
- 一個文檔就相當于資料庫中的一條記錄。
1.3.3 field域
- filed對象屬性:
- 是否分詞:是否對域的内容進行分詞處理,便于查詢。但例如身份證号就不要分詞。
- 是否索引:将Filed分析後的詞或者整個值進行索引,隻有開啟索引才能搜到。
- 是否存儲:将Filed值存儲在文檔中,隻要存儲下來的将來才能從文檔中擷取。
- term對象:從文檔對象中拆分出來的同一個域中每個單詞叫做同一個term,不可分割。含有一定的定位資訊:域和詞均不重複。
- 一個field對象就相當于一條記錄中的某個字段。
2. ElasticSearch引擎
- 簡稱ES,開源 高擴充 分布式 全文檢索 引擎。
- 近乎實時的存儲和檢索資料,可擴充到上百台伺服器、處理PB級别資料
- 核心是Lucene實作所有的索引和搜尋功能,通過簡單RESTful來隐藏其複雜性。
- 對比Solr引擎的特點:
- Solr 利用 Zookeeper 進行分布式管理,而 Elasticsearch 自身帶有分布式協調管理功能;
- Solr 支援更多格式的資料,而 Elasticsearch 僅支援JSON檔案格式;
- Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,進階功能多有第三方插件提供;
- Solr 在傳統的搜尋應用中表現好于 Elasticsearch,但在處理實時搜尋應用時效率明顯低于Elasticsearch
- Elasticsearch是面向文檔(document oriented)的,這意味着它可以存儲整個對象或文檔(document)。然而它不僅僅是存儲,還會索引(index)每個文檔的内容使之可以被搜尋。在Elasticsearch中,你可以對文檔(而非成行成列的資料)進行索引、搜尋、排序、過濾。
2.1 索引index
- 一個索引對應的可能是文檔集合,這個集合中的文檔都有某些相似的特征。
- 一個索引必須小寫字母的名字來命名,他相當于MySQL資料庫中的某個資料庫名稱。
2.2 類型type
- 在一個索引中的不同類型隻是該索引的不同邏輯分區,完全由使用者指定。
- 我們雖然可以通俗的去了解Index比作 SQL 的 Database,Type比作SQL的Table。但這并不準确,因為如果在SQL中,Table之間互相獨立,不同表中可以有相同字段名,但是在ES中,同一個Index 下不允許有相同的 Type 名稱,他們會被 Lucene當作同一個字段 ,并且他們的定義必須相同。是以我覺得Index現在更像一個表,而Type字段并沒有多少意義。7.0.0以後将将不建議使用,8.0.0 以後完全不支援。
2.3 字段Filed
- 相當于是資料表的字段,對文檔資料根據不同屬性進行的分類
- field是索引庫中存儲資料的最小機關。
2.4 映射mapping
- mapping是處理資料的方式和規則方面做一些限制,如某個字段的資料類型、預設值、分析器、是否被索引,使用規則等等。
- **意義所在:**按着最優規則處理資料對性能提高很大,是以才需要建立映射,并且需要思考如何建立映射才能對性能更好。
2.5 文檔document
- 一個文檔是一個可被索引的基礎資訊單元,文檔以JSON格式來表示。
2.6 接近實時NRT
- 從索引一個文檔直到這個文檔能夠被搜尋到有一個輕微的延遲(通常是1秒以内)
2.7 叢集 cluster
- 一個叢集由一個唯一的名字辨別,這個名字預設就是
。這個名字是重要的,因為一個節點隻能通過指定某個叢集的名字,來加入這個叢集。elasticsearch
2.8 節點 node
- 一個節點是叢集中的一個伺服器,作為叢集的一部分,它存儲資料,參與叢集的索引和搜尋功能。
- 節點也是由一個名稱來辨別的,通過節點名去确定網絡中的哪些伺服器對應于Elasticsearch叢集中的哪些節點
- 一個節點可以通過配置叢集名稱的方式來加入一個指定的叢集,預設情況下,他們會加入到名為
的叢集中。elasticsearch
2.9 分片shards
- 一個索引可能存儲超出單個結點硬體限制的大量資料,即使該節點硬體存儲量很大,若存儲了大量資料,單個節點處理搜尋請求,響應太慢。為此引入了分片的概念。
- Elasticsearch提供了将索引劃分成多份的能力,這些份就叫做分片。當你建立一個索引的時候,你可以指定你想要的分片的數量。
- 每個分片本身也是一個功能完善并且獨立的“索引”,這個“索引”可以被放置到叢集中的任何節點上。
- 使用分片主要優點有兩個,水準擴充和分布式并行:
- 允許你水準分割/擴充你的内容容量。
- 允許你在分片(潛在地,位于多個節點上)之上進行分布式的、并行的操作,進而提高性能/吞吐量。
2.10 複制replicas
- 考慮到網絡環境的不穩定性,某個分片或者節點可能離線或損毀,Elasticsearch允許你建立分片的一份或多份拷貝,這些拷貝叫做複制分片。
-
預設情況下,Elasticsearch中的每個索引被分片5個主分片和1個複制,每個索引總共
就有10個分片。注意分片數量隻能在建立時指定。
- 使用複制機制的兩個優點:
- 在分片/節點失敗的情況下,提供了高可用性
- 擴充你的搜尋量/吞吐量。搜尋可以在所有複制上并行運作。
3. ElasticSearch實操
3.1 docker安裝ES
- docker鏡像下載下傳和容器安裝:
docker pull elasticsearch:5.6.8 #9200端口(Web管理平台端口) 9300(服務預設端口) docker run -it -e ES_JAVA_OPTS="-Xms128m -Xmx128m" --name=my_es -p 9200:9200 -p 9300:9300 elasticsearch:5.6.8
- 端口服務說明:
- 9200 是ES節點與外部通訊使用的端口。它是http協定的RESTful接口(各種CRUD操作都是走的該端口,如查詢:http://localhost:9200/user/_search)。
- 9300是ES節點之間通訊使用的端口。它是TCP通訊端口。java程式中使用ES時,在配置檔案中要配置該端口。
- 容器啟動可能失敗 因為ES鏡像預設配置設定2g給JVM,由于記憶體不足導緻配置設定失敗,由兩個解決辦法,一個是docker中的全局修改,一個是針對該容器在建立時指定(推薦):
#方法1:将文檔中的-Xms2g和-Xmx2g各修改為-Xms128m -Xmx128m vim `find /var/lib/docker/overlay2/ -name jvm.options` #方法2:在建立容器時指定參數 -e ES_JAVA_OPTS="-Xms128m -Xmx128m" docker run -di -e ES_JAVA_OPTS="-Xms128m -Xmx128m"
- 開啟遠端連接配接(由于鏡像裡自帶apt,請自行安裝vim):
docker exec -it my_es /bin/bash cd /usr/share/elasticsearch/config vim elasticsearch.yml #文檔中去掉下面的注釋以開啟遠端連接配接 transport.host: 0.0.0.0 #文檔中指定叢集名稱 cluster.name: my-elasticsearch #退出編輯後重新開機docker 注意由于資源限制還需要後面的配置後才能啟動 docker restart my_es
- 跨域配置,修改
,追加:elasticsearch/config/elasticsearch.yml
http.cors.enabled: true http.cors.allow-origin: "*" network.host: 192.168.131.129 #你自己的ip #退出文檔後重新開機容器 docker restart my_es #可以設定容器自動啟動 docker update --restart=always my_es
- 修改主控端系統參數,限制使用更多的虛拟記憶體:
vim /etc/security/limits.conf #追加内容 nofile是單個程序允許打開的最大檔案個數 soft(軟限制) hard(硬限制) * soft nofile 65536 * hard nofile 65536 vim /etc/sysctl.conf #追加内容 (限制一個程序可以擁有的VMA(虛拟記憶體區域)的數量 ) vm.max_map_count=655360 #退出文檔後使配置參數生效并重新開機整個系統 sysctl -p reboot
- 添加apt包管理器的阿裡源:
echo "deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse" > sources.list #解決the public key is not available: NO_PUBKEY 3B4FE6ACC0B21F32 #添加一個key就可以了 其他報錯就沒了 apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
3.2 ElasticSearch用戶端
- 有三種方式作為用戶端進行操作:
-
插件elasticsearch-head
- Restful接口直接通路
- SDK提供的API進行通路
-
3.2.1 elasticsearch-head插件
- 根據具體的作業系統選擇相應的安裝包:
#下載下傳插件本身 https://github.com/mobz/elasticsearch-head #下載下傳node.js依賴 https://nodejs.org/en/download/ #使用node包管理器下載下傳grunt cnpm install -g grunt-cli
- 啟動插件服務:
#先進入到插件安裝目錄再執行下面指令進行包安裝和服務啟動 npm install grunt server #通路插件服務 注意端口為9100需要ES服務端配置跨域 9100服務會跨域通路9200 https://localhost:9100 #通路成功後,進入插件配置界面,輸入你ES服務的位址 http://192.168.131.129:9200/
3.2.2 Postman進行RESTful通路
- 文法格式:不推薦記憶,直接使用執行個體記憶即可。
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
3.2.2.1 同時建立索引和映射
- 請求體最外層是mapping字段,其次是type字段。請求URL中隻給出了索引名稱,有了這三個元素,就能夠建立一個完整的索引了。
3.2.2.2 先建立索引後設定映射
- 先使用
單獨建立一個空的索引,然後再使用POST接口在請求URL中指定該索引下某個類型的映射:PUT 192.168.131.129:9200/blog2
3.2.2.3 删除索引index
- 使用DELETE接口删除指定的index:
DELETE 192.168.131.129:9200/blog2
3.2.2.4 建立文檔document
- 如果該索引不存在則會自動建立這個索引且自動設定mapping,若建立文檔時沒有指定文檔的
,則會自動建立一個例如_id
随機值:AXwD8wZTp_SbyR6-LOPW
3.2.2.5 修改文檔document
- 與建立文檔使用同一個接口,建立文檔時可以不指定文檔ID,但是修改文檔是靠這個文檔ID來定位需要更改的文檔的。
3.2.2.6 删除文檔document
- 使用DELETE接口,注意要指定文檔ID,因為沒有辦法直接删除某個類型:
DELETE 192.168.131.129:9200/blog1/article/22
3.2.2.7 查詢文檔-三種方法
- 根據文檔ID查詢文檔:
GET 192.168.131.129:9200/blog1/article/1
- 使用query_string根據字段内容查詢:注意由于Standard标準分詞器會将漢語分成一個個漢字,是以該請求實際上請求了5個詞比對
- 使用term查詢文檔,注意term特性就是對搜尋的關鍵詞不分詞:
3.3 IK分詞器
- 為解決中文分詞開發,從Lucene獨立出來,使用Java語言開發的工具包。
3.3.1 為ES安裝IK插件
- IK官網下載下傳位址:https://github.com/medcl/elasticsearch-analysis-ik/releases
- 上傳到伺服器并解壓後拷貝到ES容器中的plugins目錄下:
docker cp ./ik kkb_es:/usr/share/elasticsearch/plugins
- 使用9200服務進行GET測試:兩種分詞方法
http://192.168.131.129:9200/_analyze?analyzer=ik_smart&pretty=true&text=搜尋伺服器http://192.168.131.129:9200/_analyze?analyzer=ik_max_word&pretty=true&text=搜尋伺服器
- 這兩種分詞方法的差別是:
- ik_max_word:會将文本做最細粒度的拆分
- ik_smart:會做最粗粒度的拆分
3.4 Kibana可視化平台
- 對ES操作更專業,提供專用的DSL語句,Kibana是 Elastic Stack 成員之一,設計用于和 Elasticsearch協作。
- DSL 其實是 Domain Specific Language,領域特定語言,相對的就是 GPL, General Purpose Language ,通用程式設計語言Java、Python 以及 C 語言等等。DSL 它們的表達能力有限,隻是在特定領域解決特定任務的。
3.4.1 docker安裝Kibana
- 注意鏡像的版本号與ES鏡像版本号比對:
docker pull docker.io/kibana:5.6.8
- 安裝kibana容器,注意需要指定連接配接的ES服務位址:
docker run -id -e ELASTICSEARCH_URL=http://192.168.131.129:9200 --name kibana -p 5601:5601 kibana:5.6.8
3.4.2 kibana頁面實操
- 配置索引:在Management菜單裡Create Index Pattern,配置一個用來比對index的比對規則。
- 配置完索引後,就可以基于這些索引進行各種基于頁面的操作了。這裡不再介紹。
- 實操部分着重介紹DSL語句使用。
3.4.3 DSL語句實操
- 選擇Kibana頁面菜單裡的Dev Tools菜單,進入控制台,輸入各種DSL語句測試。
3.4.3.1 索引操作
- 查詢/删除/新增索引:
GET /_cat/indices?vDELETE /skuinfoPUT /user
- 建立映射(需指定類型且要求已存在該index):注意建立時指定了ik分詞器。
- 新增/更新文檔資料:注意若index不存在則自動建立
- 更新某個指定字段的文檔資料:要求該index存在
- Sort排序查詢:使用sort字段指定排序域名和排序方法。
- 分頁查詢,使用from字段和size字段分别指定分頁中的目前頁和查詢頁數:
3.4.3.2 查詢模式
- term查詢,不會将查詢的内容分詞,用于精确查詢:
- terms查詢可以指定多個term:
- math查詢,會把使用者查詢的内容進行分詞,然後再查詢:
- query_string查詢,也會進行分詞後查詢:
- range過濾查詢,例如查詢年齡範圍:
- exists 過濾可以用于查找擁有某個域的資料:
- bool查詢:must相當于邏輯與,must_not相當于邏輯非,should相當于邏輯或:
- 預設的match_all查詢:
- match查詢:标準查詢,會進行分詞後查詢:
- 字首查詢prefix:
- multi_match 查詢,多字段聯合查詢某值:
3.5 Java引入ES用戶端
- 本文隻做梳理性介紹,具體操作再另外的部落格給出。
- 由于引入的是ES官方給出的jar包,是以首先是引入pom依賴,導入坐标:
- 建立索引:使用Settings—>PreBuiltTransportClient—>TransportClient 。
- 建立映射:XContentBuilder—>PutMappingRequest —>TransportClient
- 建立文檔之純調用API: XContentBuilder —>TransportClient
- 建立文檔之Jackson轉換實體:引入Jackson依賴—>ObjectMapper—>TransportClient
- 查詢文檔之termQuery: QueryBuilder —>TransportClient—>SearchResponse—>SearchHits
- 查詢文檔之QueryString: 同termQuery,隻不過在第一步QueryBuilder調用方法不同。
- 查詢文檔之MatchQuery: 同termQuery,隻不過在第一步QueryBuilder調用方法不同。
- 查詢文檔之文檔ID查詢:同termQuery,隻不過在第一步QueryBuilder調用方法不同。
- 查詢文檔之分頁操作:在上述過程中的TransportClient—>SearchResponse中調用方法。
- 查詢文檔之高亮操作:不會幹涉原有的結果,隻是額外增加了需要高亮的部分。前端在原有結果顯示後,查找傳回的高亮結果,并使用高亮結果替換原有的字段結果。操作流程: HighlightBuilder—>TransportClient—>調用設定高亮的方法—>SearchResponse—>SearchHits
3.6 SpringData整合ElasticSearch
- Spring Data是一個用于簡化資料庫通路,并支援雲服務的開源架構。
- 導入依賴:使用Spring Initializr通過勾選的方式選中各依賴,自動建立pom依賴。
- 更改預設給出的配置檔案問啟動yml檔案:
- 編寫實體類,使用相關注解:
- 編寫響應Dao,注意,和MP一樣,繼承自帶的ORM類 ElasticsearchRepository,其方法名稱IDEA有代碼提示,例如findByTitleAndContent,可以逐個測試。
- 建立索引配置映射關系: ElasticsearchTemplate:
public void createIndex() throws Exception { //建立索引,并配置映射關系 template.createIndex(Article.class); //配置映射關系 //template.putMapping(Article.class); }
- 通過有了注解的實體類寫入文檔到索引庫:
dao.save(article);
- 全部删除:
dao..deleteAll();
- 查找所有:
dao.findAll();
- 自定義查詢對象: NativeSearchQueryBuilder–>NativeSearchQuery–>ElasticsearchTemplate
public void testNativeSearchQuery() throws Exception { //建立一個查詢對象 NativeSearchQuery query = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.queryStringQuery("女護士").defaultField("title")) .withPageable(PageRequest.of(0, 15)) .build(); //執行查詢 List<Article> articleList = template.queryForList(query, Article.class); articleList.forEach(a-> System.out.println(a)); }
3.7 聚合查詢
- 聚合可以讓我們極其友善的實作對資料的統計、分析
- Elasticsearch中的聚合,包含多種類型,最常用的兩種,桶(bucket)和度量(metrics):
- 桶的作用,是按照某種方式對資料進行分組,每一組資料在ES中稱為一個桶, 隻負責對資料進行分組,并不進行計算,是以桶往往嵌套度量。
- 分組完成以後,我們一般會對組中的資料進行聚合運算,例如求平均值、最大、最小、求和等,這些在ES中稱為度量
- 使用aggs字段進行分組,桶名自定義:
GET /car_index/car/_search { "aggs": { "group_by_bland": { "terms": { "field": "color" }, //桶裡嵌套度量 求出桶裡price的平均值 "aggs": { "avg_price": { "avg": { "field": "price" } } } } } }
- 代碼實作桶及桶内度量:
public void testQuerySelfSubAggs(){ //1.查詢條件的建構器 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery()); //2.排除所有的字段查詢, queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null)); //3.添加聚合條件 queryBuilder.addAggregation(AggregationBuilders.terms("group_by_color").field("color").subAggregation(AggregationBuilders.avg("avg_price").field("price"))); //4.執行查詢,把查詢結果直接轉為聚合page AggregatedPage<Car> aggPage = (AggregatedPage<Car>) carDao.search(queryBuilder.build()); //5.從所有的聚合中擷取對應名稱的聚合 StringTerms agg = (StringTerms) aggPage.getAggregation("group_by_color"); //6.從聚合的結果中擷取所有的桶資訊 List<StringTerms.Bucket> buckets = agg.getBuckets(); for (StringTerms.Bucket bucket : buckets) { String brand = bucket.getKeyAsString(); long docCount = bucket.getDocCount(); //7.取得内部聚合 InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("avg_price"); System.out.println("brand = " + brand+" 總數:"+docCount+" 平均價格:"+avg.getValue()); } }