Try   HackMD

Elasticsearch 學習筆記

介紹

Elasticsearch 是一個免費的、分散式的、RESTful 風格的全文檢索引擎,它基於 Apache Lucene 開發而成。不僅支持全文檢索,還具有分散式和高可用性的特點,使其成為大規模數據檢索和分析的理想選擇。以下是主要特點:

  • 分散式架構:可以自動將數據和查詢負載分散到多個節點,實現橫向擴展和高可用性。
  • 實時檢索:提供近實時的檢索性能,使數據在被索引後很快就可以被查詢到。
  • 高度可擴展:可以輕鬆擴展到數百個節點,並且可以處理 PB 級別的數據。
  • RESTful API: 使用標準HTTP方法(GET/POST/PUT/DELETE)進行操作。
  • 強大的查詢和分析功能:支持多種查詢類型,如全文檢索、範圍查詢、地理位置查詢等,並提供豐富的數據聚合和分析功能。

Apache Lucene: 用於全文檢索和搜尋的開放原始碼程式庫

全文檢索引擎的意義和用途:

全文檢索引擎是一種專門用於在大量文本數據中快速查找特定關鍵字或短語的軟件。通過建立索引和使用高效的檢索算法,全文檢索引擎可以在短時間內返回與查詢條件相匹配的結果。全文檢索引擎在各種應用場景中都有廣泛的用途,例如:

  • 搜索引擎:構建網絡搜索引擎的核心技術之一,用於在數據庫中查找與用戶查詢相關的網頁。
  • 日誌分析:可以用於分析和查找大量的應用程序和系統日誌,以快速定位問題和分析性能指標。
  • 文檔管理:可以幫助用戶在數據庫中快速查找和管理大量文檔,提高工作效率。
  • 電子商務:可以用於構建高效的商品搜索和推薦系統,幫助用戶快速找到所需的產品。
  • 社交媒體:可以用於實時檢索和分析社交媒體上的內容,以發現熱門話題和趨勢。
  • 新聞和媒體:可以用於新聞網站和媒體資源庫,以便讀者快速找到感興趣的文章和報導。
  • 知識管理:可以用於構建企業知識庫,幫助員工快速查找和共享知識資源。

基本概念

  • 索引 (index): 搜尋引擎需要先將要檢索的文檔建立索引,以方便搜索。索引可以看作是一個文檔的目錄,包含了文檔中每個詞的位置。一個索引可以包含多個 shard,類似於關係型數據庫中的表(table)。
  • 文檔 (document): 是指一個 JSON 格式的資料物件,在搜尋引擎中最小的數據單元,一個文檔通常表示一個實體,如一篇文章、一個用戶、一張圖片等。每個文檔在 index 中有唯一的 document ID,一個文檔內包含多個字段(field)。類似於關係型數據庫中的行(row)
  • 分片 (shard): 將索引拆分成多個更小、更易於管理的部分。每個分片本身就是一個完整的搜索引擎實例,擁有獨立的索引和檢索能力。可分散在不同的節點中。允許同時進行檢索操作,提高檢索速度。
    • 主分片(Primary Shard):這是創建索引時預先分配的分片數量。每個主分片都包含索引數據的一部分。主分片的數量在創建索引時設置,並且在索引生命周期內不能更改。
    • 副本分片(Replica Shard):這是主分片的副本,用於增加數據的冗餘和提高查詢性能。副本分片可以在索引創建後動態調整數量。
  • 檢索 (search): 當使用者輸入關鍵詞時,搜尋引擎會通過索引快速找到包含該詞的文檔,並按照一定的算法對文檔進行排名,最終返回一個搜索結果列表。
  • 相似度算法: 搜尋引擎需要將檢索到的文檔按照一定的算法進行排名,以保證搜索結果準確性。常用的算法包括 TF-IDF 算法、PageRank算法等。
  • 分詞器 (analyzer): 是用於將文本轉換為索引中可搜索詞項的一種組件。它是由多個步驟組成的,包括字符過濾器、分詞器和標記過濾器等。當文本被索引時,analyzer 會將文本進行分析並生成一組可搜索的詞項,以便在搜索時可以匹配相應的文檔。

