Elasticsearch 查詢和數(shù)據(jù)同步 - 記一次技術(shù)實(shí)踐

前言

前段時(shí)間與同事一起為產(chǎn)品接入了 Elasticsearch 框架技術(shù)。從參與方案會(huì)議到搭建開發(fā)上線過(guò)程中有很多討論點(diǎn),故產(chǎn)生本文,希望藉此總結(jié)和分享一些經(jīng)驗(yàn)。

1. 業(yè)務(wù)模型

接觸已有的業(yè)務(wù)時(shí),數(shù)據(jù)模型是最早需要知道的信息。我和同事負(fù)責(zé)接入 Elasticsearch 的產(chǎn)品是一個(gè)業(yè)務(wù)繁多的通訊錄,簡(jiǎn)化下來(lái)就是 3 個(gè)關(guān)鍵的模型,如下:

  • 部門(Department)
  • 人員(User)
  • 標(biāo)簽(Tag)

它們的用途和聯(lián)系,就跟它們的詞義一樣。
由此產(chǎn)生的業(yè)務(wù)如下:

  • 通過(guò) 標(biāo)簽 查詢 部門、人員
  • 通過(guò) 部門 查詢 人員
3個(gè)模型的簡(jiǎn)化聯(lián)系.png

基于以上模型和業(yè)務(wù),在典型的關(guān)系型數(shù)據(jù)庫(kù)下,為了實(shí)現(xiàn)關(guān)聯(lián)關(guān)系,自然會(huì)有額外的關(guān)聯(lián)表

  • 部門人員關(guān)聯(lián)表:每條記錄包含1個(gè)部門,1個(gè)人員
  • 標(biāo)簽對(duì)象關(guān)聯(lián)表:每條記錄包含1個(gè)標(biāo)簽,1個(gè)部門或人員

2. 需求

Elasticsearch 的特點(diǎn)有全文檢索、分布式、海量數(shù)據(jù)下近實(shí)時(shí)查詢
當(dāng)時(shí)為通訊錄業(yè)務(wù)引入 Elasticsearch 的需求和目標(biāo)如下:

  • 多字段的匹配或模糊查詢。這些部門、人員、標(biāo)簽數(shù)據(jù)原本存儲(chǔ)在 MySQL 中,如果要做匹配多個(gè)字段的模糊查詢就比較吃力了,考慮一個(gè)常用功能 “輸入姓名/手機(jī)號(hào)/拼音/首字母來(lái)查詢?nèi)藛T”。而快速查詢此類業(yè)務(wù)是 Elasticsearch 可以提供的。
  • 基礎(chǔ)模塊能力。其他業(yè)務(wù)模塊也提出了類似全文檢索的需求,因此在通訊錄業(yè)務(wù)首次應(yīng)用 es 時(shí),要定義和提供好 es 的訪問(wèn)和工具方法,供其他模塊在未來(lái)接入時(shí),能復(fù)用一些實(shí)現(xiàn),能保持一致的接口和命名風(fēng)格等。

3. 索引設(shè)計(jì)

從原 MySQL 數(shù)據(jù)庫(kù)表,到 Elasticsearch 的索引,數(shù)據(jù)模型的變化稱為異構(gòu)。
Elasticsearch 適合解決在 MySQL 中多條件或連表這樣比較慢的查詢業(yè)務(wù),因此除了原有的信息字段,我們會(huì)再附加 3 個(gè)模型的關(guān)聯(lián)關(guān)系到 es 索引中。

索引 \ 字段 原有 關(guān)聯(lián)關(guān)系
部門 部門名
完整部門路徑名
(無(wú))
人員 姓名
拼音
首字母
手機(jī)號(hào)
父部門Id
所有父級(jí)部門Id
標(biāo)簽Id
標(biāo)簽 標(biāo)簽名 部門Id
人員Id

(上表略去了一些無(wú)關(guān)本篇內(nèi)容的字段,如 SaaS 平臺(tái)的租戶Id、每個(gè)對(duì)象的信息詳情字段)

  • 是否需要添加關(guān)聯(lián)關(guān)系的字段,是由業(yè)務(wù)需求決定的。拿人員索引的 “所有父級(jí)部門Id” 舉例子,因?yàn)橛胁樵儾块T下所有人員(包括直屬、子部門下的)的業(yè)務(wù)需求,所以會(huì)設(shè)計(jì)這么一個(gè)字段。

  • 可以使用 Elasticsearch 的分詞功能來(lái)記錄關(guān)聯(lián)關(guān)系的字段中。為該字段定義一個(gè)分隔模式為豎線 “|” 的分詞器,把若干個(gè)關(guān)聯(lián)Id存成一個(gè)拼接的字符串。

4. 版本選擇

同事是個(gè)版本控,在選擇版本時(shí)了解和考慮了非常多的信息。不過(guò)版本選擇確實(shí)是為平臺(tái)接入新技術(shù)時(shí)的一個(gè)重要考慮點(diǎn)。我們提出這個(gè)方案的當(dāng)時(shí)(2018年4月),對(duì)比了主要使用的云服務(wù)提供商的幾個(gè)版本,考慮項(xiàng)可以按優(yōu)先級(jí)概括為:

  1. 穩(wěn)定的
  2. 案例資料多的
  3. 時(shí)新程度,包括 Elasticsearch版本 和 Lucene版本
  4. 我們已經(jīng)使用了某家云服務(wù)提供商,會(huì)偏向再用其提供的服務(wù)

