Elasticsearch最佳實踐之Index與Shard設計

  Index與Shard,這兩個概念在《Elasticsearch最佳實踐之核心概念與原理》一文有詳細的介紹,分別對應了Elasticsearch的兩種數據組織方式:邏輯組織和物理組織。邏輯層面上,Index與業務數據的結構、類型、使用方式等息息相關;而物理層面上,Shard關係到數據在不同機器上的分佈情況。作爲專欄的第三篇,本文主要探討實際應用中Index與Shard的設計方法。所謂設計,即通過合理組合Elasticsearch的特性與功能來完成業務需求,儘可能實現業務的靈活性,保證系統的高性能與穩定性。當然,前提是得先知曉這些特性與功能以及何時使用。本文將從以下幾個方面進行介紹,寫作背景是Elasticsearch 5.5。(文中使用的一些示例和圖片來自於筆者在2018年Elasticsearch南京Meetup中的幻燈片。)

  • 基於時間的Index設計
  • Mapping設計技巧
  • 巧妙的Alias
  • Shard分配原則
  • 整體思路

1. 基於時間的Index設計

  Index設計時要考慮的第一件事,就是基於時間對Index進行分割,即每隔一段時間產生一個新的Index。爲什麼要這樣做呢?因爲現實世界的數據是隨着時間的變化而不斷產生的,切分管理可以獲得足夠的靈活性和更好的性能。

  • 如果數據都存儲在一個Index中,很難進行擴展和調整,因爲Elasticsearch中Index的某些設置在創建時就設定好了,是不能更改的,比如Primary Shard的個數。而根據時間來切分Index,則可以實現一定的靈活性,既可以在數據量過大時及時調整Shard個數,也可以及時響應新的業務需求。
  • 大多數業務場景下,客戶對數據的請求都會命中在最近一段時間上,通過切分Index,可以儘可能的避免掃描不必要的數據,提高性能。

  當然,並不排除某些特定的業務場景下,不用對Index進行切分管理,比如一個固定的數據集或者一個增長非常緩慢的數據集。大多數情況下,筆者都建議按照時間進行分割。那麼,要考慮的第二件事就是,按照什麼規則來設定切分的間隔呢?根據上面的分析,自然是時間越短越能保持靈活性,但是這樣做就會導致產生大量的Index,而每個Index都會消耗資源來維護其元信息的,因此需要在靈活性、資源和性能上做權衡。建議按照如下幾點來思考:

  • 常見的間隔有小時、天、周和月。先考慮總共要存儲多久的數據,然後選一個既不會產生大量Index又能夠滿足一定靈活性的間隔。比如,你需要存儲6個月的數據,那麼一開始選擇“周”這個間隔就會比較合適。
  • 考慮業務增長速度。假如業務增長的特別快,比如上週產生了1億數據,這周就增長到了10億,那麼就需要調低這個間隔來保證有足夠的彈性能應對變化。
  • 結合業務特性和性能測試來決定要不要調整間隔。這點更多的是從業務角度來考慮的,舉個例子,在筆者的一個項目中,一開始選擇了“周”作爲間隔,一週產生一個新的Index來存儲實時的分鐘級數據,但是每週會將歷史數據合併成小時級數據來降低數據量、提高查詢速度。在性能測試中發現,合併後的查詢性能相比合並前提升特別大,因此我們將整體間隔調整到“天”來縮短合併的週期。(後面會另撰文章更詳細的分享這個案例)

  接下來就是第三件事如何實現Index的分割?切分行爲是由客戶端(數據的寫入端)發起的,根據時間間隔與數據產生時間將數據寫入不同的Index中,爲了易於區分,會在Index的名字中加上對應的時間標識。創建新Index這件事,可以是客戶端主動發起一個創建的請求,帶上具體的Settings、Mappings等信息,但是可能會有一個時間錯位,即有新數據寫入時新的Index還沒有建好。Elasticsearch提供了更優雅的方式來實現這個動作,即Index Template。
  Index Template提供的功能是:先設置一個Template,定義好具體的Settings、Mappings等信息,當有數據需要寫入新的Index時,就會根據Template內容自動創建Index。是否根據Template建立新的Index受三點因素制約:

  • 是否是新的Index;
  • Index的名字是否與template參數中定義的格式相匹配;
  • 如果有多個Template匹配上了,則根據order參數的大小來依次生效,即從小到大逐步更新相應的配置信息(相同的配置信息會被覆蓋)。

  下面給出了一個具體的Index Template定義,來自於筆者項目中的真實定義(去掉了部分字段信息),該Template會匹配所有前綴爲“ce-index-access-v1-”的Index,這個示例會被上下多個小節引用。