資料結構:

Elasticsearch 使用一種名為倒序索引(Inverted Index)的數據結構來實現高效的全文檢索。倒序索引將文檔中的詞語與包含該詞語的文檔列表關聯起來,從而在檢索時能夠快速找到包含特定詞語的文檔。

與 Solr 名詞比較表

概念 Elasticsearch Solr
索引數據的單位 Index Core
文檔結構描述 Mapping Schema
查詢語法 Query DSL Solr Query
儲存數據的最小單位 Document Document
儲存單位 Shard Shard
儲存單位拷貝 Replica Replica
文檔中的字段 Field Field
分析器 Analyzer Analyzer
篩選器 Filter Filter

安裝與配置

作業系統: Debian 9
JAVA版本: 7.x 建議是 JAVA 11

每個 elasticsearch 版本,搭配可支援的作業系統跟 JAVA版本。詳細的版本支援

sudo apt-get update && sudo apt-get install elasticsearch

常用檔案位置

Name Path
基本設定檔 /etc/elasticsearch/elasticsearch.yml
jvm 設定檔 /etc/elasticsearch/jvm.options
plugin 執行檔 /usr/share/elasticsearch/bin/elasticsearch-plugin

參數配置

  • elasticsearch.yml

    • cluster.name: 群集名稱,節點會加入相同名稱的群集
    • cluster.initial_master_nodes:用於指定集群的初始主節點。
    • node.name: 節點名稱,方便區別不同的節點
    • http.port:服務的 HTTP 端口。默認值是 9200。
    • path.data: 索引儲存的路徑
    • path.logs: 日誌儲存的路徑
    • node.master: 是否為主節點
    • node.data: 是否為資料節點
    • node.ingest: 是否為預處理節點 (預設為 true)
  • jvm.options

    • Xms: JVM 啟動時最小內存分配量
    • Xmx: JVM 最大的內存分配量

節點角色

一個節點可以是包含一至多個節點角色

  • 主節點(master node):主節點用於管理索引和集群級別的操作,如創建、刪除索引、節點加入或退出集群等。每個集群只能有一個主節點,如果主節點出現故障,集群將自動重新選舉新的主節點。

    • node.master: true
  • 資料節點(data node):資料節點用於存儲索引資料和執行 CRUD(創建、讀取、更新、刪除)操作。資料節點負責維護索引的完整性和一致性,並將資料分片存儲在本地硬碟上。一個集群可以有多個資料節點,資料節點之間可以相互複製和同步資料。

    • node.data: true
  • 預處理節點 (Ingest node): 在資料被索引之前,對資料進行預處理和轉換。各種資料轉換操作,包括資料格式轉換、資料清洗、資料修剪、資料解析等。

    • node.ingest: true
  • 協調節點(coordinating node):協調節點用於處理請求和協調集群中的操作。它不存儲資料,而是充當請求路由器和負載均衡器的角色。協調節點接收來自客戶端的請求,並將請求路由到相應的資料節點上進行處理,然後將結果返回給客戶端。

    • node.master: false
    • node.data: false

Plugin

Discovery-gce

如果是在 Google Compute Engine 內安裝 Elasticsearch,可以使用 discovery-gce 插件,這插件可以自動搜尋在同一個 GCP 專案內的 GCE 機器的群集加入,以下是需要在 elasticsearch.yml 增加的欄位。

cloud:
  gce:
    project_id: <your-google-project-id>
    zone: <your-zone>
discovery:
  seed_providers: gce

Repository-gce

如果需要將索引備份到 Google Cloud Storage 內,可以使用 repository-gce 插件。

  • 需要 GCP service account

IK Analyzer

適用於中文文本的 Elasticsearch 分析器,透過結合 IK 中文分詞器的分詞能力,對中文文本進行詞法分析,以實現更加精準的搜索。

