前言
前段時間與同事一塊兒爲產品接入了 Elasticsearch 框架技術。從參與方案會議到搭建開發上線過程當中有不少討論點,故產生本文,但願藉此總結和分享一些經驗。spring
1. 業務模型
接觸已有的業務時,數據模型是最先須要知道的信息。我和同事負責接入 Elasticsearch 的產品是一個業務繁多的通信錄,簡化下來就是 3 個關鍵的模型,以下:數據庫
- 部門(Department)
- 人員(User)
- 標籤(Tag)
它們的用途和聯繫,就跟它們的詞義同樣。
由此產生的業務以下:數據結構
- 經過 標籤 查詢 部門、人員
- 經過 部門 查詢 人員
基於以上模型和業務,在典型的關係型數據庫下,爲了實現關聯關係,天然會有額外的關聯表:多線程
- 部門人員關聯表:每條記錄包含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月),對比了主要使用的雲服務提供商的幾個版本,考慮項能夠按優先級歸納爲:工具
- 穩定的
- 案例資料多的
- 時新程度,包括 Elasticsearch版本 和 Lucene版本
- 咱們已經使用了某家雲服務提供商,會偏向再用其提供的服務
幾個版本對比
咱們當時選擇了 Elasticsearch 6.2.2 版本。性能
v5.6.4
- 是 Spring 整合的各個框架中,支持數最多的版本
- 市面使用人數較多,資料較多
- 其依賴的 Lucene 大版本是v6,較舊
v6.2.2
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 處理留一點時間。
- 異常。信息彙總和失敗重試。
具體設計細節以下:
- 爲 SaaS 系統的每一個租戶建立一個任務,提交到
ExecutorCompletionService
。
- 在該租戶的任務中:
一次查詢全部部門;
分頁查詢全部人員、部門人員關聯;
一次查詢全部標籤,標籤對象關聯;
- 將關聯關係作成便於查詢的數據結構,以用於添加 es 文檔時的快速查詢。
例如,映射<人員,部門>可用於查詢:人員所屬的部門;
例如,映射<部門,標籤>可用於查詢:部門所貼的標籤;
用到了 Guava 的 Multimap
,以達到相似於 Map<String, Set<String>> 的效果。
- 創建新增 es 文檔的批量請求
BulkRequest
。對於每一個對象,均可以用上一步作好的結構快速獲取其關聯關係。
- 提交批量新增請求給 es。
6. 數據源同步
咱們的 MySQL 數據同步到 Elasticsearch 的方案,是在應用層基於事件通知進行的。以人員對象爲例,步驟以下:
- 人員的增刪查改事件,都會通知給其餘訂閱者。這是已有的邏輯;
- 設計一個「記錄人員變更」訂閱者,被通知時,將變更儲存起來;
- 設計一個「Es同步」定時任務,天天凌晨,取出變更記錄,提交到 Es,以後刪除變更記錄;
看到這個方案,你可能會問爲何不使用像 Logstash 等成熟的框架或插件,而是自寫一套同步方法?緣由以下:
- 咱們選擇的 MySQL 雲服務提供商在當時沒有提供 binlog 日誌訪問。這使咱們沒法選擇一些基於日誌的同步方案。
- 部門、人員、標籤的數據表本來沒有像 update_time 這樣的——能反映變動的列。故又能夠排除基於時間去增量同步的方案。
- 人員、標籤表的數據量很大,若是要增長一列 update_time 並加上索引,帶來的成本有:額外的儲存空間(咱們購買的雲服務空間每增加百G每一年的成本大約是1000元);新字段給應用層帶來的維護成本。
- 設計出來的 Es 索引和 MySQL 表的字段不一樣。一些在 Es 索引中新增的字段,是須要在 MySQL 中作額外查詢才能獲得的。