我在做 Elasticsearch 相關(guān)咨詢和培訓(xùn)過程中,發(fā)現(xiàn)大家普遍更關(guān)注實戰(zhàn)中涉及的問題,下面我選取幾個常見且典型的問題,和大家一起分析一下。
如果我們對上述實戰(zhàn)問題進行歸類,就都可以歸結(jié)為 Elasticsearch 本文將以實戰(zhàn)問題為基準,手把手帶你實踐 Elasticsearch 數(shù)據(jù)建模全流程,重點解析基于業(yè)務(wù)角度、數(shù)據(jù)量角度、Setting 、Mapping ,以及復(fù)雜索引關(guān)聯(lián),這五個層面中涉及的數(shù)據(jù)建模實戰(zhàn)問題,讓你學(xué)完即可應(yīng)用到工作中。 1、為什么要做數(shù)據(jù)建模?我們選型傳統(tǒng)的數(shù)據(jù)庫,這里以 MySQL 為例,做數(shù)據(jù)存儲前需要考慮的問題如下:
以上這些疑問也均是數(shù)據(jù)建模問題。在 MySQL 中我們往往認為建模非常有必要,但反觀 Elasticsearch ,“上手快”這類先入為主的觀念已根植在很多同學(xué)心中,使得大家忽略了 Elasticsearch 數(shù)據(jù)建模的重要性。 接下來,我們基于 MySQL 做數(shù)據(jù)存儲需要考慮的問題,重新審視數(shù)據(jù)建模的定義,內(nèi)容如下。
到這里,相信你已經(jīng)初步明晰了數(shù)據(jù)建模的重要性。但我還想提醒你的是,“一把梭用法,上來就是干”并不是捷徑,尤其到了項目中后期,極易暴露出問題。經(jīng)歷的項目越多,你會發(fā)現(xiàn)建模的時間不能省。 下面我們具體分析一下為什么要數(shù)據(jù)建模? 相比于 MySQL,Elasticsearch 有非??旖莸?/span> Elasticsearch 支持動態(tài)類型檢查和匹配。也就是說,當我們寫入索引數(shù)據(jù)的時候,可以不提前指定數(shù)據(jù)類型,直接插入數(shù)據(jù)。 以類似天眼查、企查查的工商實戰(zhàn)數(shù)據(jù)為例(已做脫敏處理),如果利用以下語句直接創(chuàng)建索引和寫入一條數(shù)據(jù),豈不是很快?
相比于 MySQL 中一個字段一個字段地敲定,這樣操作確實節(jié)省了很多時間。但隨著后續(xù)數(shù)據(jù)量激增,副作用便會很快顯現(xiàn)出來。該處理方式的弊端:
接下來,結(jié)合我自己工作中早期系統(tǒng)的一個案例,我們做進一步分析。 5 個數(shù)據(jù)節(jié)點集群(5 個分片,1 個副本),微博數(shù)據(jù)每日增量 5000W+(增量存儲 150GB),核心數(shù)據(jù)磁盤 10TB 左右,很明顯該系統(tǒng)面臨存儲上限問題。 我們當時就上述業(yè)務(wù)數(shù)據(jù)規(guī)劃了一個大索引,比如微博數(shù)據(jù)一個索引,微信數(shù)據(jù)一個索引。但微博索引最多只能存儲 20 天左右的數(shù)據(jù),然后就得走刪除索引數(shù)據(jù)的操作。由于 1 個索引只能通過 delete_by_query 刪除部分數(shù)據(jù),而 delete_by_query 的特點是版本號更新的邏輯刪除,實際效果是越刪數(shù)據(jù)量越大,磁盤占用率激增。加上是線上環(huán)境,壓力之大,處理難度之大,經(jīng)歷過你就知道有多苦。 這也是很多大廠在面試候選人的時候,尤其偏愛數(shù)據(jù)建模能力強的工程師的主要原因之一。 比如下圖是美團對大數(shù)據(jù)開發(fā)高級工程師的崗位要求,第一條就是“深入理解業(yè)務(wù),對業(yè)務(wù)服務(wù)流程進行合理的抽象和建模。” ![]() 從以上兩個反例,以及這條招聘信息中便可以窺探出數(shù)據(jù)建模的重要性。下面我們具體說說如何做數(shù)據(jù)建模。 2、Elasticsearch 如何數(shù)據(jù)建模?在做數(shù)據(jù)建模之前,會先進行架構(gòu)設(shè)計,架構(gòu)環(huán)節(jié)涉及選型、集群規(guī)劃、節(jié)點角色劃分。 本文涉及的建模傾向于索引層面、數(shù)據(jù)層面的建模。為了讓你學(xué)完即可應(yīng)用到工作中,我會結(jié)合項目實戰(zhàn)進行講解。 2.1 基于業(yè)務(wù)角度建模Elasticsearch 適用范圍非常廣,包括電商、快遞、日志等各行各業(yè)。涉及索引層面的設(shè)計,和業(yè)務(wù)貼合緊密。 其一:業(yè)務(wù)一定要細分。 分成哪幾類數(shù)據(jù),每類數(shù)據(jù)歸結(jié)為一個索引還是多個索引,這是產(chǎn)品經(jīng)理、架構(gòu)師、項目經(jīng)理要討論敲定的問題。比如大數(shù)據(jù)類的數(shù)據(jù),可以按照業(yè)務(wù)數(shù)據(jù)分為微博索引、微信索引、Twiiter 索引、Facebook 索引等。 其二:多個業(yè)務(wù)類型需不需要跨索引檢索? 跨索引檢索的痛點是字段不統(tǒng)一、不一致,需要寫非常復(fù)雜的 bool 組合查詢語句來實現(xiàn)。為了避免這種情況,最好的方式就是提前建模。每一類業(yè)務(wù)數(shù)據(jù)的相同或者相似字段,采取統(tǒng)一建模的方式。 下面我們舉一個實際的例子加以分析。微博、微信、Twitter、Facebook 都有的字段,可以設(shè)計如下:
這樣設(shè)計的好處是:字段統(tǒng)一,寫查詢 DSL 無需特殊處理,非常快捷方便。所以,在設(shè)計階段,多個業(yè)務(wù)索引數(shù)據(jù)要盡可能地“求同存異”。具體來說:
比如微博信息來源字段有手機 App 或者網(wǎng)頁等,別的業(yè)務(wù)索引如果沒有,獨立建模就可以。 類似這些建模信息可以統(tǒng)一 Excel 存儲,統(tǒng)一 git 多人協(xié)作管理。 多索引管理一般優(yōu)先推薦使用模板(template)和 別名(alias)結(jié)合的方式。
![]() 2.2 基于數(shù)據(jù)量角度建模如本文前面所述,我是吃過單索引激增的虧,所以對于時序性數(shù)據(jù)(日志數(shù)據(jù)、大數(shù)據(jù)類數(shù)據(jù))等,我強烈建議你基于時間切分索引,具體如下圖所示。 ![]() 當然,其他可用的方案非常多,這里我列舉如下,供你選型參考。 ![]() 由此可見,時序管理數(shù)據(jù)的優(yōu)點非常明顯。
2.3 基于 Setting 層面建模Setting 層面又分為靜態(tài) Setting 和動態(tài) Setting 兩種。 一種是靜態(tài) Settings,一旦設(shè)置后,后續(xù)不可修改。如 另一種是動態(tài) Setting,索引創(chuàng)建后,后面隨時可以更新。如 僅就建模階段最核心的問題,拆解如下。
這里有個認知前提,就是主分片數(shù)一旦設(shè)置后就不可以修改,副本分片數(shù)可以靈活動態(tài)調(diào)整。 主分片設(shè)計一般會考量總體數(shù)據(jù)量、集群節(jié)點規(guī)模,這點在集群規(guī)劃層面會著重強調(diào)。一般主分片數(shù)要考慮集群未來動態(tài)擴展,通常設(shè)置為數(shù)據(jù)節(jié)點的 1 倍或者 1~3 倍之間的值。 副本分片是保證集群的高可用性,普通業(yè)務(wù)場景建議至少設(shè)置一個副本。
默認值 1s,這意味著在寫入階段,每秒都會生成一個分段。
在實際業(yè)務(wù)場景里,如果寫入的數(shù)據(jù)不需要近實時搜索可見,可以適當?shù)卦谀0?、索引層面調(diào)大這個值,當然也可以動態(tài)調(diào)整,比如調(diào)整為 30s 或者 60s。
這里同樣有個認知前提,就是對于深度翻頁的 from + size 實現(xiàn),越往后翻頁越慢。其實你對比看主流搜索引擎,比如 Google、百度、360、Bing 均不支持一下跳轉(zhuǎn)到最后一頁,這就是最大翻頁上限限制。 其實在基本業(yè)務(wù)層面也很好理解,按照相關(guān)度返回結(jié)果,前面幾頁是最相關(guān)的,越往后相關(guān)度越低。比如默認值 10000,也就是說如果每頁顯示 10 條數(shù)據(jù),可以翻 1000 頁。基本業(yè)務(wù)場景已經(jīng)足夠了。因此不建議調(diào)大該值。 如果需要向后翻頁查詢,推薦 search_after 查詢方式。如果需要全量遍歷或者全量導(dǎo)出數(shù)據(jù),推薦 scroll 查詢方式。
管道預(yù)處理的好處很多,雖然 5.X 版本就有了這個功能,但實戰(zhàn)環(huán)境用起來還不多。 管道 這里給出索引層面 更多設(shè)置,推薦閱讀官方文檔,地址如下: https://www./guide/en/elasticsearch/reference/current/index-modules.html#index-modules-settings
2.4 基于 Mapping 層面建模Mapping 層面核心是字段名稱、字段類型、分詞器選型、多字段 multi_fields 選型,以及字段細節(jié)(是否索引、是否存儲等)的敲定。 (1)字段命名要規(guī)范索引名稱不允許用大寫,字段名稱官方?jīng)]有限制,但是可以參考 Java 編碼規(guī)范。我還真見過學(xué)員用中文或者拼音命名的,非常不專業(yè),大家一定要避免。 (2)字段類型要合理要結(jié)合業(yè)務(wù)類型選擇合適的字段類型。比如 integer 能搞定的,就不要用 long、float 或 double。 注意,字符串類型在 5.X 版本之后分為兩種類型:
再舉個例子,實戰(zhàn)中情感值介于 0~100 之間,50 代表中性,0~50 代表負面,50~100 代表正面。如果使用 integer 查詢的時候要 range query,而實際存儲可以增加字段:0~50 設(shè)置為 -1,50 設(shè)置為 0,50~100 設(shè)置為 1,三種都是 keyword 類型,檢索時直接走 term 檢索會非???。 (3)分詞器要靈活實戰(zhàn)中中文分詞器用得比較多,中文分詞又分為 ansj,結(jié)巴,IK 等。以 IK 舉例,可以細分為 ik_smart 粗粒度分詞、ik_max_word 細粒度分詞。 在工作中,要結(jié)合業(yè)務(wù)選擇合適的分詞器,分詞器一旦設(shè)定是不可以修改的,除非 reindex。 分詞器選型后,都會有動態(tài)詞典的更新問題。更新的前提是不要僅使用開源插件原生詞典,而是要在平時業(yè)務(wù)中自己多積累特定業(yè)務(wù)數(shù)據(jù)詞典、詞庫。 如果要動態(tài)更新:一般推薦第三方更新插件借助數(shù)據(jù)庫更新實現(xiàn)。如果普通分詞都不能滿足業(yè)務(wù)需要,可以考慮 (4)multi_fields 適機使用同一個字段根據(jù)需要可以設(shè)置多種類型。實戰(zhàn)業(yè)務(wù)中,對用特定中文詞明明存在,卻無法召回的情況,采用字詞混合索引的方式得以滿足。 所謂字詞混合,實際就是 standard 分詞器實現(xiàn)單字拆解,以及 ik_max_word 實現(xiàn)中文切詞結(jié)合的方式。檢索的時候 bool 對兩種分詞器結(jié)合,就可以實現(xiàn)相對精準的召回效果。
為了方便你記憶和使用,這里我把字段細節(jié)總結(jié)在如下這張表格中。
我們再來分析一下數(shù)據(jù)建模的流程,如下圖所示。 ![]() 數(shù)據(jù)建模的流程圖 首先,根據(jù)業(yè)務(wù)選擇合適的數(shù)據(jù)類型。 注意字符串類型分為兩種 text 和 keyword類型;盡量選擇貼近實際大小的數(shù)據(jù)類型;nested 和 join 復(fù)雜類型需根據(jù)業(yè)務(wù)特點選型,具體會在下一部分詳細闡述。 其次,判定是否需要檢索,如果不需要,index 設(shè)置為 false 即可。 然后,判定是否需要排序和聚合操作,如果不需要可以設(shè)置 doc_values 為 false。 最后,考慮一下是否需要另行存儲,會結(jié)合使用 store 和 _source 字段。 Mapping 層面要強調(diào)的是:盡量不要使用默認的 dynamic 動態(tài)字段類型,強烈建議 strict 嚴格控制字段,避免字段“暴漲”導(dǎo)致不可預(yù)知的風(fēng)險,比如字段數(shù)超過默認 1000 個的上限、磁盤大于預(yù)期的激增等。 2.5 基于復(fù)雜索引關(guān)聯(lián)建模要摒棄 MySQL 的多表關(guān)聯(lián)建模思想,因為 MySQL 中的范式思想都不再適用于 Elasticsearch?;仡櫸恼麻_頭的幾個多表關(guān)聯(lián)問題,Elasticsearch 能提供的核心解決方案如下。 (1) 寬表方案這是空間換時間的方案,就是允許部分字段冗余存儲的存儲方式。實戰(zhàn)舉例如下。 用戶索引:user。 博客索引:blogpost。 一個用戶可以發(fā)表多篇博客。按照傳統(tǒng)的 MySQL 建表思想:兩個表建立個用戶外鍵,即可搞定一切。而對于 Elasticsearch,我們更愿意在每篇博文后面都加上用戶信息(這就是寬表存儲的方案),看似存儲量大了,但是一次檢索就能搞定搜索結(jié)果。
(2) nested 方案
如果需要索引對象數(shù)組并保持數(shù)組中每個對象的獨立性,則應(yīng)使用嵌套 Nested 數(shù)據(jù)類型而不是對象 Oject 數(shù)據(jù)類型。 nested 文檔的優(yōu)點是可以將父子關(guān)系的兩部分數(shù)據(jù)(如博客+評論)關(guān)聯(lián)起來,我們可以基于nested 類型做任何的查詢。但缺點是查詢速度相對較慢,更新子文檔需要更新整篇文檔。 (3) join 父子文檔方案
比如 1 個產(chǎn)品和供應(yīng)商之間就是 1 對 N 的關(guān)聯(lián)關(guān)系。當使用父子文檔時,使用 has_child 或者 has_parent 做父子關(guān)聯(lián)查詢。優(yōu)點是父子文檔可獨立更新,但維護 Join 關(guān)系需要占據(jù)部分內(nèi)存,查詢較 Nested 更耗資源。 注意:5.X 之前版本叫父子文檔(多 type 實現(xiàn)),6.X 之后高版本是 join 類型(單 type 類型)。 (4) 業(yè)務(wù)層面實現(xiàn)關(guān)聯(lián)需通過多次檢索獲取所需的關(guān)鍵字段,業(yè)務(wù)層面自己寫代碼實現(xiàn)。 這里小結(jié)一下,以上四種方式便是 Elasticsearch 能實現(xiàn)的全量多表關(guān)聯(lián)方案。實戰(zhàn)建模階段,一定要結(jié)合自己的業(yè)務(wù)場景,盡量往上靠,先通過 kibana dev tool 模擬實現(xiàn),找到契合自己業(yè)務(wù)的多表關(guān)聯(lián)方案。 此外我還要強調(diào)的是:多表關(guān)聯(lián)都會有性能問題,數(shù)據(jù)量極大且檢索性能要求高的場景需要慎用。這里我摘取了官方文檔對應(yīng)的描述如下,供你參考。 尤其應(yīng)該避免多表關(guān)聯(lián)。Nested 嵌套可以使查詢慢幾倍,而 Join 父子關(guān)系可以使查詢慢數(shù)百倍。 3、總結(jié)最后,我們再來總結(jié)一下建模其他核心考量因素。 ![]()
數(shù)據(jù)建模是 Elasticsearch 開發(fā)實戰(zhàn)中非常重要的一環(huán),也是項目管理角度中的設(shè)計環(huán)節(jié)的重中之重,你一定要重視!千萬不要著急寫業(yè)務(wù)代碼,以“代碼之前,設(shè)計先行”作為行動準繩。 感謝你的時間!有 Elasticsearch 建模問題歡迎留言交流。 本文成文于:2021年8月11日 推薦更短時間更快習(xí)得更多干貨! 和全球 1600+ Elastic 愛好者一起精進! ![]() |
|