ik_analyzer 可以對中文文本進行以下處理:

  • 中文分詞:將中文文本按照一定的規則進行分詞,以便搜尋引擎能夠更好地理解文本內容。
  • 去除停用詞:對於一些無意義的詞語(如「的」、「了」等),ik_analyzer 可以將其去除,以減少搜尋引擎的計算量。
  • 同義詞轉換:對於一些具有相同意義的詞語,ik_analyzer 可以將其轉換為一個標準詞語,以便搜尋引擎更好地理解搜尋意圖。

需要在 Elasticsearch 內安裝,如果有多台機器則需要在每個機器上皆安裝,各機器內的字典檔也必須同步。Elasticsearch 與 ik_analyzer 須搭配相同版本。實際的安裝方式可以參考官方的網頁的說明

由於是基於字典的分詞方式,所以有個合適的字典也是很重要的。除了可以先將自訂字典檔載入,還可以利用即時的線上字典,更新原有的字典檔。此方法不需要再重新啟動 Elasticsearch。只是要注意,字典檔只對,新的文本有效,對過去已索引的資料,並不會重新針對新的字典檔重新索引。如果要更新現有的資料,必須要手動重新索引資料。
(字典放置於 Google Cloud Storage 中,利用公開的網址存取)

IKAnalyzer.cfg.xml 內可以設定字典位置,

  • ext_dict: 擴充字典檔位置,
  • ext_stopwords: 擴充停用詞字典檔位置
  • remote_ext_dict: 可設定外部線上字典,更新的方式是每 60 秒會利用 HEAD Trigger 連結,確認 header 內的 Last-Modified 或是 ETag 有沒有變化。如果有,就會 GET 方法,取得最新的字典檔,並重新更新現有的字典檔。

更新 IKAnalyzer.cfg.xml 內容後,需要重新啟動 Elasticsearch 才能啟用。

配置檔案內容範例如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer Plugin 配置</comment>
    <entry key="ext_dict">custom/mydict.dic;custom/single_word_low_freq.dic</entry>
    <entry key="ext_stopwords">custom/ext_stopword.dic</entry>
    <entry key="remote_ext_dict">location</entry>
    <entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry>
</properties>

如何確認 plugin 是否有安裝,以及其安裝版本

GET /_cat/plugins

分詞方法

IK analyzer 有提供兩種不同的分詞方法,分別為 ik_max_wordik_smart

  • ik_max_word: 會將文本按照最大長度進行切分,即盡可能將文本切分為有意義的詞語。
    觀光官方網站 會切分為 觀光官方網站官方網站
  • ik_smart: 較智能的分詞方式,它能夠根據上下文關係和語義信息對文本進行智能切分。
    觀光官方網站 會切分為 觀光官方網站

會影響效能的因素

  • 硬體配置:Elasticsearch 需要大量的計算和存儲資源,硬體配置的好壞會直接影響效能。例如 CPU、內存、硬碟等的性能都會影響檢索和寫入效率。
  • 數據量和索引結構:索引的大小和結構會影響效能。如果索引中包含大量的文檔和字段,查詢和聚合操作可能會變得緩慢。
  • 查詢複雜度:複雜的查詢語句需要更多的計算資源,會降低效能。例如複雜的聚合操作、多重嵌套(Nested)的查詢語句等。
  • 網絡和集群配置:網絡的性能和集群的配置會影響效能和可靠性。例如節點之間的網絡頻寬、網絡延遲、數據分片和副本的配置等。
  • 索引和查詢緩存:Elasticsearch 支持使用索引和查詢緩存來提高檢索速度。如果緩存設置不合理或緩存的數據不穩定,會降低效能。
  • 數據寫入頻率:如果數據寫入頻率過高,可能會影響效能。特別是在使用索引和查詢緩存的情況下,寫入操作可能會導致緩存失效。

Index

建立 Index

三個部分可做調整,對系統的設定(settings)、欄位名稱對映的類型(mappings)、別名設定(aliases)。

fielddata: 是否允許 Elasticsearch 將該欄位的值載入到記憶體中,以便在搜索過程中快速存取和處理該欄位的值。

