1. 程式人生 > 其它 >Elasticsearch網際網路主流分散式全文檢索框架實戰

Elasticsearch網際網路主流分散式全文檢索框架實戰

本篇先了解Elasticsearch底層原理,通過docker方式搭建3臺ES叢集,安裝Head外掛和Kibana視覺化工具,加入ik分詞器支援中文分詞,通過實踐Elasticsearch基本概念和操作對其有更深的理解,掌握基礎運維知識,最後利用spring-boot整合elasticsearch啟動器寫一個增刪改查功能示例。

Elasticsearch概述

**本人部落格網站 **IT小神 www.itxiaoshen.com

Elasticsearch官網地址 https://www.elastic.co/cn/elasticsearch/

Elasticsearch簡稱為ES,是一個基於Lecene開源的分散式高度可擴充套件的搜尋和資料分析引擎,使用Java語言開發,帶有RESTful 風格的API,是目前最流行的企業級搜尋引擎;能夠快速、接近實時地儲存、搜尋和分析大量資料;通常被用作底層引擎/技術,為具有複雜搜尋特性和需求的應用程式提供支援。目前最新Release版本為7.14 ,7.15版本雖然已出現在官方但暫時還沒有提供基於docker的映象

Elasticsearch參考文件官網 https://www.elastic.co/guide/en/elasticsearch/reference/index.html

Elasticsearch為所有型別的資料提供近乎實時的搜尋和分析。無論您使用的是結構化或非結構化文字、數字資料還是地理空間資料,Elasticsearch都能以一種支援快速搜尋的方式有效地儲存和索引它們。您可以遠遠超出簡單的資料檢索和聚合資訊來發現資料中的趨勢和模式。隨著資料和查詢量的增長,Elasticsearch的分散式特性使您的部署能夠與之無縫地增長。

Elasticsearch使用場景

  • 維基百科,類似百度百科、谷歌和百度這類基於搜尋核心,使用到全文檢索、高亮、搜尋推薦,
  • GitHub,託管開源專案原始碼,全文搜尋千億行級的原始碼
  • 電商網站,比如京東、淘寶之類,檢索商品
  • Stack Overflow ,國外程式設計師討論論壇,輸入報錯日誌搜尋問題和答案
  • 日誌資料分析,ELK,Logstash和Beats收集日誌,Elasticsearch複雜分析,Kibana做視覺化
  • 常見新聞網站使用者行為分析,比如將使用者的點選、瀏覽、收藏、評論進行資料分析
  • 其他行業的站內搜尋
  • 總之ES常用全文檢索、結構化搜尋、資料分析及相互混用的場景

Elasticsearch和Solr

Solr簡介

Solr官網 https://solr.apache.org

Solr是一個基於 Apache Lucene 之上的搜尋伺服器,開源的、基於 Java 的資訊檢索庫。它的主要功能包括強大的全文搜尋、高亮顯示、分面搜尋、動態叢集、資料庫整合、豐富的文件處理和地理空間搜尋;Solr具有高度的可擴充套件性,提供容錯的分散式搜尋和索引,並支援許多世界上最大的網際網路站點的搜尋和導航功能。具有類似rest的API,可以通過JSON、XML、CSV或HTTP上的二進位制檔案將文件放入其中(稱為“索引”),通過HTTP GET查詢它,並接收JSON、XML、CSV或二進位制結果。目前最新版本為8.9.0

Lucene

概述

Lucene官網 https://lucene.apache.org/

Apache Lucene™是一個開源的、高效能、全功能的文字搜尋引擎庫,完全用Java編寫。它適用於幾乎所有需要全文搜尋的應用程式,特別是跨平臺的應用程式。Lucene Core是一個Java庫,提供強大的索引和搜尋功能,以及拼寫檢查、點選高亮和高階分析/標記功能。

Apache Lucene為搜尋和索引效能設定了標準,是Apache Solr和Elasticsearch的搜尋核心,目前最新版本為8.9.0

缺點

  • Lucene只能在Java專案中使用,以jar引用
  • 使用比較複雜,包括建立和搜尋索引程式碼較為複雜,需要先了解檢索的相關知識來理解它是如何工作的
  • 不支援叢集環境,索引不能同步,不支援大規模資料的應用

什麼是全文檢索

  • 簡單的將就是通過掃描文字的每一個單詞,針對單詞建立索引,並儲存該單詞在文字中的位置以及出現的頻次
  • 使用者需要查詢時,通過之前建立好的索引來查詢,將索引中單詞對應的位置、單詞頻次資訊返回給使用者,因為有了文字位置資訊編號就可以通過正排索引將內容獲取出來

倒排索引

索引就類似於目錄,平時我們使用的都是索引,都是通過主鍵定位到某條資料,那麼倒排索引,剛好相反,資料對應到主鍵.這裡以一個部落格文章的內容為例:

  • 索引
