Stories

Detail Return Return

如何合理規劃Elasticsearch的索引|得物技術 - Stories Detail

一、背景

隨着ES在業務場景中的使用逐漸增多,平台對ES集羣的穩定性、管理、運維的壓力逐漸增大,通過日常的運維情況來看,發現用户對ES的瞭解熟悉程度參差不齊,經常性的遇到索引創建不規範,或者參考別人索引的創建腳本進行創建索引,對索引沒有一個比較清晰的認知,對索引結構的規劃也寥寥無幾,為此,平台使用了一些列手段來幫助用户提前合理規劃模板,比如索引、模板的創建接入飛書審批流,平台側會逐一結合業務場景和ES集羣情況詳細溝通確定索引或者模板結構;又比如ES內核增加業務不停服的動態擴分片能力,旨在進行不合理索引的治理提升ES集羣穩定性(索引一旦創建分片是不能修改的),我們內部改動ES源碼實現了不停服動態擴分片。

因此有必要從ES的索引講起,讓大家對ES的索引從概念、原理到使用有一個清晰的認知,希望日常業務場景中用到ES的同學能夠抽時間讀一下。當然文章避免不了存在主觀的分析,大家可以在文章底部進行評論或者私聊我們,一起探討。好了廢話不多説了,現在開始介紹。

二、什麼是index(索引)

下面會針對索引的組成和基本結構結合官方文檔逐一介紹。

基本概念

index(索引)是索引是具有相似特徵的文檔(Document)集合,類似於關係型數據庫中的表。每個索引都具有自己唯一的名稱與\_id。並且可以進行不同的參數配置與mapping映射。以適應不同的業務場景。索引中的最小單位是文檔。每一條文檔(doc)都是一個json格式的數據對象。包含了實際的具體數據以及該數據所對應的元數據。文檔可以是結構化,半結構化或非結構化的數據。索引在elasticsearch中被用於存儲,檢索與分析數據。通過對索引進行搜索與聚合操作可以快速地找到相關的文檔。

官方描述:The index is the fundamental unit of storage in Elasticsearch, a logical namespace for storing data that share similar characteristics. After you have Elasticsearch deployed, you’ll get started by creating an index to store your data.

翻譯:索引是Elasticsearch中存儲數據的基本單位,是一個邏輯命名空間,用於存儲具有相似特性的數據。在部署Elasticsearch後,您將通過創建索引來存儲數據。

An index is a collection of documents uniquely identified by a name or an alias. This unique name is important because it’s used to target the index in search queries and other operations.

翻譯:索引是一種文檔集合,通過名稱或別名唯一標識。這個唯一名稱非常重要,因為它用於在搜索查詢和其他操作中定位索引。

三、索引結構詳解

索引結構詳解

圖片

創建索引結構
PUT /index_demo
{
  "aliases" : {
    "index_demo_alias" : { }
  },
  "mappings" : {
    "properties" : {
      "id" : {
        "type" : "long"
      },
      "name" : {
        "type" : "text",
        "fields" : {
          "keyword" : {
            "type" : "keyword",
            "ignore_above" : 256
          }
        }
      },
      "status" : {
        "type" : "keyword"
      },
      "createDate" : {
        "type" : "long"
      }
    }
  },
  "settings" : {
    "index" : {
      "refresh_interval" : "5s",
      "number_of_shards" : "3",
      "number_of_replicas" : "1"
    }
  }
}

ignore\_above屬性説明:

- ignore\_above的默認值通常為256個字符,這意味着任何超過256個字符的字符串將不會被索引或存儲。

- 該參數僅適用於keyword類型的字段,因為這些字段主要用於過濾、排序和聚合操作,不需要進行全文搜索。

- ignore\_above的值以字符為單位計算,包括英文字符和漢字。例如,一個漢字和一個英文字符都算作一個字符。

- 性能優化:通過限制字段長度,可以減少索引大小和查詢時間,從而提高性能。

- 避免資源浪費:對於包含大量數據的字段,如日誌文件中的長字符串,可以通過ignore\_above避免不必要的存儲和索引。

官方描述:Strings longer than the ignore\_above setting will not be indexed or stored. For arrays of strings, ignore\_above will be applied for each array element separately and string elements longer than ignore\_above will not be indexed or stored.

3.1 別名

別名將其生命置於羣集狀態內,由主節點(master node) 管理; 這意味着如果你有一個名為 xiaoming 的別名指向一個名為 potato 的索引,那麼開銷就是羣集狀態映射中的一個額外鍵,它將名稱 xiaoming 映射到具體的索引字符串。這意味着與其他指數相比,別名的重量要輕得多; 可以維護數千個而不會對集羣產生負面影響。