是一種將文件中的欄位值載入到記憶體中以便搜索的資料結構。它允許 Elasticsearch 在搜索過程中快速存取和處理欄位資料,以提高搜索效能。

當我們執行某些類型的查詢時,Elasticsearch 需要在索引中載入欄位資料,例如聚合(aggregation)、排序、腳本腳本和文字查詢(text query)。這些查詢需要快速存取和處理欄位資料,以便在搜索結果中對文件進行排序或聚合。

為了實現這個目的,Elasticsearch 使用 fielddata 結構將欄位資料載入到記憶體中,使得搜索過程更快速、更高效。fielddata 結構被設計為緊湊的記憶體結構,可以減少磁碟 I/O 操作,從而提高查詢效能。

需要注意的是,由於 fielddata 需要將欄位值載入到記憶體中,因此它可能會佔用大量的記憶體。如果使用不當,fielddata 可能會導致節點記憶體不足或者效能下降。因此,在使用 Elasticsearch 時,應該謹慎使用 fielddata,並根據實際情況進行優化和調整。

  • Settings
    • refresh_interval:設置新文檔添加到索引後多久可供搜索的時間間隔。默認值為 1s。
    • number_of_replicas: 設置索引的副本分片(replica shards)數量
    • max_result_window: 設置分頁查詢時允許的最大結果窗口大小。默認值為 10000。
  • Mappings
    • Field 類型:
      • text: 儲存文字,會進行分詞處理,以便於進行全文檢索。
      • boolean: true or false
      • keywords: 儲存關鍵字或短語。不會進行分詞處理,可用於精確的過濾。
      • numbers: 儲存數字,可用於範圍(range)搜尋跟排序。
      • dates: 儲存日期和時間,可用於日期範圍的查詢跟排序。
    • Analyzer:
      • ik_smart
      • ik_max_word
  • Aliases
    • 索引別名,用於將索引分群或是隱藏實際索引名稱,方便管理。
    • 不能是現有的索引名稱。
    • 如果在多個索引,加上相同的索引別名,要注意避免同時對大量索引同時更新、刪除。

PUT /<INDEX_NAME>

{
    "aliases": {
        "example_alias_1": {}
    }, 
    "mappings": {
        "properties": {
            "content": {
                "analyzer": "ik_max_word", 
                "type": "text"
            }, 
            "createtime": {
                "type": "date"
            }, 
            "status": {
                "type": "keyword"
            }, 
            "title": {
                "analyzer": "ik_max_word", 
                "type": "text"
            }
        }
    }, 
    "settings": {
        "number_of_replicas": 1, 
        "number_of_shards": 1,
        "max_result_window": 20000
    }
}

Index Template

預先定義好的模板,用於自動設置新建的索引的設定、映射、別名。當新建一個索引時,如果索引名稱有相對應的模板,模板會自動應用到新的索引上。確保新的索引跟舊的索引有一致性。

PUT /_index_template/<TEMPLATE_NAME>

{
    "index_patterns": [
        "example-*"
    ], 
    "template": {
        "aliases": {
            "example_alias_1": {}
        }, 
        "mappings": {
            "properties": {
                "content": {
                    "analyzer": "ik_max_word", 
                    "type": "text"
                }, 
                "createtime": {
                    "type": "date"
                }, 
                "status": {
                    "type": "keyword"
                }, 
                "title": {
                    "analyzer": "ik_max_word", 
                    "type": "text"
                }
            }
        }, 
        "settings": {
            "max_result_window": 20000, 
            "number_of_replicas": 1, 
            "number_of_shards": 1
        }
    }
}