{
    "facet_internet_access_minute": {
        "template": "ce-index-access-v1-*",
        "order": 0,
        "settings": {
            "number_of_shards": 5
        },
        "aliases": {
            "{index}-query": {}
        },
        "mappings": {
            "es_doc": {
                "dynamic": "strict",
                "_all": {
                    "enabled": false
                },
                "_source": {
                    "enabled": false
                },
                "properties": {
                    "CLF_Timestamp": {
                        "type": "long"
                    },
                    "CLF_CustomerID": {
                        "type": "keyword"
                    },
                    "CLF_ClientIP": {
                        "type": "ip",
                        "ignore_malformed": true
                    }
                }
            }
        }
    }
}

2. Mapping設計技巧

  Index設計的第四件事,是結合業務需求來設定Mapping信息。如在專欄的第二篇中所述,Mapping定義了具體的數據結構與相關的元信息,合適的設置可以有效提高性能、節省磁盤空間。Mapping的設計,主要考慮兩方面內容:

  • Schema設計。第一,儘管Elasticsearch支持半結構化數據,但是在實際使用中還是應該盡最大可能對數據結構加以控制。第二,因爲Elasticsearch不支持JOIN操作,所以Schema應該儘量扁平化。第三,對於只需要做精確匹配的字段,應該設置爲不做分詞,5.5中通過type=keyword來設定。
  • 參數調整,即修改部分參數的默認值來適應自身業務。Elasticsearch中有很多參數可用,幾乎可以滿足大多數的業務需求,你很難記住所有的,不妨在有相關需求時先去文檔查查看。

  這裏將結合上述Index Template示例,闡述幾個筆者用過的Mapping參數。

_all參數

  假設在你的業務中,需要根據關鍵詞來查找某條數據,但是並不明確知道要到哪個字段中去查,這時用_all參數就可以幫助你解決。_all=true時,Elasticsearch會在寫入數據時將其所有字段值合併起來組合爲一個新的值,對其建立索引,用來滿足前面所述的搜索需求。也就是說,如果你的業務沒有這種需求,那麼將其設置爲false,可以節省磁盤、提高性能。

_source參數

  我們知道Elasticsearch中存儲的每一條數據都是一個JSON結構,在寫入數據時會對每個字段建立Inverted Index、doc values等,如果_source=true,Elasticsearch會將整個JSON數據也存儲下來。如果你的業務中,不需要查詢原始數據,只需要根據索引來過濾然後做聚合查詢,那麼可以將其設置爲false,同樣可以節省磁盤空間、提高性能。

dynamic參數

  假如突然有一條數據裏面包含了一個沒有定義的字段,這時你期望Elasticsearch怎麼做?dynanic參數便是用來處理此種情況的,有三種選擇:strict表示不接受數據中包含沒有定義的字段,發現了就報錯;true表示允許沒有定義的字段被插入進來;false表示忽略沒有定義過的字段,繼續寫入數據的其他信息。具體怎麼選,更多的取決於業務需求,但是要考慮清楚可能帶來的後果。在筆者的項目中,就選擇了strict,用來拒絕一切髒數據。

ignore_malformed參數

  假設有一個字段CLF_ClientIP傳過來的是字符串,我們期望將其轉換爲ip數據類型,方便做查詢。理論上,將type設置爲ip就可以解決了,但是有這麼一種情況,客戶端在某些情況下沒有拿到對應的IP信息,用了一個“–”來表示,這樣的數據到了Elasticsearch端是沒法轉換爲ip數據類型的,但是這又是一條正常數據,該怎麼處理呢?通過ignore_malformed=true,可以做到忽略CLF_ClientIP字段而保留數據的其他信息。

3. 巧妙的Alias

  Alias,顧名思義,就是Index的別名。一個Alias可以指向多個Index,一個Index也可以關聯多個Alias,具體可以通過Alias的REST API來設定。在查詢數據時,Elasticsearch會自動檢測請求的Path是否是Alias,是的話就會從其關聯的Index中查詢數據。Alias的巧妙之處在於,能幫助我們將查詢請求無縫的從一個Index切換到另一個Index上面,這種切換對於客戶端是透明的。
  舉個例子來闡述,上面有提到,在筆者業務中,存在一種數據合併:將分鐘粒度數據合併成小時粒度的數據,從而可以降低數據量、提高查詢性能。下圖中,左邊表示分鐘粒度數據的Index,右邊表示小時粒度數據的Index。用戶的查詢會先命中到分鐘粒度Index上,一天之後,當歷史數據完成合並後,會將查詢請求轉到小時粒度Index上(分鐘粒度的Index會被刪除)。通過Alias,我們就可以實現這種沒有downtime的切換,並且對客戶端透明。客戶端只要按照固定的Alias來查詢即可,至於Elasticsearch這邊從哪個Index來查,則是取決於不同時間這個Alias關聯的Index。(在數據合併完成後,切換Alias關聯的Index,在一個請求中完成卸載老的Index和關聯新的Index。)