文章ID 文章標題 文章內容
1 淺析JAVA設計模式 JAVA設計模式是每一個JAVA程式設計師都應該掌握的進階知識
2 JAVA多執行緒設計模式 JAVA多執行緒與設計模式結合
  • 倒排索引

假如,我們有一個站內搜尋的功能,通過某個關鍵詞來搜尋相關的文章,那麼這個關鍵詞可能出現在標題中,也可能出現在文章內容中,那我們將會在建立或修改文章的時候,建立一個關鍵詞與文章的對應關係表,這種,我們可以稱之為倒排索引,因此倒排索引,也可稱之為反向索引.如:

關鍵詞 文章ID
JAVA 1
設計模式 1,2
多執行緒 2

倒排索引一般由單詞詞典(Term Distionary)和倒排列表(PostingList)組成

單詞詞典

  • 是倒排索引的重要組成部分,記錄所有單詞及其與倒排列表的關聯關係,單詞詞典一般用B+Tree來實現,儲存在記憶體中

倒排列表一般儲存在磁碟中,包含一下資訊

  • 文件ID
  • 單詞詞頻 (Term Frequency)
  • 位置 (position)
  • 偏移 (offset)

動態索引更新策略

動態索引通過在記憶體中維護臨時索引,實現對動態文件和實時搜尋的支援。對於伺服器的記憶體總是有限的,隨著加入的文件資料越來越多,臨時索引消耗的記憶體也會不斷增加。當最初分配的記憶體被使用完時就需要考慮使用什麼策略來將臨時索引的部分內容更新到磁碟索引中,以釋放記憶體空間來儲存新的資料。

常用的索引更新策略主要有四種:完全重建策略、再合併策略、原地更新策略及混合策略。

  • 完全重建策略:屬於一種很直接的方法,當新增文件達到一定數量,將新增文件和原先的老文件進行合併,然後利用建立靜態索引的方式,對所有文件重新建立索引。新索引建立完成後,老的索引被遺棄釋放,之後對使用者查詢的相應完全由新的搜尋來負責
  • 再合併策略:有新增文件進入搜尋系統時,搜尋系統在記憶體維護臨時倒排索引來記錄資訊,當新增文件達到一定數量,或者指定大小的記憶體被消耗完,則把臨時索引和老文件的倒排索引進行合併,以生成新的索引
  • 原地更新策略:原地更新策略在索引合併時,並不生成新的索引檔案,而是直接在原先老的索引檔案裡進行追加操作,將增量索引裡單詞的倒排列表項追加到老索引相應位置的末尾,這樣就可以達到上述目標,即只更新增量索引裡出現的單詞相關資訊,其他單詞相關資訊不做變動
  • 混合策略:混合策略的出發點是能夠結合不同索引更新策略的長處,將不同的索引更新策略混合,以形成更高效的方法。混合策略一般會將單詞根據不同性質進行分類 ,不同類別單詞,對其索引採取不同的索引更新策略

分詞器

定義

  • 把全文字轉換為一系列單詞的過程就叫分詞,一般通過分析器來實現
  • 分詞器的作用就是將整篇文章按照一定語義切分為一條條的詞條,目標是提升文件的召回率,降低無效資料的噪音
    • recall:召回率,也叫可搜尋性,指進行搜尋時候能夠增加搜尋到結果的數量
    • 降噪:指降低文章中的一些低相關性的詞條對整體搜尋排序結果的干擾

組成

分析器一般都由三個構建組成,包括字元過濾器、分詞器、token過濾器組成

  • 字元過濾器:預處理,比如過濾html標籤
  • 分詞器:例如英文分詞可以通過按照空格將單詞分開,而中文分詞就要複雜一些,一般通過機器學習演算法來分詞
  • token過濾器:對分詞後單詞就行加工,比如大小寫轉換、去掉一些停用詞如英文(a、and、the),中文(的、地、了)等

分詞器

  • 預設分詞器:Standard Analyzer,基於Unicode文字分格演算法,按詞切分,小寫處理,適用於大多數語言
  • 簡單分詞器:Simple Analyzer,按照非字母切分符號過濾掉,小寫處理
  • 空格分詞器:Whitespace Analyzer,按照空格進行分詞,不轉小寫,中文不分詞
  • 停用詞分析器:StopAnalyzer:小寫處理,停用詞過濾
  • 常用語言分詞器
  • 自定義分詞器

中文分詞器

中文分詞器目前比較推薦的是IK分詞器,為何要進行分詞,如果沒有分詞“我愛中國”就會被拆分為四個單獨的子,顯然不符合我們中文的語義

IK分詞器原始碼地址 https://github.com/hutea/ikanalyzer