官方原話:An alias points to one or more indices or data streams. Most Elasticsearch APIs accept an alias in place of a data stream or index name.

Aliases enable you to:

- Query multiple indices/data streams together with a single name

- Change which indices/data streams your application uses in real time

- Reindex data without downtime

翻譯:別名(Alias)可以指向一個或多個索引或數據流。大多數Elasticsearch API接受別名代替數據流或索引名稱。別名的功能包括:

- 使用單一名稱查詢多個索引/數據流;

- 實時更改應用程序使用的索引/數據流;

- 在不中斷服務的情況下進行擴分片。

可以看到索引有上面三個作用,平台建議為每個索引添加別名(動態擴分片依賴別名)。添加別名可以在索引創建時和創建後再添加,即索引可以隨時添加,但是平台還是建議你在創建索引時候指定別名,避免動態擴分片時候再去修改代碼重新部署應用。

添加別名的幾種方式

1. 創建索引時指定別名

PUT /test_index
{
    "settings" : {
        "number_of_shards" : 1,
        "number_of_replicas" : 1
    },
    "aliases":{"test_alias":{}},
    "mappings" : {
        "properties" : {
            "field1" : { 
                "type" : "text" 
            },
            "createdAt": {
                "type": "date",
                "format": "yyyy-MM-dd HH:mm:ss"
           }
        }
    }
}

2. 已存在的索引添加別名

POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "test_index", # 索引名
        "alias": "test_alias" # 別名
      }
    }
  ]
}

3. 別名更換

別名更換可以零停機進行動態擴分片。
POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "existing_index",
        "alias": "test_alias" # 別名
      },
      {
        "remove": {
          "index": "old_index",
          "alias": "old_test_alias" # 別名
        }
      }
    }
  ]
}

3.2 映射

建立索引時需要定義文檔的數據結構,這種結構叫作映射。在映射中,文檔的字段類型一旦設定後就不能更改。因為字段類型在定義後,elasticsearch已經針對定義的類型建立了特定的索引結構,這種結構不能更改。藉助映射可以給文檔新增字段。另外,elasticsearch還提供了自動映射功能,即在添加數據時,如果該字段沒有定義類型,elasticsearch會根據用户提供的該字段的真實數據來猜測可能的類型,從而自動進行字段類型的定義。

## 3.3 字段類型

字段類型(Field Type)是定義數據格式和索引方式的重要概念,它決定了字段在索引中的存儲、搜索和聚合行為。下面針對日常用到最多的三個字段類型進行解釋,text、keyword、Numeric(Integer、Long)。

Text

text字段類型是Elasticsearch中用於全文搜索的核心字段類型。它通過分析器將文本拆分為單個詞,並存儲為倒排索引,適用於非結構化文本的搜索和分析。然而,由於其經過分析器處理,不適用於排序和聚合操作。

1. 特點

  • 全文搜索: text字段類型主要用於存儲和索引可讀的文本內容,例如郵件正文、產品描述、新聞文章等。這些字段會被分析器(analyzer)處理,將字符串拆分為單個詞(term),以便進行全文搜索。
  • 分詞處理: text字段支持分詞器(tokenizer),可以根據語言和需求選擇不同的分詞策略(如標準分詞器、正則表達式分詞器等)。分詞後的結果會存儲為倒排索引,便於快速檢索。
  • 不適用於排序和聚合: 由於text字段經過分析器處理,其原始字符串無法直接用於排序或聚合操作。如果需要排序或聚合,通常需要結合keyword字段類型。
  • 支持多字段映射: 可以通過多字段(multi-field)映射同時使用text和keyword類型,以滿足全文搜索和精確匹配的需求。

2. 使用場景

  • 全文搜索: 適用於需要對文本內容進行模糊搜索的場景,例如搜索引擎、新聞網站、商品搜索等。
  • 文本分析: 可以結合分析器(如TF-IDF、BM25等)進行文本相似性搜索或評分計算。
  • 日誌分析: 用於分析和搜索日誌文件中的文本內容,提取關鍵信息。
  • 內容管理: 在內容管理系統中,用於存儲和搜索文檔、文章等內容。

3. 官方建議

Use a field as both text and keyword

Sometimes it is useful to have both a full text (text) and a keyword (keyword) version of the same field: one for full text search and the other for aggregations and sorting. This can be achieved with multi-fields.

通過多字段映射同時使用text和keyword類型,可以實現全文搜索和精確匹配的雙重需求。

