Elasticsearch原理解析與性能調優


基本概念

定義

  • 一個分布式的實時文檔存儲,每個字段 可以被索引與搜索
  • 一個分布式實時分析搜索引擎
  • 能勝任上百個服務節點的擴展,並支持 PB 級別的結構化或者非結構化數據

用途

  • 全文檢索
  • 結構化搜索
  • 分析

VS傳統數據庫

  • 傳統數據庫
    • 提供精確匹配
  • ES
    • 提供精確匹配
    • 全文檢索
    • 處理同義詞
    • 給文檔相關性評分
    • 生成分析與聚合數據
    • 實時

專有名詞

  • 索引(名詞)

    類似於數據庫

  • 索引(動詞)

    類似於insert。例如索引一個文檔到一個索引

  • 倒排索引

    默認每個屬性都會有一個倒排索引,可以設置屬性不被索引,它只能被覆蓋,不能被修改

  • 類型

    類似表,同一索引的不同類型,可以擁有不同的字段,但應該擁有大部分相似的字段。它可以包含大小寫,不能包含句號,不能以下划線開頭,長度限制為256.

  • Id

    文檔的id,可以在生成文檔時指定或自動生成,自動生成的ID,在大部分情況下多個節點的時候唯一。如果在創建文檔時,ID沖突,服務器會返回409

  • 文檔

    類似於記錄,文檔只能被替換,而不能被修改,文檔的字段類型需要一致,否則無法進行精確匹配

  • 精確值字段

    對於數字,日期,布爾和一個not_analyzed字段,進行查詢,會適用精確匹配

  • 全文搜索字段

    否則,進行相關性搜索

PUT /megacorp/employee/1
{
    "first_name" : "John",
    "last_name" :  "Smith",
    "age" :        25,
    "about" :      "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}

例如

  • megacorp是索引
  • employee是類型
  • 1是文檔id
  • json內容是文檔

交互

RESTful API

curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
VERB 適當的 HTTP 方法謂詞 : GETPOSTPUTHEAD 或者 DELETE
PROTOCOL http 或者 https(如果你在 Elasticsearch 前面有一個 https 代理)
HOST Elasticsearch 集群中任意節點的主機名,或者用 localhost 代表本地機器上的節點。
PORT 運行 Elasticsearch HTTP 服務的端口號,默認是 9200
PATH API 的終端路徑(例如 _count 將返回集群中文檔數量)。Path 可能包含多個組件,例如:_cluster/stats_nodes/stats/jvm
QUERY_STRING 任意可選的查詢字符串參數 (例如 ?pretty 將格式化地輸出 JSON 返回值,使其更容易閱讀)
BODY 一個 JSON 格式的請求體 (如果請求需要的話)

例子

request:

curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
    "query": {
        "match_all": {}
    }
}
'

response:

{
    "count" : 0,
    "_shards" : {
        "total" : 5,
        "successful" : 5,
        "failed" : 0
    }
}

檢索文檔功能

  • 獲取一個文檔

    GET /megacorp/employee/1
    
  • 簡單查詢

    GET /megacorp/employee/_search #查詢前十條記錄
    
    GET /megacorp/employee/_search?q=last_name:Smith  #查詢smith的前十條記錄
    
  • 表達式查詢

    GET /megacorp/employee/_search
    {
        "query" : {
            "match" : {
                "last_name" : "Smith"
            }
        }
    }
    
    GET /megacorp/employee/_search
    {
        "query" : {
            "bool": {
                "must": {
                    "match" : {
                        "last_name" : "smith" 
                    }
                },
                "filter": {
                    "range" : {
                        "age" : { "gt" : 30 } 
                    }
                }
            }
        }
    }
    
  • 全文檢索

    GET /megacorp/employee/_search
    {
        "query" : {
            "match" : {
                "about" : "rock climbing"
            }
        }
    }
    
  • 短語查詢

    GET /megacorp/employee/_search
    {
        "query" : {
            "match_phrase" : {
                "about" : "rock climbing"
            }
        }
    }
    
  • 分析(類似於聚合group by)

    request:

    GET /megacorp/employee/_search
    {
        "aggs" : {
            "all_interests" : {
                "terms" : { "field" : "interests" },
                "aggs" : {
                    "avg_age" : {
                        "avg" : { "field" : "age" }
                    }
                }
            }
        }
    }
    

    response:

     ...
      "all_interests": {
         "buckets": [
            {
               "key": "music",
               "doc_count": 2,
               "avg_age": {
                  "value": 28.5
               }
            },
            {
               "key": "forestry",
               "doc_count": 1,
               "avg_age": {
                  "value": 35
               }
            },
            {
               "key": "sports",
               "doc_count": 1,
               "avg_age": {
                  "value": 25
               }
            }
         ]
      }
    
  • 修改文檔

    PUT /website/blog/123
    {
      "title": "My first blog entry",
      "text":  "I am starting to get the hang of this...",
      "date":  "2014/01/02"
    }
    

    舊文檔不會馬上刪掉

    新文檔會被索引

    文檔的version會加一

  • 刪除文檔

    DELETE /megacorp/employee/123
    

    文檔的version依然會加一