Search Query-DSL 介紹

  • query_string: 複雜語法搜尋,針對一個或多個欄位進行全文檢索

    • 以下範例是搜尋 title台電,且 content 中有出現 電廠 的文檔
      ​​​​​​​​{
      ​​​​​​​​  "query": {
      ​​​​​​​​    "query_string": {
      ​​​​​​​​      "query": "title:台電 AND content:電廠"
      ​​​​​​​​    }
      ​​​​​​​​  }
      ​​​​​​​​}
      
  • term: 字段搜尋,此搜尋是不會針對字段分詞,需要精准匹配。

    • 以下範例是搜尋 tags 內有 機場 的文檔
      ​​​​​​​​{
      ​​​​​​​​  "query": {
      ​​​​​​​​    "term": {
      ​​​​​​​​      "tags": "機場"
      ​​​​​​​​    }
      ​​​​​​​​  }
      ​​​​​​​​}
      
    • 支援多個字段的搜尋,以下範例是搜尋 tags 內有 機場桃園 的文檔
      ​​​​​​​​{
      ​​​​​​​​  "query": {
      ​​​​​​​​    "terms": {
      ​​​​​​​​      "tags": ["機場", "桃園"]
      ​​​​​​​​    }
      ​​​​​​​​  }
      ​​​​​​​​}
      
  • range:

    • 以下範例是搜尋 publish_date2022-01-012022-12-31 的文檔,包含前後兩天。
      ​​​​​​​​{
      ​​​​​​​​  "query": {
      ​​​​​​​​    "range": {
      ​​​​​​​​      "publish_date": {
      ​​​​​​​​        "gte": "2022-01-01",
      ​​​​​​​​        "lte": "2022-12-31"
      ​​​​​​​​      }
      ​​​​​​​​    }
      ​​​​​​​​  }
      ​​​​​​​​}
      

進階應用

  • Boolean
    用於組合多個查詢條件的查詢類型。通過 bool 查詢,可以將多個查詢子句以布林邏輯(例如 AND、OR、NOT)進行組合,從而實現更複雜的查詢需求。bool 查詢中包含四種子句類型

    • must: 必須滿足所有指定的條件 (AND)
    • should: 滿足指定的一個或多個條件,通常與 minimum_should_match 參數搭配使用,以指定滿足至少多少個 should 條件。(OR)
    • must_not: 表示文檔必須不滿足指定的條件 (AND)
    • filter: 文檔必須滿足指定的過濾條件。不影響該文檔的分數,只關心文檔是否符合過濾條件。過濾查詢的結果通常會被緩存,可提高查詢效率。 (AND)

    以下的範例是搜尋,必須包含 Elasticsearch 關鍵字在標題中且 status 不是 draft,且至少包含 分散式全文檢索 在內文中,且publish_date2022-01-012022-12-31 間。

    ​​​​{
    ​​​​    "query": {
    ​​​​        "bool": { 
    ​​​​            "must": [
    ​​​​                {
    ​​​​                    "match": {
    ​​​​                        "title": "Elasticsearch"
    ​​​​                    }
    ​​​​                }
    ​​​​            ], 
    ​​​​            "must_not": [
    ​​​​                {
    ​​​​                    "term": {
    ​​​​                        "status": "draft"
    ​​​​                    }
    ​​​​                }
    ​​​​            ], 
    ​​​​            "should": [
    ​​​​                {
    ​​​​                    "match": {
    ​​​​                        "content": "分散式"
    ​​​​                    }
    ​​​​                }, 
    ​​​​                {
    ​​​​                    "match": {
    ​​​​                        "content": "全文檢索"
    ​​​​                    }
    ​​​​                }
    ​​​​            ],
    ​​​​            "minimum_should_match": 1,
    ​​​​            "filter": [
    ​​​​                {
    ​​​​                    "range": {
    ​​​​                        "publish_date": {
    ​​​​                            "gte": "2022-01-01", 
    ​​​​                            "lt": "2022-12-31"
    ​​​​                        }
    ​​​​                    }
    ​​​​                }
    ​​​​            ]
    ​​​​        }
    ​​​​    }
    ​​​​}
    
  • Aggregations

    針對搜尋的結果進行聚合操作,計算統計數據。

    以下的範例是使用 terms 聚合對文檔的 category 字段進行分組。將回傳每個狀態下的文檔數量。

    ​​​​{
    ​​​​    "query": {
    ​​​​        "match_all": {}
    ​​​​    },
    ​​​​    "aggs": {
    ​​​​        "group_by_status": {
    ​​​​            "terms": {
    ​​​​                "field": "status"
    ​​​​            }
    ​​​​        }
    ​​​​    }
    ​​​​}
    