4. 平台建議

  • 明確業務使用場景,如果不需要進行模糊搜索的話,設置為keyword類型,來避免分詞帶來的存儲開銷,增加系統壓力。

Keyword

keyword字段類型是一種用於存儲和索引結構化數據的字段類型。

1. 特點

  • 不進行分詞: keyword字段類型不會對字段值進行分詞處理,而是將其作為整體存儲。這意味着字段值會被原樣存儲到倒排索引中,不會被拆分成單獨的單詞或短語。
  • 精確匹配: 由於字段值不進行分詞,keyword字段類型非常適合用於精確匹配查詢,例如查找特定的電子郵件地址、身份證號或狀態碼等。
  • tips: 在term查詢中可以結合case\_insensitive屬性,忽略大小寫對值進行搜索,但不支持terms查詢。
  • 支持排序和聚合: keyword字段類型可以用於排序和聚合操作,例如按狀態碼統計數量或按用户ID進行分組。
  • 存儲效率高: 由於不需要分詞,keyword字段類型的存儲開銷較低,適合存儲大量具有唯一性或固定值的字段。

2. 使用場景

  • 精確查詢: 適用於需要精確匹配的場景,例如查找特定的電子郵件地址、身份證號、狀態碼等。
  • 排序和聚合: 當需要對數據進行排序或聚合時,keyword字段類型是理想選擇。例如,按用户ID排序或按狀態統計數量。
  • 標籤和分類: 用於存儲標籤、分類等結構化數據,例如用户畫像標籤(學生、IT、教師等)。
  • 唯一性字符串: 適用於存儲具有唯一性的字符串,如SpuId、貨號、得物訂單號等。

Numeric

數值類型,包含long、interger、short、byte、double、float等數字類型。

1. 特點

  • 整數類型: 適用於範圍查詢、排序和聚合操作。由於整數類型佔用空間較小,推薦優先使用範圍較小的類型(如 integer 或 long)以提高索引和搜索效率。
  • 浮點類型: 適用於需要高精度的計算場景。如果數據範圍較大或精度要求不高,可以使用 scaled\_float 類型並設置合適的 scale 值。
  • 選擇合適的類型: 在滿足需求的前提下,儘量選擇範圍較小的類型以節約存儲空間和提升性能。

tips

如果確定業務使用場景,建議keyword代替數值類型字段,如果不確定則採用多字段,keyword在term查詢中性能更佳。

圖片

3.4 針對字段類型選擇的幾條建議

  1. 針對Text和數值類型場景的字段,儘量改成keyword字段類型,來提升查詢速度。
  2. 在不確定業務查詢有哪些需求的情況下,設置多字段類型keyword。
  3. 枚舉字段沒有特殊業務場景下,統一使用keyword字段類型。
  4. 業務不需要範圍查詢的話,使用keyword字段類型(支持聚合和排序的)。
  5. 對keyword字段類型進行模糊查詢會性能較差,使用多字段類型wildcard來模糊查詢性能更高。
  6. 儘量不要使用聚合查詢,text的fielddata會加大對內存的佔用,如有需求使用,建議使用keyword。
  7. 需要中文分詞的話,不要使用默認分詞器,推薦使用ik\_smart,ik\_max\_word會生成更多的分詞,其中含有重複的內容,需謹慎使用。
  8. 時間字段不要使用keyword,除非點查,推薦使用date/long類型,支持範圍查詢,建議精確到分鐘,會提高查詢效率。
  9. keyword字段類型不適用於模糊wildcard查詢,建議使用wildcard字段類型。
  

