每天幾百 GB 增量實時資料的TB級甚至PB級别的大索引如何設計?
分片數和副本數大小如何設計,才能提升 ES 叢集的性能?
ES 的 Mapping 該如何設計,才能保證檢索的高效?
檢索類型 term/match/matchphrase/querystring /match_phrase _prefix /fuzzy 那麼多,設計階段如何選型呢?
分詞該如何設計,才能滿足複雜業務場景需求?
傳統資料庫中的多表關聯在 ES 中如何設計?......
這麼看來,沒有那麼 Easy,坑還是得一步步的踩出來的。
正如攜程架構師 WOOD 大叔所說“做搜尋容易,做好搜尋相當難!”,
VIVO 搜尋引擎架構師所說“ 熟練使用 ES 離做好搜尋還差很遠!”。
本文主結合作者近千萬級開發實戰經驗,和大家一起深入探讨一下Elasticsearch 索引設計......
索引設計的重要性
在美團寫給工程師的十條精進原則中強調了“設計優先”。無數事實證明,忽略了前期設計,往往會帶來很大的延期風險。并且未經評估的不當的設計會帶來巨大的維護成本,後期不得不騰出時間,專門進行優化和重構。
而 Elasticsearch 日漸成為大家非結構資料庫的首選方案,項目前期良好的設計和評審是必須的,能給整個項目帶來收益。
索引層面的設計在 Elasticsearch 相關産品、項目的設計階段的作用舉重若輕。
好的索引設計在整個叢集規劃中占據舉足輕重的作用,索引的設計直接影響叢集設計的好壞和複雜度。
好的索引設計應該是充分結合業務場景的時間次元和空間次元,結合業務場景充分考量增、删、改、查等全次元設計的。
好的索引設計是完全基于“設計先行,編碼在後”的原則,前期會花很長時間,為的是後期工作更加順暢,避免不必要的返工。
1、PB 級别的大索引如何設計?
單純的普通資料索引,如果不考慮增量資料,基本上普通索引就能夠滿足性能要求。
我們通常的操作就是:
步驟 1:建立索引;
步驟 2:導入或者寫入資料;
步驟 3:提供查詢請求通路或者查詢服務。
1.1 大索引的缺陷
如果每天億萬+的實時增量資料呢,基于以下幾點原因,單個索引是無法滿足要求的。在 360 技術訪談中也提到了大索引的設計的困惑。
1.1.1 存儲大小限制次元
單個分片(Shard)實際是 Lucene 的索引,單分片能存儲的最大文檔數是:2,147,483,519 (= Integer.MAX_VALUE - 128)。如下指令能檢視全部索引的分隔分片的文檔大小:
GET _cat/shards
app_index 2 p STARTED 9443 2.8mb 127.0.0.1 Hk9wFwU
app_index 2 r UNASSIGNED
app_index 3 p STARTED 9462 2.7mb 127.0.0.1 Hk9wFwU
app_index 3 r UNASSIGNED
app_index 4 p STARTED 9520 3.5mb 127.0.0.1 Hk9wFwU
app_index 4 r UNASSIGNED
app_index 1 p STARTED 9453 2.4mb 127.0.0.1 Hk9wFwU
app_index 1 r UNASSIGNED
app_index 0 p STARTED 9365 2.3mb 127.0.0.1 Hk9wFwU
app_index 0 r UNASSIGNED
1.1.2 性能次元
當然一個索引很大的話,資料寫入和查詢性能都會變差。
而高效檢索展現在:基于日期的檢索可以直接檢索對應日期的索引,無形中縮減了很大的資料規模。
比如檢索:“2019-02-01”号的資料,之前的檢索會是在一個月甚至更大體量的索引中進行。
現在直接檢索"index_2019-02-01"的索引,效率提升好幾倍。
1.1.3 風險次元
一旦一個大索引出現故障,相關的資料都會受到影響。而分成滾動索引的話,相當于做了實體隔離。
1.2 PB 級索引設計實作
綜上,結合實踐經驗,大索引設計建議:使用模闆+Rollover+Curator動态建立索引。動态索引使用效果如下:
index_2019-01-01-000001
index_2019-01-02-000002
index_2019-01-03-000003
index_2019-01-04-000004
index_2019-01-05-000005
1.2.1 使用模闆統一配置索引
目的:統一管理索引,相關索引字段完全一緻。
1.2.2 使用 Rollver 增量管理索引
目的:按照日期、文檔數、文檔存儲大小三個次元進行更新索引。使用舉例:
POST /logs_write/_rollover
{
"conditions": {
"max_age": "7d",
"max_docs": 1000,
"max_size": "5gb"
}
}
1.2.3 索引增量更新原理
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SMzUmZyMzYyYWN2IjYiNWYlZWZ0QWM1YGNllDO2YmY38CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)
一圖勝千言。
索引更新的時機是:當原始索引滿足設定條件的三個中的一個的時候,就會更新為新的索引。為保證業務的全索引檢索,一般采用别名機制。
在索引模闆設計階段,模闆定義一個全局别名:用途是全局檢索,如圖所示的别名:indexall。每次更新到新的索引後,新索引指向一個用于實時新資料寫入的别名,如圖所示的别名:indexlatest。同時将舊索引的别名 index_latest 移除。
别名删除和新增操作舉例:
POST /_aliases
"actions" : [
{ "remove" : { "index" : "index_2019-01-01-000001", "alias" : "index_latest" } },
{ "add" : { "index" : "index_2019-01-02-000002", "alias" : "index_latest" } }
]
經過如上步驟,即可完成索引的更新操作。
1.2.4 使用 curator 高效清理曆史資料
目的:按照日期定期删除、歸檔曆史資料。
一個大索引的資料删除方式隻能使用 delete_by_query,由于 ES 中使用更新版本機制。删除索引後,由于沒有實體删除,磁盤存儲資訊會不減反增。有同學就回報 500GB+ 的索引 delete_by_query 導緻負載增高的情況。
而按照日期劃分索引後,不需要的曆史資料可以做如下的處理。
删除——對應 delete 索引操作。
壓縮——對應 shrink 操作。
段合并——對應 force_merge 操作。
而這一切,可以借助:curator 工具通過簡單的配置檔案結合定義任務 crontab 一鍵實作。
注意:7.X高版本借助iLM實作更為簡單。
舉例,一鍵删除 30 天前的曆史資料:
[root@localhost .curator]# cat action.yml
actions:
1:
action: delete_indices
description: >-
Delete indices older than 30 days (based on index name), for logstash-
prefixed indices. Ignore the error if the filter does not result in an
actionable list of indices (ignore_empty_list) and exit cleanly.
options:
ignore_empty_list: True
disable_action: False
filters:
- filtertype: pattern
kind: prefix
value: logs_
- filtertype: age
source: name
direction: older
timestring: '%Y.%m.%d'
unit: days
unit_count: 30
2、分片數和副本數如何設計?
2.1 分片/副本認知
1、分片:分片本身都是一個功能齊全且獨立的“索引”,可以托管在叢集中的任何節點上。
資料切分分片的主要目的:
(1)水準分割/縮放内容量 。
(2)跨分片(可能在多個節點上)分布和并行化操作,提高性能/吞吐量。
注意:分片一旦建立,不可以修改大小。
2、副本:它在分片/節點出現故障時提供高可用性。
副本的好處:因為可以在所有副本上并行執行搜尋——是以擴充了搜尋量/吞吐量。
注意:副本分片與主分片存儲在叢集中不同的節點。副本的大小可以通過:number_of_replicas動态修改。
2.2 分片和副本實戰中設計
最常見問題答疑
2.2.1 問題 1:索引設定多少分片?
Shard 大小官方推薦值為 20-40GB, 具體原理呢?Elasticsearch 員工 Medcl 曾經讨論如下:
Lucene 底層沒有這個大小的限制,20-40GB 的這個區間範圍本身就比較大,經驗值有時候就是拍腦袋,不一定都好使。
Elasticsearch 對資料的隔離和遷移是以分片為機關進行的,分片太大,會加大遷移成本。
一個分片就是一個 Lucene 的庫,一個 Lucene 目錄裡面包含很多 Segment,每個 Segment 有文檔數的上限,Segment 内部的文檔 ID 目前使用的是 Java 的整型,也就是 2 的 31 次方,是以能夠表示的總的文檔數為Integer.MAXVALUE - 128 = 2^31 - 128 = 2147483647 - 1 = 2,147,483,519,也就是21.4億條。
同樣,如果你不 forcemerge 成一個 Segment,單個 shard 的文檔數能超過這個數。
單個 Lucene 越大,索引會越大,查詢的操作成本自然要越高,IO 壓力越大,自然會影響查詢體驗。
具體一個分片多少資料合适,還是需要結合實際的業務資料和實際的查詢來進行測試以進行評估。
綜合實戰+網上各種經驗分享,梳理如下:
第一步:預估一下資料量的規模。一共要存儲多久的資料,每天新增多少資料?兩者的乘積就是總資料量。
第二步:預估分多少個索引存儲。索引的劃分可以根據業務需要。
第三步:考慮和衡量可擴充性,預估需要搭建幾台機器的叢集。存儲主要看磁盤空間,假設每台機器2TB,可用:2TB0.85(磁盤實際使用率)0.85(ES 警戒水位線)。
第四步:單分片的大小建議最大設定為 30GB。此處如果是增量索引,可以結合大索引的設計部分的實作一起規劃。
前三步能得出一個索引的大小。分片數考慮次元:
1)分片數 = 索引大小/分片大小經驗值 30GB 。
2)分片數建議和節點數一緻。設計的時候1)、2)兩者權衡考慮+rollover 動态更新索引結合。
每個 shard 大小是按照經驗值 30G 到 50G,因為在這個範圍内查詢和寫入性能較好。
經驗值的探推薦閱讀:
Elasticsearch究竟要設定多少分片數?
探究 | Elasticsearch叢集規模和容量規劃的底層邏輯
2.2.2 問題 2:索引設定多少副本?
結合叢集的規模,對于叢集資料節點 >=2 的場景:建議副本至少設定為 1。
之前有同學出現過:副本設定為 0,長久以後會出現——資料寫入向指定機器傾斜的情況。
注意:
單節點的機器設定了副本也不會生效的。副本數的設計結合資料的安全需要。對于資料安全性要求非常高的業務場景,建議做好:增強備份(結合 ES 官方備份方案)。
3、Mapping 如何設計?
3.1 Mapping 認知
Mapping 是定義文檔及其包含的字段的存儲和索引方式的過程。例如,使用映射來定義:
應将哪些字元串字段定義為全文檢索字段;
哪些字段包含數字,日期或地理位置;
定義日期值的格式(時間戳還是日期類型等);
用于控制動态添加字段的映射的自定義規則。
3.2 設計 Mapping 的注意事項
ES 支援增加字段 //新增字段
PUT new_index
{
"mappings": {
"_doc": {
"properties": {
"status_code": {
"type": "keyword"
}
}
}
}
ES 不支援直接删除字段
ES 不支援直接修改字段
ES 不支援直接修改字段類型 如果非要做靈活設計,ES 有其他方案可以替換,借助reindex。但是資料量大會有性能問題,建議設計階段綜合權衡考慮。
3.3 Mapping 字段的設定流程
索引分為靜态 Mapping(自定義字段)+動态 Mapping(ES 自動根據導入資料适配)。
實戰業務場景建議:選用靜态 Mapping,根據業務類型自己定義字段類型。
好處:
可控;
節省存儲空間(預設 string 是 text+keyword,實際業務不一定需要)。
設定字段的時候,務必過一下如下圖示的流程。根據實際業務需要,主要關注點:
資料類型選型;
是否需要檢索;
是否需要排序+聚合分析;
是否需要另行存儲。
核心參數的含義,梳理如下:
3.4 Mapping 建議結合模闆定義
索引 Templates——索引模闆允許您定義在建立新索引時自動應用的模闆。模闆包括settings和Mappings以及控制是否應将模闆應用于新索引。
注意:模闆僅在索引建立時應用。更改模闆不會對現有索引産生影響。
第1部分也有說明,針對大索引,使用模闆是必須的。核心需要設定的setting(僅列舉了實戰中最常用、可以動态修改的)如下:
index.numberofreplicas 每個主分片具有的副本數。預設為 1(7.X 版本,低于 7.X 為 5)。
index.maxresultwindow 深度分頁 rom + size 的最大值—— 預設為 10000。
index.refresh_interval 預設 1s:代表最快 1s 搜尋可見;
寫入時候建議設定為 -1,提高寫入性能;
實戰業務如果對實時性要求不高,建議設定為 30s 或者更高。
3.5 包含 Mapping 的 template 設計萬能模闆
以下模闆已經在 7.2 驗證 ok,可以直接拷貝修改後實戰項目中使用。
PUT _template/test_template
"index_patterns": [
"test_index_*",
"test_*"
],
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"max_result_window": 100000,
"refresh_interval": "30s"
},
"mappings": {
"properties": {
"id": {
"type": "long"
},
"title": {
"type": "keyword"
"content": {
"analyzer": "ik_max_word",
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
"available": {
"type": "boolean"
"review": {
"type": "nested",
"nickname": {
"type": "text"
},
"text": {
"stars": {
"type": "integer"
"publish_time": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
"expected_attendees": {
"type": "integer_range"
"ip_addr": {
"type": "ip"
"suggest": {
"type": "completion"
4、分詞的選型
主要以 ik 來說明,最新版本的ik支援兩種類型。ik_maxword 細粒度比對,适用切分非常細的場景。ik_smart 粗粒度比對,适用切分粗的場景。
4.1 坑 1:分詞選型
實際業務中:建議适用ik_max_word分詞 + match_phrase短語檢索。
原因:ik_smart有覆寫不全的情況,資料量大了以後,即便 reindex 能滿足要求,但面對極大的索引的情況,reindex 的耗時我們承擔不起。建議ik_max_word一步到位。
4.2 坑 2:ik 要裝叢集的所有機器嗎?
建議:安裝在叢集的所有節點上。
4.3 坑 3:ik 比對不到怎麼辦?
方案1:擴充 ik 開源自帶的詞庫+動态更新詞庫;原生的詞庫分詞數量級很小,基礎詞庫盡量更大更全,網上搜尋一下“搜狗詞庫“。
動态更新詞庫:可以結合 mysql+ik 自帶的更新詞庫的方式動态更新詞庫。
更新詞庫僅對新建立的索引生效,部分老資料索引建議使用 reindex 更新處理。
方案2:采用字詞混合索引的方式,避免“明明存在,但是檢索不到的”場景。
探究 | 明明存在,怎麼搜尋不出來呢?
5、檢索類型如何選型呢?
前提:5.X 版本之後,string 類型不再存在,取代的是text和keyword類型。
text 類型作用:分詞,将大段的文字根據分詞器切分成獨立的詞或者詞組,以便全文檢索。
适用于:email 内容、某産品的描述等需要分詞全文檢索的字段;
不适用:排序或聚合(Significant Terms 聚合例外)
keyword 類型:無需分詞、整段完整精确比對。
适用于:email 位址、住址、狀态碼、分類 tags。
以一個實戰例子說明:
PUT zz_test
{
"mappings": {
"doc": {
"title": {
"type": "text",
"analyzer":"ik_max_word",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
GET zz_test/_mapping
PUT zz_test/doc/1
"title":"錘子加濕器官方緻歉,難産後臨時推遲一個月發貨遭diss耍流氓"
POST zz_test/_analyze
"text": "錘子加濕器官方緻歉,難産後臨時推遲一個月發貨遭diss耍流氓",
"analyzer": "ik_max_word"
ik_max_word的分詞結果如下:
錘子、錘、子、加濕器、濕、器官、官方、方、緻歉、緻、歉、難産、産後、後、臨時、臨、時、推遲、遲、一個、 一個、 一、個月、 個、 月、 發貨、發、貨、遭、diss、耍流氓、耍、流氓、氓。
5.1 term 精确比對
核心功能:不受到分詞器的影響,屬于完整的精确比對。
應用場景:精确、精準比對。
适用類型:keyword。
舉例:term 最适合比對的類型是 keyword,如下所示的精确完整比對:
POST zz_test/_search
"query": {
"term": {
"title.keyword": "錘子加濕器官方緻歉,難産後臨時推遲一個月發貨遭diss耍流氓"
注意:如下是比對不到結果的。
POST zz_test/_search
"query": {
"term": {
"title": "錘子加濕器"
原因:對于 title 中的錘子加濕器,term 不會做分詞拆分比對的。且 ik_max_word 分詞也是沒有“錘子加濕器”這組關鍵詞的。
5.2 prefix 字首比對
核心功能:字首比對。
應用場景:字首自動補全的業務場景。
如下能比對到文檔 id 為 1 的文章。
"prefix": {
"title.keyword": "錘子加濕器"
5.3 wildcard 模糊比對
核心功能:比對具有比對通配符表達式 keyword 類型的文檔。支援的通配符:*,它比對任何字元序列(包括空字元序列);?,它比對任何單個字元。
應用場景:請注意,選型務必要慎重!此查詢可能很慢多組關鍵次的情況下可能會導緻當機,因為它需要周遊多個術語。為了防止非常慢的通配符查詢,通配符不能以任何一個通配符*或?開頭。
如下比對,類似 MySQL 中的通配符比對,能比對所有包含加濕器的文章。
"wildcard": {
"title.keyword": "*加濕器*"
5.4 match 分詞比對
核心功能:全文檢索,分詞詞項比對。
應用場景:實際業務中較少使用,原因:比對範圍太寬泛,不夠準确。
适用類型:text。
如下示例,title 包含"錘子"和“加濕器”的都會被檢索到。
"profile": true,
"match": {
5.5 match_phrase 短語比對
核心功能:match_phrase 查詢首先将查詢字元串解析成一個詞項清單,然後對這些詞項進行搜尋; 隻保留那些包含 全部 搜尋詞項,且 位置"position" 與搜尋詞項相同的文檔。
應用場景:業務開發中 90%+ 的全文檢索都會使用 match_phrase 或者 query_string 類型,而不是 match。
"text": "錘子加濕器",
分詞結果:
錘子, 錘,子, 加濕器, 濕,器。而:id為1的文檔的分詞結果:錘子, 錘, 子, 加濕器, 濕, 器官。是以,如下的檢索是比對不到結果的。
"query": {
"match_phrase": {
"title": "錘子加濕器"
如果想比對到,怎麼辦呢?這裡可以字詞組合索引的形式。
推薦閱讀:
5.6 multi_match 多組比對
核心功能:match query 針對多字段的更新版本。
應用場景:多字段檢索。
舉例:
"multi_match": {
"query": "加濕器",
"fields": [
"title",
"content"
]
5.7 query_string 類型
核心功能:支援與或非表達式+其他N多配置參數。
應用場景:業務系統需要支援自定義表達式檢索。
"query_string": {
"default_field": "title",
"query": "(錘子 AND 加濕器) OR (官方 AND 道歉)"
5.8 bool 組合比對
核心功能:多條件組合綜合查詢。
應用場景:支援多條件組合查詢的場景。
适用類型:text 或者 keyword。一個 bool 過濾器由三部分組成:
"bool" : {
"must" : [],
"should" : [],
"must_not" : [],
"filter": []
}
must ——所有的語句都 必須(must) 比對,與 AND 等價。
must_not ——所有的語句都 不能(must not) 比對,與 NOT 等價。
should ——至少有一個語句要比對,與 OR 等價。
filter——必須比對,運作在非評分&過濾模式。
小結:
6、多表關聯如何設計?
6.1 為什麼會有多表關聯
多表關聯是被問的最多的問題之一。幾乎每周都會被問到。
主要原因:正常基于關系型資料庫開發,多多少少都會遇到關聯查詢。而關系型資料庫設計的思維很容易帶到 ES 的設計中。
6.2 多表關聯如何實作
方案一:多表關聯視圖,視圖同步 ES
MySQL 寬表導入 ES,使用 ES 查詢+檢索。适用場景:基礎業務都在 MySQL,存在幾十張甚至幾百張表,準備同步到 ES,使用 ES 做全文檢索。
将資料整合成一個寬表後寫到 ES,寬表的實作可以借助關系型資料庫的視圖實作。
寬表處理在處理一對多、多對多關系時,會有字段備援問題,如果借助:logstash_input_jdbc,關系型資料庫如 MySQL 中的每一個字段都會自動幫你轉成 ES 中對應索引下的對應 document 下的某個相同字段下的資料。
步驟 1:提前關聯好資料,将關聯的表建立好視圖,一個索引對應你的一個視圖,并确認視圖中資料的正确性。
步驟 2:ES 中針對每個視圖定義好索引名稱及 Mapping。
步驟 3:以視圖為機關通過 logstash_input_jdbc 同步到 ES 中。
方案二:1 對 1 同步 ES
MySQL+ES 結合,各取所長。适用場景:關系型資料庫全量同步到 ES 存儲,沒有做備援視圖關聯。
ES 擅長的是檢索,而 MySQL 才擅長關系管理。
是以可以考慮二者結合,使用 ES 多索引建立相同的别名,針對别名檢索到對應 ID 後再回 MySQL 通過關聯 ID join 出需要的資料。
方案三:使用 Nested 做好關聯
适用場景:1 對少量的場景。
舉例:有一個文檔描述了一個文章和一個包含文章上所有評論的内部對象評論。可以借助 Nested 實作。
Nested 類型選型——如果需要索引對象數組并保持數組中每個對象的獨立性,則應使用嵌套 Nested 資料類型而不是對象 Oject 資料類型。
當使用嵌套文檔時,使用通用的查詢方式是無法通路到的,必須使用合适的查詢方式(nested query、nested filter、nested facet等),很多場景下,使用嵌套文檔的複雜度在于索引階段對關聯關系的組織拼裝。
方案四:使用 ES6.X+ 父子關系 Join 做關聯
适用場景:1 對多量的場景。
舉例:1 個産品和供應商之間是1對N的關聯關系。
Join 類型:join 資料類型是一個特殊字段,用于在同一索引的文檔中建立父/子關系。關系部分定義文檔中的一組可能關系,每個關系是父名稱和子名稱。
當使用父子文檔時,使用has_child 或者has_parent做父子關聯查詢。
方案三、方案四選型對比:
注意:方案三&方案四選型必須考慮性能問題。文檔應該盡量通過合理的模組化來提升檢索效率。
Join 類型應該盡量避免使用。nested 類型檢索使得檢索效率慢幾倍,父子Join 類型檢索會使得檢索效率慢幾百倍。
盡量将業務轉化為沒有關聯關系的文檔形式,在文檔模組化處多下功夫,以提升檢索效率。
幹貨 | 論Elasticsearch資料模組化的重要性
幹貨 | Elasticsearch多表關聯設計指南
小結
7、實戰中遇到過的坑
如果能重來,我會如何設計 Elasticsearch 系統?
來自累計近千萬實戰項目設計的思考。
坑1: 資料清洗一定發生在寫入 es 之前!而不是請求資料後處理,拿勢必會降低請求速度和效率。
坑2:高亮不要重複造輪子,用原生就可以。
坑3:讓 es 做他擅長的事,檢索+不複雜的聚合,否則資料量+複雜的業務邏輯大會有性能問題。
坑4:設計的工作必須不要省!快了就是慢了,否則無休止的因設計缺陷引發的 bug 會增加團隊的戳敗感!
坑5:在給定時間的前提下,永遠不會有完美的設計,必須相對合理的設計+重構結合,才會有相對靠譜的系統。
坑6:SSD 能提升性能,但如果系統業務邏輯非常負責,換了 SSD 未必達到預期。
坑7:由于 Elasticsearch 不支援事務 ACID 特性,資料庫作為實時資料補充,對于實時資料要求嚴格的場景,必須同時采取雙寫或者同步的方式。這樣,一旦實時資料出現不一緻,可以通過資料庫進行同步遞增更新。
8、小結
本文從選題和撰寫曆時2周+的時間,期間反複梳理了開發過程中遇到的問題、社群/QQ 群/知識星球等中大家提問的問題。
将TOP N 大家最關注的問題也是實戰中得得确确容易混淆的問題梳理出來,目的是:讓更多的後來人看到,不要再走一遍彎路。
相信經過幾天梳理的實戰文檔,能給你帶來幫助!感謝您的回報和交流。
1、百億級日志系統架構設計及優化
http://t.cn/RsGCUGs2.探讨理想的Elasticsearch索引設計原則。
http://t.cn/Rusfb0U公衆号:銘毅天下
部落格:elastic.blog.csdn.net