IK Analyzer是一個開源的,基於java語言開發的輕量級的中文分詞工具包。從2006年12月推出1.0版開始, IKAnalyzer已經推出了4個大版本。最初,它是以開源專案Luence為應用主體的,結合詞典分詞和文法分析演算法的中文分片語件。從3.0版本開始,IK發展為面向Java的公用分片語件,獨立於Lucene專案,同時提供了對Lucene的預設優化實現。在2012版本中,IK實現了簡單的分詞歧義排除演算法,標誌著IK分詞器從單純的詞典分詞向模擬語義分詞衍化。 IK Analyzer 2012特性:

  1. 採用了特有的“正向迭代最細粒度切分演算法“,支援細粒度和智慧分詞兩種切分模式;
  2. 在系統環境:Core2 i7 3.4G雙核,4G記憶體,window 7 64位, Sun JDK 1.6_29 64位 普通pc環境測試,IK2012具有160萬字/秒(3000KB/S)的高速處理能力。
  3. 2012版本的智慧分詞模式支援簡單的分詞排歧義處理和數量詞合併輸出。
  4. 採用了多子處理器分析模式,支援:英文字母、數字、中文詞彙等分詞處理,相容韓文、日文字元
  5. 優化的詞典儲存,更小的記憶體佔用。支援使用者詞典擴充套件定義。特別的,在2012版本,詞典支援中文,英文,數字混合詞語。
  • 分ik_smart:最粗力度拆,也即是最少的拆分

  • ik_max_word:也即是最細的粒度拆分

Elasticsearch和Solr比較

  • 如果對已有的資料進行搜尋,則Solr速度更快
  • 實時建立索引時,Solr會產生IO阻塞,查詢效能較差,而這種場景Elasticsearch具有明顯的優勢
  • 隨著資料量增大,Solr的搜尋效率的降低,而Elasticsearch卻沒有明顯的變化
  • Elasticsearch基本是開箱即用,比較簡單,Solr則略微複雜一點
  • Solr利用Zookeeper進行分散式管理,而Elasticsearch內建有分散式協調和管理功能
  • Solr比Elasticsearch支援更多格式的資料如Json、XML、CSV,而目前Elasticsearch僅支援Json
  • Solr提供功能較豐富,Elasticsearch則關注核心功能,高階功能可以使用第三方外掛整合,比如圖形化可以搭配Kibana使用
  • Solr比較成熟有一個更多使用者和開發者社群支援,而Elasticsearch是新興的,開發維護者相對較少,且更新速度太快,學習成本暫時較高
  • Solr查詢快,但是更新索引較慢,適用於查詢多更新不頻繁的場景比如電商領域,是傳統搜尋應用的有力解決方案;而Elasticsearch建立索引快,即實時性查詢快,更適用於新興實時搜尋應用如facebook、新浪等
  • 經過大型網際網路公司線上生產環境驗證,將搜尋引擎Solr替換為Elasticsearch後平均查詢速度提升近50倍左右

Elasticsearch核心概念

Elasticsearch核心內容

  • 資料型別:文件和索引
  • 資訊輸出:搜尋和資料分析
  • 分散式特點:可伸縮性和彈性

文件和索引