分布式特性

ES自動執行的分布式動作

  • 分配文檔到不同的容器 或 分片 中,文檔可以儲存在一個或多個節點中
  • 按集群節點來均衡分配這些分片,從而對索引和搜索過程進行負載均衡
  • 復制每個分片以支持數據冗余,從而防止硬件故障導致的數據丟失
  • 將集群中任一節點的請求路由到存有相關數據的節點
  • 集群擴容時無縫整合新節點,重新分配分片以便從離群節點恢復

水平擴展VS垂直擴展

ES對水平擴展是友好的,通過購置更多的機器,可以更好的使用ES的分布式功能

集群

集群擁有一個或多個節點,當有節點加入或者退出集群時,集群會重新平均分配所有數據的分布

主節點功能

  • 增加/刪除索引

  • 增加/刪除節點

    不涉及文檔的變更和搜索,因此單一的主節點不會成為集群的性能瓶頸

索引分片的元數據在每個ES節點都有存儲,每個節點在接到請求后,都知道到哪台ES node找到數據,通過轉發請求到ES node所在的機器

一個分片的最大文檔數:(2^31-128)

一個索引的主分片數在建立時被確定,且無法修改:因為文檔的存儲是用shard = hash(routing) % number_of_primary_shards來確定文檔的位置的。routing默認是id,也可以自定義

分片的副本數可以隨時修改

建立索引

PUT /blogs
{
   "settings" : {
      "number_of_shards" : 3,
      "number_of_replicas" : 1
   }
}

ES-cluster

故障轉移

增加一台機器到集群

ES-addonenode

刪除節點

ES-delonenode

分布式寫入沖突

ES-datalose
對於多個client的寫入ES,有可能造成寫入沖突,導致數據的丟失

在一些場景下,數據丟失是可以接受的

但是在某些場景下,是不允許的。

悲觀控制並發

傳統數據庫的控制方式。通過對記錄加鎖,來實現並發的串行執行

樂觀並發控制

ES采用樂觀控制

所謂樂觀控制,就是服務器假設大部分情況下,是不會發生沖突的,如果發生沖突,則拒絕修改,客戶端可以需要通過重新獲取並重試進行處理。

過程如下圖

ES-versionConflict

分布式文檔存儲

確定文檔位於那個shard
shard = hash(routing) % number_of_primary_shards

API支持帶routing參數,來自定義路由,來確保相關文檔路由到同一個分片

以ID新建,寫入和刪除文檔

ES-writedocbyid

一致性保證
  • none: 主分片活躍,允許寫入
  • all: 在所有分片活躍,允許寫入
  • quorum: 半數以上節點活躍,允許寫入

如果暫時沒有足夠的分片活躍,ES會等待,默認等待1分鍾,可以通過參數timeout改變這個值,如果超時,則失敗返回

新索引默認有 1 個副本分片,這意味着為滿足 規定數量 應該 需要兩個活動的分片副本。 但是,這些默認的設置會阻止我們在單一節點上做任何事情。為了避免這個問題,要求只有當 number_of_replicas 大於1的時候,規定數量才會執行。

以ID檢索文檔

與上圖類似

以ID更新文檔

與上圖類似,但在更新完文檔后,會重建索引

在局部更新文檔的時候,主分片會以整份文檔來同步給副本,來保證數據的完整性

通過條件獲取多個文檔

ES-mget

搜索

返回特殊字段