update_by_query

用來對符合特定條件的所有文件進行更新,

  • 這個範例會將 myindex 索引中 field1 的值為 value1 的所有文件的 field2 更新為 newvalue。
POST /myindex/_update_by_query
{
  "script": {
    "source": "ctx._source.field2 = 'newvalue'"
  },
  "query": {
    "term": {
      "field1": "value1"
    }
  }
}
  • 這個範例會將 myindex 索引中所有文件的 count 欄位的值增加一。
POST /myindex/_update_by_query
{
  "script": {
    "source": "ctx._source.count += 1"
  }
}

delete_by_query

用來刪除符合特定條件的所有文件,可搭配搜尋語法使用

POST /myindex/_delete_by_query
{
  "query": {
    "term": {
      "field1": "value1"
    }
  }
}

使用時,有幾點需要注意:

  • 性能影響:可能會對 Elasticsearch 集群的性能造成顯著影響,尤其是當涉及到大量文檔時。此操作需要對每個匹配的文檔進行讀取、刪除和索引,因此可能需要消耗大量的計算資源。在高峰時段或資源有限的情況下,請謹慎使用。
  • 索引狀態:由於會修改索引,所以在操作過程中,索引會變得不再是只讀的。如果你的索引設置為只讀,你需要先取消只讀設置,然後再執行 delete_by_query。
  • 版本衝突:並不會解決版本衝突。如果在刪除操作執行期間,文檔被其他操作更新,則 delete_by_query 可能會失敗。你可以透過設定 conflicts 參數為 proceed 來忽略版本衝突。
  • 操作結果:操作是異步的,這意味著該操作會立即返回一個任務ID,並在背景中繼續執行。你可以使用這個任務ID來跟蹤操作的進度和結果。
  • 數據恢復:請注意,這操作是不可逆的,一旦文檔被刪除,就無法恢復。在執行此操作前,應確保有適當的數據備份,並確定該操作符合你的需求。

_aliases

是用於索引的邏輯名稱,可以用來替代實際的索引名。別名的使用具有許多優點和應用場景:

簡化操作:可以為複雜的、多索引的查詢操作創建一個別名,然後在後續的操作中只需要使用這個別名即可。
零停機時間的重建索引:當你需要改變索引的映射或者其他設置並且重建索引時,你可以創建一個新的索引,然後將別名從舊的索引移動到新的索引上,這樣不會影響正在使用別名的客戶端的操作。
時間序列數據:對於時間序列數據,你可以為每個時間週期(例如每天或者每月)創建一個新的索引,然後使用別名來代表所有的索引。這樣,你可以在不改變查詢代碼的情況下,輕鬆地添加新的索引和刪除舊的索引。

Cluster 相關 API

Explain API

當群集內,有未分配的 分片分配不均的問題,使用此 API 可以解釋該分片為什麼會在目前的分配狀態。
以下範例,myindex 是索引的名稱,0 是我們想要解釋的分片號碼,primary 表示我們正在解釋主分片。

這個請求將返回一個詳細解釋,說明該分片是否被分配,如果沒有被分配,為什麼沒有被分配,如果已經被分配,它是如何被分配的。

GET /_cluster/allocation/explain
{
  "index": "myindex",
  "shard": 0,
  "primary": true
}

Reroute API

手動將分片從一個節點移動到另一個節點。

  • 這樣的操作通常應謹慎使用。在大多數情況下,Elasticsearch 的內建分片平衡和分配機制應該能夠很好地處理分片分配問題。手動介入可能會打亂這些機制,可能會導致數據不均衡或其他問題。

在這個例子中,myindex 的 0號分片將從nodeA移動到nodeB。

POST /_cluster/reroute
{
  "commands": [
    {
      "move": {
        "index": "myindex", 
        "shard": 0,
        "from_node": "nodeA", 
        "to_node": "nodeB"
      }
    }
  ]
}