幾個(gè)版本對(duì)比

我們當(dāng)時(shí)選擇了 Elasticsearch 6.2.2 版本。

v5.6.4

  • 是 Spring 整合的各個(gè)框架中,支持?jǐn)?shù)最多的版本
  • 市面使用人數(shù)較多,資料較多
  • 其依賴的 Lucene 大版本是v6,較舊

v6.2.2

  • 是當(dāng)時(shí)穩(wěn)定的版本中最新的,性能比 v5 好

v6.2.4

  • 是當(dāng)時(shí)最新的版本,修復(fù)了許多 bug
  • 性能更好,是官方推薦的版本
  • 官方的技術(shù)文檔部分還沒(méi)更新,得看舊文檔
  • 市面上找不到相應(yīng)的人的使用資料

版本發(fā)展(于2019年4月

在寫本篇文章時(shí),我再去了解了和 Elasticsearch版本 相關(guān)的變更:

  • Elasticsearch穩(wěn)定版本中最新的是 v7.0、v6.7
  • 依賴的Lucene版本分別為 v8.0、v7.2
  • Spring 的穩(wěn)定支持程度為:v3.2.x 的 spring data elasticsearch 支持 v6.5.0 的 elasticsearch 版本,比最新版本低一些。

5. 導(dǎo)入已有數(shù)據(jù)

考慮到要使用 Elasticsearch 時(shí),通常意味著已經(jīng)有很多數(shù)據(jù)了。首次使用自然會(huì)有導(dǎo)入已有數(shù)據(jù)的過(guò)程,而且這些數(shù)據(jù)量都是很大的。
我們的方案是 JDBC 查詢并提交給 es。設(shè)計(jì)要點(diǎn)有:

  • 分批。數(shù)據(jù)量之大已經(jīng)無(wú)法一次存到內(nèi)存中。數(shù)據(jù)按明確的邊界劃分而獨(dú)立,會(huì)讓多線程處理、日志記錄、重試都變得輕松。按租戶來(lái)劃分就是一種好的方式。
  • 緩慢。避免影響線上的服務(wù),同時(shí)適當(dāng)給 JVM 回收和 Elasticsearch 處理留一點(diǎn)時(shí)間。
  • 異常。信息匯總和失敗重試。

具體設(shè)計(jì)細(xì)節(jié)如下:

  1. 為 SaaS 系統(tǒng)的每個(gè)租戶創(chuàng)建一個(gè)任務(wù),提交到ExecutorCompletionService。
  2. 在該租戶的任務(wù)中:
    一次查詢所有部門;
    分頁(yè)查詢所有人員、部門人員關(guān)聯(lián);
    一次查詢所有標(biāo)簽,標(biāo)簽對(duì)象關(guān)聯(lián);
  3. 關(guān)聯(lián)關(guān)系做成便于查詢的數(shù)據(jù)結(jié)構(gòu),以用于添加 es 文檔時(shí)的快速查詢。
    例如,映射<人員,部門>可用于查詢:人員所屬的部門;
    例如,映射<部門,標(biāo)簽>可用于查詢:部門所貼的標(biāo)簽;
    用到了 Guava 的 Multimap,以達(dá)到類似于 Map<String, Set<String>> 的效果。
  4. 建立新增 es 文檔的批量請(qǐng)求BulkRequest。對(duì)于每個(gè)對(duì)象,都可以用上一步做好的結(jié)構(gòu)快速獲取其關(guān)聯(lián)關(guān)系。
  5. 提交批量新增請(qǐng)求給 es。

6. 數(shù)據(jù)源同步

我們的 MySQL 數(shù)據(jù)同步到 Elasticsearch 的方案,是在應(yīng)用層基于事件通知進(jìn)行的。以人員對(duì)象為例,步驟如下:

  1. 人員的增刪查改事件,都會(huì)通知給其他訂閱者。這是已有的邏輯;
  2. 設(shè)計(jì)一個(gè)“記錄人員變動(dòng)”訂閱者,被通知時(shí),將變動(dòng)儲(chǔ)存起來(lái);
  3. 設(shè)計(jì)一個(gè)“Es同步”定時(shí)任務(wù),每天凌晨,取出變動(dòng)記錄,提交到 Es,之后刪除變動(dòng)記錄;

看到這個(gè)方案,你可能會(huì)問(wèn)為什么不使用像 Logstash 等成熟的框架或插件,而是自寫一套同步方法?原因如下:

  • 我們選擇的 MySQL 云服務(wù)提供商在當(dāng)時(shí)沒(méi)有提供 binlog 日志訪問(wèn)。這使我們無(wú)法選擇一些基于日志的同步方案。
  • 部門、人員、標(biāo)簽的數(shù)據(jù)表原本沒(méi)有像 update_time 這樣的——能反映變更的列。故又可以排除基于時(shí)間去增量同步的方案。
  • 人員、標(biāo)簽表的數(shù)據(jù)量很大,如果要增加一列 update_time 并加上索引,帶來(lái)的成本有:額外的儲(chǔ)存空間(我們購(gòu)買的云服務(wù)空間每增長(zhǎng)百G每年的成本大約是1000元);新字段給應(yīng)用層帶來(lái)的維護(hù)成本。
  • 設(shè)計(jì)出來(lái)的 Es 索引和 MySQL 表的字段不同。一些在 Es 索引中新增的字段,是需要在 MySQL 中做額外查詢才能得到的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容