我們拿關係資料庫做層級的類比,在Elasticsearch叢集中可以存在多個index(索引可類比為資料庫或者表,index下type這個在6.x開始就不推薦且只有index只能有一個type,在7.x版本則是已去掉了,一個 index 中只有一個預設的 type,即 _doc),每個索引中可以包含多個document(記錄可類比為行),每個document下又可以包含多個Field(欄位類比列),mapping相當於我們表結構schema

  • 索引 index
    • 一個索引就是一個擁有幾分相似特徵的文件的集合。比如說,可以有一個客戶資料的索引,另一個產品目錄的索引,還有一個訂單資料的索引
    • 一個索引由一個名字來標識(必須全部是小寫字母的),並且當我們要對對應於這個索引中的文件進行索引、搜尋、更新和刪除的時候,都要使用到這個名字
  • 對映 mapping
    • ElasticSearch中的對映(Mapping)用來定義一個文件
    • mapping是處理資料的方式和規則方面做一些限制,如某個欄位的資料型別、預設值、分詞器、是否被索引等等,這些都是對映裡面可以設定的
  • 欄位Field
    • 相當於是資料表的欄位|列
  • 欄位型別 Type
    • 每一個欄位都應該有一個對應的型別,例如:Text、Keyword、Byte等
  • 文件 document
    • 一個文件是一個可被索引的基礎資訊單元,類似一條記錄,文件以JSON(Javascript Object Notation)格式來表示;
    • ElasticSearch是面向文件的,所以文件也是ElasticSearch搜尋和索引資料的最小單位
    • 有靈活的結構,可以動態新增文件的欄位,不需要像關係資料庫那樣得先定義好欄位
  • 叢集 cluster
    • 一個叢集就是由一個或多個節點組織在一起,它們共同持有整個的資料,並一起提供索引和搜尋功能
  • 節點 node
    • 一個節點是叢集中的一個伺服器,作為叢集的一部分,它儲存資料,參與叢集的索引和搜尋功能
    • 一個節點可以通過配置叢集名稱的方式來加入一個指定的叢集。預設情況下,每個節點都會被安排加入到一個叫做“elasticsearch”的叢集中
    • 這意味著,如果在網路中啟動了若干個節點,並假定它們能夠相互發現彼此,它們將會自動地形成並加入到一個叫做“elasticsearch”的叢集中
    • 在一個叢集裡,可以擁有任意多個節點。而且,如果當前網路中沒有執行任何Elasticsearch節點,這時啟動一個節點,會預設建立並加入一個叫做“elasticsearch”的叢集。所以在ES中,一個節點就可以是一個叢集
  • 分片和副本 shards&replicas
    • 分片 shards
      • 一個索引可以儲存超出單個結點硬體限制的大量資料。比如,一個具有10億文件的索引佔據1TB的磁碟空間,而任一節點都沒有這樣大的磁碟空間;或者單個節點處理搜尋請求,響應太慢,為了解決這個問題,Elasticsearch提供了將索引劃分成多份的能力,這些份就叫做分片
      • 當建立一個索引的時候,可以指定你想要的分片的數量
      • 每個分片本身也是一個功能完善並且獨立的“索引”,這個“索引”可以被放置到叢集中的任何節點上
      • 分片很重要,主要有兩方面的原因
        • 允許水平分割/擴充套件你的內容容量
        • 允許在分片之上進行分散式的、並行的操作,進而提高效能/吞吐量
      • 至於一個分片怎樣分佈,它的文件怎樣聚合回搜尋請求,是完全由Elasticsearch管理的,作為使用者來說,這些都是透明的
    • 副本 replicas
      • 在一個網路/雲的環境裡,失敗隨時都可能發生,在某個分片/節點不知怎麼的就處於離線狀態,或者由於任何原因消失了,這種情況下,有一個故障轉移機制是非常有用並且也是強烈推薦的。為此Elasticsearch允許你建立分片的一份或多份拷貝,這些拷貝叫做副本
      • 副本之所以重要,有兩個主要原因
        • 在分片/節點失敗的情況下,提供了高可用性
          • 注意到複製分片從不與原/主要(original/primary)分片置於同一節點上是非常重要的
        • 擴充套件搜尋量/吞吐量,因為搜尋可以在所有的副本上並行執行
          • 每個索引可以被分成多個分片。一個索引有0個或者多個副本
    • 一旦設定了副本,每個索引就有了主分片和副本分片,分片和副本的數量可以在索引,建立的時候指定,在索引建立之後,可以在任何時候動態地改變副本的數量,但是不能改變分片的數量

Elasticsearch部署

Elasticsearch官網下載地址

部署方式可以有多種,現在是容器化時代,由於之前文章我們已學習過Docker,所以我們選擇基於Docker容器化部署,基於docker可以選擇單機部署,基於K8S部署後續有時間再補充;可以選擇單節點叢集或多節點叢集部署,我們直接選擇官網例子基於單臺宿主機docker-compose三個節點的叢集部署

預設情況下,Elasticsearch會根據節點的角色和節點容器可用的總記憶體自動調整JVM堆的大小。對於大多數生產環境,我們建議使用此預設分級。如果需要,您可以通過手動設定JVM堆大小來覆蓋預設大小。

要在生產中手動設定堆大小,繫結掛載在/usr/share/elasticsearch/config/ JVM .options下的JVM選項檔案。D,其中包含所需的堆大小設定。

為了進行測試,您還可以使用ES_JAVA_OPTS環境變數手動設定堆大小。例如,要使用16GB,請指定-e

ES_JAVA_OPTS="-Xms16g -Xmx16g"與docker執行。ES_JAVA_OPTS變數覆蓋所有其他JVM選項。ES_JAVA_OPTS變數覆蓋所有其他JVM選項。我們不建議在生產環境中使用ES_JAVA_OPTS。

建立docker-compose.yml文件,在這個例子中使用ES_JAVA_OPTS環境變數手動設定堆大小為512MB。

version: '2.2'
services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.14.0
    container_name: es01
    environment:
      - node.name=es01
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es02,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - elastic
  es02:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.14.0
    container_name: es02
    environment:
      - node.name=es02
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data02:/usr/share/elasticsearch/data
    networks:
      - elastic
  es03:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.14.0
    container_name: es03
    environment:
      - node.name=es03
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data03:/usr/share/elasticsearch/data
    networks:
      - elastic

volumes:
  data01:
    driver: local
  data02:
    driver: local
  data03:
    driver: local

networks:
  elastic:
    driver: bridge

執行docker-compose以啟動叢集,先建立網路

docker-compose up

執行後出現如下報錯後退出了

es01 exited with code 78
es02 exited with code 78
es03 exited with code 78

我們通過其中一個容器檢視日誌 docker logs -tf --tail 10 052a5cf054a5