![圖片](https://oscimg.oschina.net/oscnet/up-5c0c872ccde433d906584724b29b1c11cc0.png)

  
  1. 日期的查詢條件為now時,並不能有效利用緩存,儘量換成絕對時間值。

  2. ES默認字段個數最大1000,但建議不要超過100,對於不需要建立索引的字段,不寫入ES。

  3. 將不需要建立索引的字段index數據設置為false,對字段不分詞,不索引可以減少很多運算操作。

  4. 不建議或者禁止每次寫入後立馬進行顯示的refresh,refresh會帶來較高的磁盤IO,和CPU消耗,甚至有可能導致ES宕機。

  5. 持續補充......

3.5 索引結構與關係性數據庫對比

圖片

四、索引(Shard)結構-分片與副本

4.1 什麼是Shard

基本概念

分片是管理文檔的一個數據單元,分片是Elasticsearch中邏輯概念。ES內部把索引中文檔進行按照一定路由規則(文檔\_id的hash值與分片數取餘)進行路由到不同的存儲數據單元,存儲數據單元就是分片。你可以理解為MySQL的分表。

ElS的邏輯分片就是一個Lucene索引,一個ES索引是分哦的集合,當ES在索引中搜索的時候,他發送查詢到每一個屬於索引的分片(Lucene索引)進行檢索,最後合併每個分片的結果得到一個全局的結果集。

分片劃分

分片分為primary shard(主分片)replicate shard(副本分片)

  • 主分片: 索引的基本數據存儲單元,每個索引被水平拆分為多個主分片,每個分片都是互相獨立的。包含一部分索引的數據與索引的結構(segement)。每個分片都可以在集羣中不同的節點上進行移動與複製。以提高數據的可用性與容錯性。
  • 副本分片: 主分片的完整拷貝,用於冗餘存儲和容災,副本分片和主分片在ES節點數足夠的情況下不會同時存在一個ES節點。

注意:單分片的記錄條數不要超過上限2,147,483,519。

  • 主副分片分佈示意圖

圖片

分片的功能

1. 主分片

  • 數據存儲與寫入: 所有文檔通過路由算法(如 hash(\_id) % num\_primary\_shards(主分片數))分配到主分片,主分片負責處理索引、更新、刪除等寫操作。
  • 擴展性: 通過增加節點和分片分佈,實現數據的水平擴展。
  • 不可變性: 主分片數量在索引創建時通過 number\_of\_shards 參數設定,創建後無法修改(需重建索引)。

2. 副本分片

  • 高可用性: 當主分片所在節點宕機時,副本分片自動升級為主分片(和對應的主分片不在一個節點),避免數據丟失和服務中斷。
  • 讀取負載均衡: 副本分片可並行處理查詢請求,提升讀吞吐量。
  • 動態調整: 副本分片數量通過 number\_of\_replicas 參數動態配置,支持按需擴展或縮減。

4.2 分片數規劃

分片的基本概念和功能咱們咱們已經瞭解,在日常ES運維過程中發現不少同學對分片和數量的設置沒有什麼概念,照搬其他同學的比較多,這是嚴重錯誤的。咱們在實際的業務場景中也要做好分片(主副)數量的規劃,來避免慢查、數據傾斜、磁盤容量浪費等問題。

當索引分片數量過多時,可能會對ES性能產生不利影響。因為每個分片都需要一定量的內存來存儲索引數據和緩存,從而導致內存消耗增加。另外當查詢或寫入數據涉及多個分片時,ES需要在節點之間進行傳輸和協調數據,從而增加網絡開銷,這也會導致查詢和寫入性能的降低。可見分片數量的選擇需要慎重考慮。

索引在不同場景中,其分片分設置是不一樣的,接下來咱們會在下面四個場景中來進行闡述。

讀場景

索引單分片20g~40g,儘量減少分片數,可以降低熱點,因為當分片數過多時,就容易出現長尾子請求,即有可能部分子請求因ES集羣節點異常、Old GC、網絡抖動等延遲響應,導致整個請求響應緩慢。另一方面,拆分過多的子請求無法提升數據節點請求吞吐,不能充分利用 CPU。在儘量減少主分片數的情況下,同時也可以適當增加副本數,從而提升查詢吞吐。

寫場景

索引單分片10g~20g,小分片更有利於數據寫入。小分片維護的segment數量遠低於大分片,在數據刷新落盤與段合併上更有優勢。由於單分片數據量更少,在寫入時數據可以更快地緩存至內存中並通過refresh參數更快的持久化至磁盤中。

日誌存儲場景

  • 需要考慮每日寫入集羣的數據總量大小。通過過數據量與數據節點數評估索引分片數量。
  • 在日誌存儲後是否需要兼顧查詢與聚合性能。合理大小的分片數據量能夠提高查詢效率。
  • 根據日誌持久化策略,採用按月/周/天的策略生成索引。並使用ILM(索引生命週期管理策略)動態對日誌索引進行完整生命週期的管理。
  • 建議副本數設置為0來減少磁盤容量成本。

小數據量索引業務場景

對於數據量比較小的索引,增加索引分片數並不一定會帶來性能提升,反而可能會帶來一些負面影響。

首先,增加索引分片數會增加集羣的管理開銷,包括維護分片的狀態、備份和恢復分片等。如果索引數據量比較小,這種開銷可能會超過性能提升帶來的收益。

其次,增加索引分片數可能會導致數據分佈不均衡,從而影響查詢性能。具體來説,如果某些分片中的數據量過小,可能會導致這些分片的查詢性能比其他分片差。此外,如果查詢涉及到多個分片,數據的合併操作也會增加查詢時間。

因此,對於數據量比較小的索引,在查詢場景下,通常建議將分片數設置為1或2,以避免不必要的開銷和性能問題。如果需要提高查詢性能,可以考慮配置索引副本,優化查詢語句或使用緩存。

通用場景

  • 根據實際業務場景提前規劃預算索引數據量,做好分片數量規劃(索引一旦創建無法修改分片數)。
  • 分片數量:推薦公式:主分片數 ≈ 總數據量 / 單分片容量上限(官方建議單分片10-50GB,單個分片文檔數在1億條以內,日誌場景可放寬至50-100GB)。

    注意:分片數量平台強烈建議或者要求設置為ES data節點角色的整數倍。
  • 副本數量:增加副本數可提升讀性能,但會降低寫入速度(需同步更多副本),因此在讀場景可以酌情考慮。
  • 如果索引是時序類,或者數據過大,單分片幾百G,可以結合生命週期和索引模板進行索引滾動管理。
  • 平台不建議使用自動移routing值進行分片,默認使用文檔\_id就好。

    原因:使用自定義routing值進行路由分片的話很容易產生數據傾斜,另外ES內部會多一些計算邏輯來如何進行分片路由,在寫入較高的場景下也會有一定的性能損耗。
  • 控制分片數量,分片數不是越多越好,過多分分片,也會造成ES集羣元數據管理的壓力,降低系統的性能損耗。
  • 設置total\_shards\_per\_node,將索引壓力分攤至多個節點。
  • index.routing.allocation.total\_shards\_per\_node參數可以限制每個節點上的shard數量,從而將索引的壓力分攤到多個節點,這樣可以提高集羣性能和可用性,避免某個節點過載導致整個集羣出現問題。
  • index.routing.allocation.total\_shards\_per\_node是一個索引級別設置(創建索引和對已有索引進行設置),語法如下:
PUT <index_name>/_settings
{
    "index.routing.allocation.total_shards_per_node":<number_of_shards>
}
<index_name>為索引名字,<number_of_shards>表示每個節點上該索引的分片數量。

持續調整索分片

對於集羣分片的調整,通常不是一蹴而就的。隨着業務的發展,不斷新增的子業務 或 原有子業務規模發生突變,都需要持續調整分片數量。

4.3 索引與資源消耗的關係**

分片數量與內存消耗

每個分片都是獨立的Lucene索引,需要維護倒排索引、緩存等內存結構。分片數量過多會導致以下問題:
  • 內存佔用激增: 每個分片默認佔用約10-30MB內存(含元數據),數千分片可能消耗數十GB內存。
  • 文件句柄耗盡: 集羣總分片數過多會佔用大量文件描述符,可能觸發"too many open files"錯誤。
  • CPU熱點問題: 分片分配不均會導致部分節點負載過高。

Segment碎片化

分片由多個segment組成,segment數量過多會:
  • 增加IO壓力: 查詢需遍歷多個segment文件。
  • 佔用堆內存: 每個segment需加載部分元數據到內存,百萬級segment可能消耗數GB內存。
  • 影響GC效率: 頻繁的segment合併會觸發Full GC。

五、總結

創建一個索引需要結合業務使用場景考量字段類型選擇和是否需要索引分詞,按照數據規模和業務增長速度來確定分片和副本的數量的大小。索引的結構直接影響集羣的穩定性,因此我們在創建索引的時候要養成習慣,作為技術方案的一環去仔細打磨這樣才能保證線上的穩定性。

大家工作中遇到的一些穩定性問題,和使用上的一些問題都可以找我們一起探討,尋找最優解。

往期回顧

1. DPP推薦引擎架構升級演進之路|得物技術

2. Cursor 在前端需求開發工作流中的應用|得物技術

3. 得物 iOS 啓動優化之 Building Closure

4. 分佈式數據一致性場景與方案處理分析|得物技術

5. 從對話到自主行動:AI應用如何從 Chat 進化為 Agent?開源項目源碼深度揭秘|得物技術

文 / 陽光

關注得物技術,每週一、三更新技術乾貨

要是覺得文章對你有幫助的話,歡迎評論轉發點贊~

未經得物技術許可嚴禁轉載,否則依法追究法律責任。

user avatar zhaodawan Avatar mamaster777 Avatar tangzhiyuan Avatar aipaobudezuoyeben Avatar puxiaoke6 Avatar feibendemaojin Avatar tuhooo Avatar chengxy Avatar qcloudcommunity Avatar tiandexianggua Avatar xiongshihubao Avatar wanzuqiudeshangba Avatar
Favorites 23 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.