給你總結幾個ES下最容易踩的坑

犀牛飼養員發表於2020-05-31

我本人接觸Elasticsearch(一下簡稱ES)有挺長一段時間了,本文結合自己的一些專案經驗,給你總結幾個實際專案中比較容易踩到的坑。希望讀者能夠避免犯這樣的錯誤。

坑一,時區問題

在我們的專案中,索引下一般都會存在一個時間的欄位,這個欄位可以用來排序,或者做時間範圍查詢,或者聚合的場景等都會用到。

ES底層預設採用UTC時間格式,而中國的時間(CST)是

CST=UTC+8

所以實際的專案中經常遇到查詢結果和自己期望的不一致。關於時區的問題以及如何解決,我之前專門寫了一篇文章,感興趣的可以看看:

ES系列之一文帶你避開日期型別存在的坑

坑二,使用預設的mappings

ES本身支援我們在寫入一個索引的時候,可以不為該索引設定任何的mappings。這種情況下,ES會為索引根據寫入的欄位值,"推斷"該欄位的型別。

看起來似乎不錯,但是根據我的經驗,還是建議應該明確的為自己的索引定義mappings。

因為有時候ES "推斷"出來的結果並不一定是我們想要的,然後給我們帶來一些頭疼的問題。

我還是給你舉個例子:

寫入一個名為message的索引,

PUT /message/_doc/1
{
  "head":   "message001",
  "body": "2020-05-30"
}

然後繼續再插入一條,

PUT /message/_doc/2
{
  "head":   "message002",
  "body": "this is an emergency"
}

這裡就報錯了,錯誤的內容是提示我們,body這個欄位無法被解析。產生這個問題的原因是當我們寫入第一條文件的時候,ES "擅自做主"把body這個欄位標記成日期型別了,然後寫入文件2的時候不是日期字串,所以無法解析。

可以用下面的命令看下索引的mapping,證實我們的猜想:

GET message/_mapping

在這個示例中,我們如果明確的定義body為text型別就不會有這樣的問題了。

坑三,沒有規劃好分片

我們一般都需要為索引設定分片的數量,具體設定成多少需要根據你的專案實際情況來定。

一般來說,單個 Shard 的建議最大大小是 20G 左右,最大不要超過 50G。

單個shard過大,或者shard過小導致shard數量太多,都會影響查詢的效率。

舉個例子,比如你預估索引的大小是100G,這個時候分片是3~5比較好。

如果你的索引是每天增量比較大的場景,比如日誌類,訂單類的索引,可能你首先要把根據日期來新建不同的索引,根據時間的資料規模選擇按天,周,甚至月來建索引。然後這些索引使用相同的分片設定。

坑四,過多依賴ES聚合的結果

ES某些場景下的聚合結果是不準確的,計算的結果只是告訴你一個大概的分佈情況,並不是精確的。

如果你不瞭解這個情況,可能會在實際的專案中犯錯誤。

我曾經寫過一篇文章,對這個坑有過詳細的分析以及閉坑指南,有興趣可以看看這篇文章:

ES系列之原來ES的聚合統計不準確啊

坑五,分桶聚合查詢的記憶體爆炸

在分桶聚合的場景下,大多數時候對單個欄位的聚合查詢非常快的,如果是多個欄位巢狀聚合。有可能撐爆記憶體,引發OOM。看下面一個例子。

假設我們有個很多電影資料的索引,有個欄位是陣列,儲存演員的名字。

{
  "actors" : [
    "Fred Jones",
    "Mary Jane",
    "Elizabeth Worthing"
  ]
}

然後,我們希望查詢出演影片最多的10個演員,以及他們合作最多的5位演員,可以使用下面這個聚合,

{
  "aggs" : {
    "actors" : {
      "terms" : {
         "field" : "actors",
         "size" :  10
      },
      "aggs" : {
        "costars" : {
          "terms" : {
            "field" : "actors",
            "size" :  5
          }
        }
      }
    }
  }
}

結果返回前10位演員,以及與他們合作最多的5位演員。但是就是這樣一個簡單的查詢,可能導致OOM。

我們可以想象下在記憶體中構建一個樹來表示這個 巢狀terms 分桶聚合。 首先actors 聚合會構建樹的第一層,每個演員都有一個桶。

然後,第一層的每個節點之下, costar 聚合會構建第二層,每個聯合演員一個桶,如果你學過排列組合,應該知道這實際上要構建n的平方個分桶。(n是每個影片中演員的數量)

假如平均每部影片(文件)有 10 名演員,每部影片就會生成

10^2

100個桶。如果總共有 20000 部影片,粗率計算就會生成 2000000 個桶。這個引起記憶體爆炸就不奇怪了。

上面產生問題的根源在於ES對於這種巢狀聚合預設使用了深度優先規則,即先構建完整的樹,再篩選符合條件的結果。

ES允許我們使用一種廣度優先的模式來進行這種場景的聚合,這種策略的工作方式有些不同,它先執行第一層聚合, 再繼續下一層聚合之前會先做修剪。

要使用廣度優先,只需簡單 的通過引數 collect 開啟,

{
  "aggs" : {
    "actors" : {
      "terms" : {
         "field" :        "actors",
         "size" :         10,
         "collect_mode" : "breadth_first" 
      },
      "aggs" : {
        "costars" : {
          "terms" : {
            "field" : "actors",
            "size" :  5
          }
        }
      }
    }
  }
}

廣度優先僅僅適用於每個組的聚合數量遠遠小於當前總組數的情況下,因為廣度優先會在記憶體中快取裁剪後的僅僅需要快取的每個組的所有資料,以便於它的子聚合分組查詢可以複用上級聚合的資料。

坑六,mapping包含的欄位過多

我們給索引建模時,要儘量避免mapping中的包含的欄位過多。

過多的欄位一個是難以維護,當存在成千上百個欄位時,很難有人真正明確每個欄位的含義。另外一個導致的問題是,當我們需要更新文件的時候,ES會在不同的節點同步這些更新。過多的欄位意味著更新變慢。

如果我們的業務場景確實需要很多欄位,應該充分利用ES的dynamic templates機制,提前定義好欄位對映的規則,這樣一些欄位就沒有必要在mapping裡定義好。

不過無論如何,都應該儘量保持你的mapping欄位足夠小。

總結

Elasticsearch 是一個分散式可擴充套件的實時搜尋和分析引擎。這樣的神器如果用好了讓你的工作事半功倍,但是如果沒用好可能又會給你帶來不少的困擾。

先寫這麼多吧,後續如果工作中踩到新的坑再跟大家分享。


參考:

https://www.elastic.co/guide/...

相關文章