#通過root使用者執行命令:
sysctl -w vm.max_map_count=262144
#檢視結果:
sysctl -a|grep vm.max_map_count
#顯示:
vm.max_map_count = 262144
#上述方法修改之後,如果重啟虛擬機器將失效,我們需要在 /etc/sysctl.conf檔案最後新增一行vm.max_map_count=262144即可永久修改

重新執行即可正常使用,訪問 http://192.168.50.94:9200/ ,至此我們安裝完畢,非常簡單

ES自身也提供很多叢集維護查詢命令,有興趣可以自己查閱學習

安裝Head外掛

//docker下載elasticsearch-head:5外掛
docker pull mobz/elasticsearch-head:5
//docker啟動elasticsearch-head:5容器,elastic_search_elastic為我們上一節安裝的docker網路
docker run -d --network elastic_search_elastic -p 9100:9100 mobz/elasticsearch-head:5

啟動後在流量訪問9100埠:http://192.168.50.94:9100/,設定連線訪問任意一臺ES服務(本文為http://192.168.50.94:9200/,也即是採用上一小節在192.168.50.94部署docker的ES叢集且暴露的9200埠),檢視瀏覽控制檯輸出跨域錯誤,因此我們解決跨域問題

通過docker ps 找到我們ES叢集所有容器,逐個進入容器的內容修改config修改config/elasticsearch.yml檔案,然後重新啟動ES叢集

Copy# 開啟跨域
http.cors.enabled: true
# 允許所有
http.cors.allow-origin: "*"

再次訪問就可以了

我們新建一個索引庫,分片數為5副本數為2

五個主分片負載均衡分佈在三個節點上,head外掛也提供資料瀏覽、基本查詢和符合查詢,但我們基本不怎麼使用,常用是使用Kibana的Devtools,下節我們安全後學習

安裝Kibana

Kibana官網地址 https://www.elastic.co/cn/kibana/

基於docker安裝Kibana https://www.elastic.co/guide/en/kibana/current/docker.html

新建kibana.yml

server.name: kibana
# kibana的主機地址 0.0.0.0可表示監聽所有IP
server.host: "0.0.0.0"
# kibana訪問es的URL
elasticsearch.hosts: ["http://es01:9200","http://es02:9200","http://es03:9200"]
# 顯示登陸頁面
xpack.monitoring.ui.container.elasticsearch.enabled: true

通過Kinana官網的安裝說明指引,我們建立docker-compose.yml,es01、es02、es03為之前建立ES叢集伺服器名稱,elastic_search_elastic為建立ES叢集建立網路,這樣可以通過主機名稱訪問

version: '2'
services:
  kibana:
    image: docker.elastic.co/kibana/kibana:7.14.1
    volumes:
   	  - /home/docker_resource/kibana/config/kibana.yml:/usr/share/kibana/config/kibana.yml
    ports:
      - "5601:5601"
    environment:
      SERVER_NAME: kibana
      ELASTICSEARCH_HOSTS: '["http://es01:9200","http://es02:9200","http://es03:9200"]'
networks:
  default:
    external:
      name: elastic_search_elastic

然後在docker-compose.yml的目錄下執行命令啟動kibana容器服務

docker-compose up -d

訪問Kibana暴露的埠地址:http://192.168.50.94:5601

點選Dev tools進入到開發工具介面

安裝elasticsearch-analysis-ik

elasticsearch-analysis-ik原始碼地址 https://github.com/medcl/elasticsearch-analysis-ik

通過GitHub上的安裝說明我們下載預構建的包

預構建的包下載地址 https://github.com/medcl/elasticsearch-analysis-ik/releases

Analyzer: ik_smart, ik_max_word,

Tokenizer: ik_smart, ik_max_word

下載elasticsearch-analysis-ik-7.14.0.zip完畢後,在所有elasticsearch服務的安裝目錄下的plugins目錄下新建ik目錄,將上面zip包的內容全部放到ik

可以通過從宿主機拷貝到容器的方式

docker cp ik/ 9bad7f196e38:/usr/share/elasticsearch/plugins

拷貝到容器下的目錄結構為下面所示

然後重啟所有elasticsearch服務服務,每臺elasticsearch服務會有出現這個日誌資訊,代表載入了ik外掛

"message": "loaded plugin [analysis-ik]"

還可以通過執行bin目錄下./elasticsearch-plugin list 檢視載入外掛資訊

通過kibana測試下ik

先試用ik_smart最粗力度拆分測試下

再使用ik_max_word最細的粒度拆分測試下

這就需要我們進行自定義拆分的詞典,將自定義分詞加到ik分詞器的字典裡

新建itxs.dic檔案,在IKAnalyzer.cfg.xml修改如下內容itxs.dic

重啟所有ES服務,檢視ES服務的日誌會發現有如下資訊代表載入我們指定以分詞字典檔案

"message": "[Dict Loading] /usr/share/elasticsearch/plugins/ik/config/itxs.dic"

重新執行查詢後發現"雲原生"已經被拆分為一個單詞了

Elasticsearch的使用

入門說明