GET /_search
{
   "hits" : {
      "total" :       14,
      "hits" : [
        {
          "_index":   "us",
          "_type":    "tweet",
          "_id":      "7",
          "_score":   1,
          "_source": {
             "date":    "2014-09-17",
             "name":    "John Smith",
             "tweet":   "The Query DSL is really powerful and flexible",
             "user_id": 2
          }
       },
        ... 9 RESULTS REMOVED ...
      ],
      "max_score" :   1
   },
   "took" :           4,
   "_shards" : {
      "failed" :      0,
      "successful" :  10,
      "total" :       10
   },
   "timed_out" :      false
}
  • took

    執行的毫秒數

  • _shards

    查詢分片的狀態,例如有幾個分片是失敗的,幾個是成功的

  • timeout

    可以通過在查詢設定超時,如果查詢超過時間,則只返回已經成功獲得的數據,剩余的數據將丟棄

  • _index

    數據來源的Lucene索引,文檔的每一個字段,都擁有一個不同的Lucene索引

    在查詢中可以指定Lucene的索引,默認是不指定的,所以會查詢該文檔的所有索引,並匯總結果。如果指定,則會限定僅在指定的Lucene索引中查詢數據

分頁

GET /_search?size=5&from=5

此方式只適用於淺分頁,如果查詢過深,會導致嚴重的性能問題。

因為例如查詢size為5,from=10000。那么ES會從各分片中都查詢10005條記錄,如果有100個shard,那么就會有100*10005條記錄,ES再對這100*10005排序,並僅返回5條記錄

深分頁

使用游標scroll

它在ES中建立了一個有有效期的快照,提供給scroll進行數據的深度查詢

倒排索引

對一下文檔進行倒排:

  1. The quick brown fox jumped over the lazy dog
  2. Quick brown foxes leap over lazy dogs in summer

得到:

Term      Doc_1  Doc_2
-------------------------
Quick   |       |  X
The     |   X   |
brown   |   X   |  X
dog     |   X   |
dogs    |       |  X
fox     |   X   |
foxes   |       |  X
in      |       |  X
jumped  |   X   |
lazy    |   X   |  X
leap    |       |  X
over    |   X   |  X
quick   |   X   |
summer  |       |  X
the     |   X   |
------------------------

倒排面臨的挑戰

  • Quick跟quick,用戶有可能認為它們是相同的,也有可能認為是不同的
  • dog和dogs非常接近,在相關性搜索時,它們應該都被搜索到
  • jump和leap是同義詞,在相關性搜索時,它們應該都被搜索到

理解相關性

相關性的分數是一個模糊的概念。沒有精確值,沒有唯一正確的答案。是一種根據各種規則對文檔進行的一種量化的估計。

它評分的准則如下:

  • 檢索詞頻率

    檢索詞在該字段出現的頻率越高,分數越高

  • 反向文檔頻率

    檢索詞在索引出現的頻率越高,分數越低

  • 字段長度准則

    字段的長度越長,分數越低

相關性破壞

在使用全文檢索某個關鍵字的時候,會出現,相關度低的文檔的得分高於相關度高的文檔的得分。

例如檢索詞milk。索引內有兩個主分片,milk在P1出現了5次,在P2出現了6次。由於P1和P2的詞分布不一樣。

P1的詞量比P2的詞量高,那么milk算在P1出現占比小,導致在P1得相關性得分高,而在P2,占比da,導致在P2的相關性得分低。

原因

是因為局部數據分布不均勻導致的

解決方法

  • 插入更多的文檔
  • 使用?search_type=dfs_query_then_fetch進行全局評分。但會有嚴重的性能問題。不推薦使用。

查詢過濾bitset

每次使用檢索詞查詢,都會為檢索詞建立一個bitset,bitset包含了匹配的文檔的序號。在熱搜索的檢索詞,ES會對這些bitset有針對的進行緩存,而不用在再次查詢的時候,重新查找倒排索引。

對於多個查詢可以有下圖

ES-bitset

當倒排索引重建的時候,bitset在緩存會自動失效

緩存的策略

  • 最近256次被使用的bitset,會被緩存
  • 段內記錄小於1w的,不會被緩存

索引管理

創建索引

可以顯式創建,也可以隱式創建。

在大集群下,索引的創建,涉及元數據的同步,有可能導致集群負載的大量增加。此時需要禁用索引的隱式創建

