mysql 分庫後怎麼通過ES數據異構查詢 多個庫的數據

前段時間與同事一起爲產品接入了 Elasticsearch 框架技術。從參與方案會議到搭建開發上線過程中有很多討論點,故產生本文,希望藉此總結和分享一些經驗。

1. 業務模型

接觸已有的業務時,數據模型是最早需要知道的信息。我和同事負責接入 Elasticsearch 的產品是一個業務繁多的通訊錄,簡化下來就是 3 個關鍵的模型,如下:

  • 部門(Department)
  • 人員(User)
  • 標籤(Tag)

它們的用途和聯繫,就跟它們的詞義一樣。
由此產生的業務如下:

  • 通過 標籤 查詢 部門人員
  • 通過 部門 查詢 人員

Elasticsearch 查詢和數據同步 - 記一次技術實踐

基於以上模型和業務,在典型的關係型數據庫下,爲了實現關聯關係,自然會有額外的關聯表

  • 部門人員關聯表:每條記錄包含1個部門,1個人員
  • 標籤對象關聯表:每條記錄包含1個標籤,1個部門或人員

2. 需求

Elasticsearch 的特點有全文檢索、分佈式、海量數據下近實時查詢
當時爲通訊錄業務引入 Elasticsearch 的需求和目標如下:

  • 多字段的匹配或模糊查詢。這些部門、人員、標籤數據原本存儲在 MySQL 中,如果要做匹配多個字段的模糊查詢就比較吃力了,考慮一個常用功能 “輸入姓名/手機號/拼音/首字母來查詢人員”。而快速查詢此類業務是 Elasticsearch 可以提供的。
  • 基礎模塊能力。其他業務模塊也提出了類似全文檢索的需求,因此在通訊錄業務首次應用 es 時,要定義和提供好 es 的訪問和工具方法,供其他模塊在未來接入時,能複用一些實現,能保持一致的接口和命名風格等。

3. 索引設計

從原 MySQL 數據庫表,到 Elasticsearch 的索引,數據模型的變化稱爲異構
Elasticsearch 適合解決在 MySQL 中多條件或連表這樣比較慢的查詢業務,因此除了原有的信息字段,我們會再附加 3 個模型的關聯關係到 es 索引中。

索引 字段 原有 關聯關係
部門 部門名、完整部門路徑名 (無)
人員 姓名、拼音、首字母、手機號 父部門Id、所有父級部門Id、標籤Id
標籤 標籤名 部門Id、人員Id

(上表略去了一些無關本篇內容的字段,如 SaaS 平臺的租戶Id、每個對象的信息詳情字段)

  • 是否需要添加關聯關係的字段,是由業務需求決定的。拿人員索引的 “所有父級部門Id” 舉例子,因爲有查詢部門下所有人員(包括直屬、子部門下的)的業務需求,所以會設計這麼一個字段。
  • 可以使用 Elasticsearch 的分詞功能來記錄關聯關係的字段中。爲該字段定義一個分隔模式爲豎線 “|” 的分詞器,把若干個關聯Id存成一個拼接的字符串。

4. 版本選擇

同事是個版本控,在選擇版本時瞭解和考慮了非常多的信息。不過版本選擇確實是爲平臺接入新技術時的一個重要考慮點。我們提出這個方案的當時(2018年4月),對比了主要使用的雲服務提供商的幾個版本,考慮項可以按優先級概括爲:

  1. 穩定的
  2. 案例資料多的
  3. 時新程度,包括 Elasticsearch版本 和 Lucene版本
  4. 我們已經使用了某家雲服務提供商,會偏向再用其提供的服務

幾個版本對比

我們當時選擇了 Elasticsearch 6.2.2 版本。

v5.6.4

  • 是 Spring 整合的各個框架中,支持數最多的版本
  • 市面使用人數較多,資料較多
  • 其依賴的 Lucene 大版本是v6,較舊

v6.2.2

  • 是當時穩定的版本中最新的,性能比 v5 好

v6.2.4

  • 是當時最新的版本,修復了許多 bug
  • 性能更好,是官方推薦的版本
  • 官方的技術文檔部分還沒更新,得看舊文檔
  • 市面上找不到相應的人的使用資料

版本發展(於2019年4月

在寫本篇文章時,我再去了解了和 Elasticsearch版本 相關的變更:

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

5. 導入已有數據

考慮到要使用 Elasticsearch 時,通常意味着已經有很多數據了。首次使用自然會有導入已有數據的過程,而且這些數據量都是很大的。
我們的方案是 JDBC 查詢並提交給 es。設計要點有:

  • 分批。數據量之大已經無法一次存到內存中。數據按明確的邊界劃分而獨立,會讓多線程處理、日誌記錄、重試都變得輕鬆。按租戶來劃分就是一種好的方式。
  • 緩慢。避免影響線上的服務,同時適當給 JVM 回收和 Elasticsearch 處理留一點時間。
  • 異常。信息彙總和失敗重試。

具體設計細節如下:

  1. 爲 SaaS 系統的每個租戶創建一個任務,提交到ExecutorCompletionService
  2. 在該租戶的任務中:
    一次查詢所有部門;
    分頁查詢所有人員、部門人員關聯;
    一次查詢所有標籤,標籤對象關聯;
  3. 關聯關係做成便於查詢的數據結構,以用於添加 es 文檔時的快速查詢。
    例如,映射<人員,部門>可用於查詢:人員所屬的部門;
    例如,映射<部門,標籤>可用於查詢:部門所貼的標籤;
    用到了 Guava 的 Multimap,以達到類似於 Map<String, Set<String>> 的效果。
  4. 建立新增 es 文檔的批量請求BulkRequest。對於每個對象,都可以用上一步做好的結構快速獲取其關聯關係。
  5. 提交批量新增請求給 es。

6. 數據源同步

我們的 MySQL 數據同步到 Elasticsearch 的方案,是在應用層基於事件通知進行的。以人員對象爲例,步驟如下:

  1. 人員的增刪查改事件,都會通知給其他訂閱者。這是已有的邏輯;
  2. 設計一個“記錄人員變動”訂閱者,被通知時,將變動儲存起來;
  3. 設計一個“Es同步”定時任務,每天凌晨,取出變動記錄,提交到 Es,之後刪除變動記錄;

看到這個方案,你可能會問爲什麼不使用像 Logstash 等成熟的框架或插件,而是自寫一套同步方法?原因如下:

  • 我們選擇的 MySQL 雲服務提供商在當時沒有提供 binlog 日誌訪問。這使我們無法選擇一些基於日誌的同步方案。
  • 部門、人員、標籤的數據表原本沒有像 update_time 這樣的——能反映變更的列。故又可以排除基於時間去增量同步的方案。
  • 人員、標籤表的數據量很大,如果要增加一列 update_time 並加上索引,帶來的成本有:額外的儲存空間(我們購買的雲服務空間每增長百G每年的成本大約是1000元);新字段給應用層帶來的維護成本
  • 設計出來的 Es 索引和 MySQL 表的字段不同。一些在 Es 索引中新增的字段,是需要在 MySQL 中做額外查詢才能得到的。

 

發佈了202 篇原創文章 · 獲贊 88 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章