Elasticsearch文件官網地址 https://www.elastic.co/guide/en/elasticsearch/reference/7.14/index.html

官方提供非常詳細開發和維護使用的指南,官方上中文文件已經比較老了,建議用最新發布版本的英文文件,如需全面學習有興趣可以自己研究,其中也有REST APIs介面

基本概念

  • text型別:會分詞,先把物件進行分詞處理,然後再再存入到es中。當使用多個單詞進行查詢的時候,當然查不到已經分詞過的內容!

  • keyword:不分詞,沒有把es中的物件進行分詞處理,而是存入了整個物件!這時候當然可以進行完整地查詢!預設是256個字元!

  • 動態對映:就是自動創建出來的對映,es 根據存入的文件,自動分析出來文件中欄位的型別以及儲存方式,這種就是動態對映

  • 靜態對映:在建立索引就指定好對映,屬於明確的對映

基礎操作

  • 建立索引

格式: PUT /索引名稱

PUT /es_dbtest 
  • 查詢索引

格式: GET /索引名稱

GET /es_dbtest
  • 刪除索引

格式: DELETE /索引名稱

DELETE /es_dbtest
  • 新增文件

格式: PUT /索引名稱/型別/id

PUT /es_dbtest/_doc/1
{"name": "張三","sex": 1,"age": 25,"address": "深圳龍華觀瀾"}
PUT /es_dbtest/_doc/2
{"name": "李四","sex": 1,"age": 28,"address": "深圳福田樞紐大廈"}
PUT /es_dbtest/_doc/3
{"name": "王五","sex": 2,"age": 18,"address": "深圳寶安機場"}
  • 修改文件

格式: PUT /索引名稱/型別/id

PUT /es_dbtest/_doc/1
{"name": "張三三","sex": 1,"age": 25,"address": "深圳龍華觀瀾高爾夫"}
  • 查詢文件

格式: GET /索引名稱/型別/id

GET /es_dbtest/_doc/1
  • 刪除文件

格式: DELETE /索引名稱/型別/id

DELETE /es_dbtest/_doc/1
  • POST和PUT都能起到建立/更新的作用
    • 需要注意的是PUT需要對一個具體的資源進行操作也就是要確定id才能進行更新/建立,而POST是可以針對整個資源集合進行操作的,如果不寫id就由ES生成一個唯一id進行建立新文件,如果填了id那就針對這個id的文件進行建立/更新
    • PUT只會將json資料都進行替換, POST只會更新相同欄位的值
    • PUT與DELETE都是冪等性操作, 即不論操作多少次, 結果都一樣

查詢應用

Restful風格

Restful是一種面向資源的架構風格,可以簡單理解為:使用URL定位資源,用HTTP動詞(GET,POST,DELETE,PUT)描述操作。 基於Restful API ES和所有客戶端的互動都是使用JSON格式的資料.其他所有程式語言都可以使用RESTful API,通過9200埠的與ES進行通訊

  • GET查詢

  • PUT新增

  • POST修改

  • DELETE刪除

使用Restful的好處

  • 透明性,暴露資源存在。

  • 充分利用 HTTP 協議本身語義,不同請求方式進行不同的操作

請求體查詢

  • 查詢當前型別中的所有文件

格式: GET /索引名稱/型別/_search

GET /es_dbtest/_doc/_search

SQL: select * from student

  • 條件查詢

如要查詢age等於28歲的

格式: GET /索引名稱/型別/_search?q=age:28

GET /es_dbtest/_doc/_search?q=age:28

SQL: select * from student where age = 28

  • 範圍查詢

如要查詢age在25至26歲之間的

格式: GET /索引名稱/型別/_search?q=age[25 TO 26] 注意: TO 必須為大寫,

GET /es_dbtest/_doc/_search?q=age[25 TO 26]

SQL: select * from student where age between 25 and 26

  • 根據多個ID進行批量查詢 _mget

格式: GET /索引名稱/型別/_mget ,相當於SQL

GET /es_dbtest/_doc/_mget{  "ids":["2","3"] }

SQL: select * from student where id in (2,3)

  • 查詢年齡小於等於28歲的 :<=

格式: GET /索引名稱/型別/_search?q=age:<=28

GET /es_dbtest/_doc/_search?q=age:<=28

SQL: select * from student where age <= 28

  • 查詢年齡大於28前的 :>

格式: GET /索引名稱/型別/_search?q=age:>26

GET /es_dbtest/_doc/_search?q=age:>26

SQL: select * from student where age > 28

  • 分頁查詢

格式: GET /索引名稱/型別/_search?q=age[25 TO 26]&from=0&size=1

GET /es_dbtest/_doc/_search?q=age[25 TO 26]&from=0&size=1

SQL: select * from student where age between 25 and 26 limit 0, 1

  • 對查詢結果只輸出某些欄位

格式: GET /索引名稱/型別/_search?__source=欄位,欄位

GET /es_dbtest/_doc/_search?_source=name,age