action.auto_create_index: false

刪除索引

刪除索引,會涉及大量數據的刪除,如果用戶意外地試圖通過一條命令,把所有索引刪掉,這可能導致可怕的后果

通過禁用此操作,可以設置如下

action.destructive_requires_name: true

分析器

每個索引都可以設置自己的分析器,分析器的用途主要是在全文索引上面,通過對不同的語言,使用不同的分詞,不同的詞轉換來構造倒排索引和計算相關性。

分片

倒排索引的不變性

好處

  • 一旦被讀入系統緩存,就會一直留在那里,直到LRU算法把不常用的倒排索引剔除。這對ES的讀取性能提供了非常大的提升

不好

  • 新的文檔加入,不能增量更新,只能重建索引並替換

如何保證新數據能實時能查詢到

用更多的索引。

對於新的文檔,不馬上重建索引,而是通過新增額外的索引。在查詢數據時,通過輪詢所有的索引,並合並結果返回。

ES-shar

ES並不是嚴格意義上的實時,准確來說是准實時,由於data從插入到建立倒排索引這段時間,新數據是不能訪問的

聚合

像數據庫的group by。只是語法不一樣。功能相通

應用層性能調優

調大 refresh interval

默認刷新時間是1s,每次刷新都會有一次磁盤寫入,並創建一個新的段。通過設置更大的刷新時間,可以讓磁盤寫入的次數更低,寫入的段更大。減少段合並的次數。

禁止OS把ES置換出去

OS的內核會在內存緊張的時候,把進程置換到外村。而對於性能跟內存強相關的ES來說,置換到外存是致命的。通過設置進程在內核的參數,禁止置換,可以避免OS的這種動作

預留大量的文件系統緩存給ES

由於ES大部分數據的不變性,使得ES的大部分磁盤操作,都可以通過文件系統的緩存來加快速度。一旦ES的倒排索引和數據緩存到系統,如果沒有其他進程的干擾,而且是比較頻繁訪問的數據,則會一直駐留在系統緩存,使得ES的大部分操作都是走內存的。一般來說,分配一半的內存給文件系統,是合適的。

使用自動生成ID

如果指定ID,ES會在集群內檢查是否ID已經存在,這對大集群來說,是昂貴的。如果ID是自動生成的,ES會跳過檢查,直接插入文檔

更好的硬件

  • 更大的內存
  • SSD
  • 本地磁盤

不要使用join關聯查詢

ES不適合做關聯查詢,會導致嚴重的性能問題。

如果業務一定要join,可以把關聯的數據都寫到一個索引內,或者通過應用程序來做關聯的動作。

強制merge只讀索引

merge成一個單一的段,會得到更好的性能

增加副本

有更多的機器,通過提高副本數,可以提高讀效率

不要返回大數據

ES不適合這場景

避免稀疏

不要把不相關的信息存入同一個索引

數據預熱

對於熱點數據,可以通過一個客戶端請求ES,讓數據先占據filesystem cache。

冷熱數據分離

冷熱數據部署在不同的機器,可以讓熱數據在緩存內不會被冷數據沖走

內核層性能調優

限流

如果ES出現高負載的請求,ES的協調節點會累積大量的請求在內存在等待處理,隨着請求數的增加,協調節點的內存占用會越來越大,最后導致OOM。

通過限流,可以有效緩解。

大查詢

如果客戶端發來了一個復雜的查詢,使得需要返回的數據異常的大,這也會導致OOM問題。

通過修改內核,讓如果請求的內存占用超過系統可以承受的范圍,則截斷來解決

FST過大引發OOM

FST是對倒排索引在內存的索引,它通過前綴狀態機的方法,快速的定位檢索詞在倒排索引的磁盤位置,達到減少磁盤訪問次數而加快檢索速度的目的。

但由於FST是常駐內存的,如果倒排索引達到一定規模時,FST必然會引起OOM問題。而且FST是存放在JVM堆內內存的。堆內內存的上限時32G。

而10 TB的數據就需要10G到15G的內存來存放FST。

  • 通過把FST的存儲放到堆外內存
  • 通過LRU算法來管理FST,對不常用的FST置換出內存
  • 修改ES訪問FST的邏輯,使得ES可以從堆內直接訪問堆外的FST
  • 在堆內增加FST的cache,加快命中速度

Ref:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM