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

前言

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

1. 業務模型

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

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

它們的用途和聯繫,就跟它們的詞義同樣。
由此產生的業務以下:數據結構

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

3個模型的簡化聯繫.png

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

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

2. 需求

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

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

3. 索引設計

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

索引 字段 原有 關聯關係
部門 部門名、完整部門路徑名 (無)
人員 姓名、拼音、首字母、手機號 父部門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版本 相關的變動:spa

  • 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 中作額外查詢才能獲得的。
相關文章
相關標籤/搜索