SQL: select name,age from student

  • 對查詢結果排序

格式: GET /索引名稱/型別/_search?sort=欄位 desc

GET /es_dbtest/_doc/_search?sort=age:desc

SQL: select * from student order by age desc

查詢搜尋

我們實際場景很少使用到上面請求體查詢,更多會使用基於body內容的搜尋功能,我們這裡就不一一舉例,有興趣學習夥伴可以參照官網學習

GET /es_dbtest/_doc/_search
{
  "query":{
    "match": {
      "name" : "張三三"
    }
  }
}

GET /es_dbtest/_doc/_search
{
  "sort":[
    {
      "age": {
        "order":"asc"
      }
    }
  ],
  "from": 0,
  "size": 2
}

GET /es_dbtest/_doc/_search
{
    "term" : {
        "age" : 18
    }
}

GET /es_dbtest/_doc/_search
{
    "query" : {
        "constant_score" : { 
            "filter" : {
                "term" : { 
                    "age" : 18
                }
            }
        }
    }
}

GET /es_dbtest/_doc/_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "range" : {
                    "age" : {
                        "gte" : 5,
                        "lt"  : 40
                    }
                }
            }
        }
    },
    "sort":[
    {
      "age": {
        "order":"asc"
      }
    }
  ],
  "from": 0,
  "size": 2
}

GET /es_dbtest/_doc/_search
{
  "query": {
    "bool": {
      "must": {
        "match": {
          "age": 18
        }
      },
      "filter": {
        "term": {
          "sex": 2
        }
      }
    }
  }
}

可以針對結果進行高亮顯示

自定義搜尋高亮

批量獲取文件資料

通過_mget的API來實現的

  • 請求方式:GET
  • 請求地址:_mget
  • 請求引數:
    • 請求地址:_mget 在URL中不指定index和type
    • 請求地址:/{{indexName}}/_mget 在URL中指定index
    • 請求地址:/{{indexName}}/{{typeName}}/_mget 在URL中指定index和type
    • docs : 文件陣列引數
      • _index : 指定index
      • _type : 指定type
      • _id : 指定id
      • _source : 指定要查詢的欄位

bulk Api批量操作文件資料

批量對文件進行操作是通過_bulk的API來實現的

  • 可以批量對多個索引進行增加或者刪除等操作,減少網路請求次數,可以顯著的提高索引的速度。

  • CURD只能對單條資料進行操作,如果是資料匯入的情況下QPS會特別高。

  • 多個API操作之間的結果互不影響。

  • bulk操作不能進行程式碼換行

  • bulk會將要處理的資料載入記憶體中,所以資料量是有限的,最佳的資料兩不是一個確定的資料,它取決於你的硬體,你的文件大小以及複雜性,你的索引以及搜尋的負載,一般建議是1000-5000個文件,大小建議是5-15MB,預設不能超過100M

  • 請求方式:POST

  • 請求地址:_bulk

  • 請求引數:通過_bulk操作文件,一般至少有兩行引數(或偶數行引數)

    • 第一行引數為指定操作的型別及操作的物件(index,type和id)
    • 第二行引數才是操作的資料
  • actionName:表示操作型別,主要有create,index,delete和update

action(行為) desc(描述)
create 文件不存在時,建立
update 更新文件
index 建立新文件,或者替換已經有的文件
delete 刪除一個文件
{"actionName":{"_index":"indexName", "_type":"typeName","_id":"id"}}{"field1":"value1", "field2":"value2"}

(1)批量建立文件create

POST _bulk
{"create":{"_index":"product",  "_id":1}}
{"id":1,"name":"白菜","content":"甜","tags":["大", "綠"],"create_time":1630675861}
{"create":{"_index":"product", "_id":2}}
{"id":2,"name":"豬五花肉","content":"肥而不膩","tags":["色澤鮮豔", "新鮮"],"create_time":1630675861}

(2)批量修改update

POST _bulk
{"update":{"_index":"product",  "_id":1}}
{"doc":{"name":"大白菜"}}
{"update":{"_index":"product", "_id":2}}
{"doc":{"name":"豬後腿肉"}}

(3)批量刪除delete

POST _bulk
{"delete":{"_index":"product", "_id":1}}
{"delete":{"_index":"product", "_id":2}}

Spring Boot ES操作示例

概述

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html

找到官方提供客戶端使用文件

簡單示例程式

本示例主要基於Spring Data的啟動器spring-boot-starter-data-elasticsearch為核心,構建一個簡單Spring Boot程式

pom檔案

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itxs</groupId>
    <artifactId>elasticsearch-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.5.2</version>
    </parent>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <elasticsearch.version>7.14.1</elasticsearch.version>
    </properties>

    <repositories>
        <repository>
            <id>es-snapshots</id>
            <name>elasticsearch snapshot repo</name>
            <url>https://snapshots.elastic.co/maven/</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
    </dependencies>

</project>

application.yml檔案