4. Shard分配原則

  所謂Shard分配原則,就是如何設定Primary Shard的個數。看上去只是一個數字而已,也許在很多場景下,即使不設定也不會有問題(默認是5個Primary Shard)。但是如果不提前考慮,一旦出問題就可能導致系統性能下降、不可訪問、甚至無法恢復。換句話說,即使使用默認值,也應該是通過足夠的評估後作出的決定,而非拍腦袋定的。具體的分配方法,很難給出一個完美的套路適用於所有的業務需求,這裏主要結合筆者的實踐談幾點原則和調整方法,給大家提供一個思路。

  • 單個Shard的存儲大小不超過30GB。首先,爲什麼是30GB?關於這個值,來自於筆者跟AWS和Elastic專家的溝通結果,大家普遍認爲30GB是個合適的上限值。目前我們尚未對此做過測試驗證,不過實踐中確實遇到過因爲單個Shard過大(超過30GB)導致系統不穩定的情況。其次,爲什麼不能超過30GB?主要是考慮Shard Relocate過程的負載。我們知道,如果Shard不均衡或者部分節點故障,Elasticsearch會做Shard Relocate,在這個過程中會搬移Shard,如果單個Shard過大,會導致CPU、IO負載過高進而影響系統性能與穩定性。
  • 單個Index的Primary Shard個數 = k * 數據節點個數。在保證第一點的前提下,單個Index的Primary Shard個數不宜過多,否則相關的元信息與緩存會消耗過多的系統資源。這裏的k,爲一個較小的整數值,建議取值爲1,2等,整數倍的關係可以讓Shard更好地均勻分佈,可以充分的將請求分散到不同節點上。
  • 對於很小的Index,可以只分配1~2個Primary Shard的。有些情況下,Index很小,也許只有幾十、幾百MB左右,那麼就不用按照第二點來分配了,只分配1~2個Primary Shard是可以,不用糾結。
  • 聊一個特殊的例子。曾經遇到過一個Index,裏面存放的是關鍵事件的日誌信息,數據量不大(400MB左右),所以只分配了2個Primary Shard。但是因爲“最近的事件”是用戶非常關注的,所以這個Index承載了整個集羣50%以上的查詢請求,爲了提高查詢性能,將請求分散到不同機器上,最後還是將Primary Shard設置爲數據節點的個數。

5. 整體思路

  Index與Shard的設計,並不是一下子就可以完成的,需要不斷預估、測試、調整、再測試來達到最終模型。整個過程大致如下圖時間軸所示,分爲以下幾個階段,當然期間有很多階段是需要迭代的。

  • 第一階段,Index的設計,即完成本文第1~3小節提到的內容。這一階段是完成業務需求的分解與設計,之後剩下的就是如何構建Elasticsearch集羣來承載這樣的需求了。
  • 第二階段,預估數據量。預估單個Index的數據量,結合需要保留的數據週期可以得到整體的數據量。
  • 第三階段,預估機型。根據業務的場景,來決定選擇CPU增強型、內存增強型還是通用型機器(針對雲服務)。
  • 第四階段,預估機器個數。因爲單機能掛載的磁盤大小是有限的,因此知道了數據量,也就知道了最少需要多少臺機器。在預估磁盤空間時,還需要考慮另外兩個因素:一個是Replica,即副本的個數,大多數情況下1個就夠了(當然還是取決於業務);另一個是預留30%的磁盤空間,這部分空間既是預留給系統使用的,也是爲磁盤告警的處理預留足夠的時間。
  • 第五階段,預估Primary Shard個數。即參考第4小節中的思路,預估Primary Shard的個數。
  • 第六階段,部署、測試。根據性能測試和業務的特殊情況,做適當調整,調整可能包括Primary Shard個數、機型、機器個數等。

(全文完,本文地址:https://blog.csdn.net/zwgdft/article/details/86416668
(版權聲明:本人拒絕不規範轉載,所有轉載需徵得本人同意,並且不得更改文字與圖片內容。大家相互尊重,謝謝!)

Bruce
2019/01/22 下午

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章