spring:  elasticsearch:    rest:      uris: http://192.168.50.94:9200

當然也可以使用配置類的方式,下面ElasticSearchClientConfig.java是例子

package com.itxs.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticSearchClientConfig {

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

建立實體類User

package com.itxs.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private Integer age;
    private String address;
}

測試類

package com.itxs;

import com.alibaba.fastjson.JSON;
import com.itxs.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@SpringBootTest
@Slf4j
public class ElasticSearchTest {

    private static String USER_INDEX_CONST ="user_index";

    @Autowired
    RestHighLevelClient restHighLevelClient;

    //建立索引庫
    @Test
    public void CreateIndex() throws IOException {
        CreateIndexRequest request = new CreateIndexRequest(USER_INDEX_CONST);
        CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
        log.info("建立索引:{}",createIndexResponse);
    }

    //檢查索引庫是否存在
    @Test
    public void GetIndexExists() throws IOException {
        GetIndexRequest request = new GetIndexRequest(USER_INDEX_CONST);
        boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
        log.info("獲取索引是否成功:{}",exists);
    }

    //刪除索引庫
    @Test
    public void DeleteIndex() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest(USER_INDEX_CONST);
        AcknowledgedResponse delete = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
        log.info("刪除索引是否成功:{}",delete.isAcknowledged());
    }

    //索引庫裡新增文件
    @Test
    public void AddDocument() throws IOException {
        User user = new User("段樂樂", 20, "北京朝陽");
        IndexRequest request = new IndexRequest(USER_INDEX_CONST);
        request.id("1");
        request.timeout(TimeValue.timeValueSeconds(1));
        request.source(JSON.toJSONString(user), XContentType.JSON);
        IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        log.info("返回值內容:{},狀態:{}",indexResponse.toString(),indexResponse.status());
    }

    //索引庫裡檢查文件是否存在
    @Test
    public void GetDocumentExists() throws IOException {
        GetRequest getRequest = new GetRequest(USER_INDEX_CONST, "1");
        getRequest.fetchSourceContext(new FetchSourceContext(false));
        getRequest.storedFields("_none_");
        boolean exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
        log.info("文件內容是否存在:{}",exists);
    }

    //查詢索引庫裡文件內容
    @Test
    public void GetDocument() throws IOException {
        GetRequest getRequest = new GetRequest(USER_INDEX_CONST, "1");
        GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
        log.info("文件內容:{},全部內容:{}",getResponse.getSourceAsString(),getResponse);
    }

    //更新索引庫裡文件內容
    @Test
    public void UpdateDocument() throws IOException {
        UpdateRequest updateRequest = new UpdateRequest(USER_INDEX_CONST, "1");
        updateRequest.timeout(TimeValue.timeValueSeconds(1));
        User user = new User("張三丰", 18, "上海浦東");
        updateRequest.doc(JSON.toJSONString(user),XContentType.JSON);
        UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        log.info("更新文件結果狀態:{}",updateResponse.status());
    }

    //刪除索引庫裡文件內容
    @Test
    public void DeleteDocument() throws IOException {
        DeleteRequest deleteRequest = new DeleteRequest(USER_INDEX_CONST, "1");
        deleteRequest.timeout(TimeValue.timeValueSeconds(1));
        DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        log.info("刪除文件結果狀態:{}",deleteResponse.status());
    }

    @Test
    public void BulkDocument() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout(TimeValue.timeValueSeconds(1));
        List<User> userList = new ArrayList<>();
        userList.add(new User("韓梅梅",22,"長沙"));
        userList.add(new User("馬濤濤",25,"成都"));
        userList.add(new User("李南",28,"南昌"));
        for (int i = 0; i < userList.size(); i++) {
            bulkRequest.add(new IndexRequest(USER_INDEX_CONST).id(""+		(i+10)).source(JSON.toJSONString(userList.get(i)),XContentType.JSON));
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        log.info("批量新增文件結果狀態:{}",bulk.status());
    }
}

Elastic Stack(待續)

The Elastic Stack官方地址 https://www.elastic.co/cn/elastic-stack/

Elastic Stack核心產品包括 Elasticsearch、Kibana、Beats 和 Logstash(也稱為 ELK Stack)。能夠安全可靠地獲取任何來源、任何格式的資料,然後實時地對資料進行搜尋、分析和視覺化。Elasticsearch是位於Elastic Stack核心的分散式搜尋和分析引擎,Logstash和Beats有助於收集、聚合和豐富資料,並將其儲存在Elasticsearch中。Kibana使您能夠互動式地探索、視覺化和共享對資料的見解,並管理和監控堆疊。Elasticsearch是索引、搜尋和分析魔術發生的地方

本篇只是入門,下篇我們再來學習Elasticsearch進階內容和ELK等Elastic Stack技術棧研究已經基於搜尋引擎的專案實戰,希望一起學習的夥伴覺得不錯的可以關注下本人的部落格網站