前言
我們使用Elasticsearch索引文檔時,最理想的情況是文檔JSON結構是確定的,數據源源不斷地灌進來即可,但實際情況中,沒人能夠阻攔需求的變更,在項目的某個版本,可能會對原有的文檔結構造成衝擊,增加新的字段還好,如果要修改原有的字段,只能重建索引了。
概要
本篇以實戰方式講解如何零停機完成索引重建的三種方案。
外部數據導入方案
整體介紹
系統架構設計中,有關係型數據庫用來存儲數據,Elasticsearch在系統架構裏起到查詢加速的作用,如果遇到索引重建的操作,待系統模塊發佈新版本後(若重建索引不是因爲客戶端修改導致的,可以不停機發版,直接操作),可以從數據庫將數據查詢出來,重新灌到Elasticsearch即可。
執行步驟
建議的功能方案:數據庫 MQ 應用模塊 Elasticsearch,可以在MQ控制檯發送MQ消息來觸發重導數據,按批次對數據進行導入,整個過程異步化處理,請求操作示意如下所示:
詳細操作步驟:
- 通過MQ的web控制檯或cli命令行,發送指定的MQ消息
- MQ消息被微服務模塊的消費者消費,觸發ES數據重新導入功能
- 微服務模塊從數據庫裏查詢數據的總數及批次信息,並將每個數據批次的分頁信息重新發送給MQ消息,分頁信息包含查詢條件和偏移量,此MQ消息還是會被微服務的MQ消息者接收處理。
- 微服務根據接收的查詢條件和分頁信息,從數據庫獲取到數據後,根據索引結構的定義,將數據組裝成ES支持的JSON格式,並執行bulk命令,將數據發送給Elasticsearch集羣。
這樣就可以完成索引的重建工作。
方案特點
MQ中間件的選型不做具體要求,常見的rabitmq、activemq、rocketmq等均可。
在微服務模塊方面,提供MQ消息處理接口、數據處理模塊需要事先開發的,一般是創建新的索引時,配套把重建的功能也一起做好。整體功能共用一個topic,針對每個索引,有單獨的結構定義和MQ消息處理tag,代碼儘可能複用。處理的批次大小需要根據實際的情況設置。
微服務模塊實例會部署多個,數據是分批處理的,批次信息會一次性全部先發送給MQ,各個實例處理的數據相互不重疊,利用MQ消息的異步處理機制,可以充分利用併發的優勢,加快數據重建的速度。
方案缺點
- 對數據庫造成讀取壓力,短時間內大量的讀操作,會佔用數據庫的硬件資源,嚴重時可能引起數據庫性能下降。
- 網絡帶寬佔用多,數據畢竟是從一個庫傳到另一個庫,雖說是內網,但大量的數據傳輸帶寬佔用也需要注意。
- 數據重建時間稍長,跟遷移的數據量大小有關。
基於scroll bulk 索引別名方案
整體介紹
利用Elasticsearch自帶的一些工具完成索引的重建工具,當然在方案實際落地時,可能也會依賴客戶端的一些功能,比如用Java客戶端持續的做scroll查詢、bulk命令的封裝等,但與上一方案相比,最明顯的區別就是:數據完全自給自足,不依賴其他數據源。
執行步驟
假設原索引名稱是music,新的索引名稱爲musicnew,Java客戶端使用別名musicalias連接Elasticsearch,該別名指向原索引music。
- 若Java客戶端沒有使用別名,需要給客戶端分配一個:
PUT /music/_alias/music_alias
- 新建索引music_new,將mapping信息,settings信息等按新的要求全部定義好。
- 使用scroll api將數據批量查詢出來
GET /music/_search?scroll=1m
{
"query": {
"match_all": {}
},
"sort": ["_doc"],
"size": 1000
}
- 採用bulk api將scoll查出來的一批數據,批量寫入新索引
POST /_bulk
{ "index": { "_index": "music_new", "_type": "children", "_id": "1" }}
{ "name": "wake me, shake me" }
- 反覆執行步驟3和步驟4,查詢一批導入一批,可以藉助Java Client或其他語言的API支持。
- 切換別名musicalias到新的索引musicnew上面,此時Java客戶端仍然使用別名訪問,也不需要修改任何代碼,不需要停機。
POST /_aliases
{
"actions": [
{ "remove": { "index": "music", "alias": "music_alias" }},
{ "add": { "index": "music_new", "alias": "music_alias" }}
]
}
- 驗證別名查詢的是否爲新索引的數據
方案特點
在數據傳輸上基本自給自足,不依賴於其他數據源,Java客戶端不需要停機等待數據遷移,網絡傳輸佔用帶寬較小。
只是scroll查詢和bulk提交這部分,數據量大時需要依賴一些客戶端工具。
補充一點
在Java客戶端或其他客戶端訪問Elasticsearch集羣時,使用別名是一個好習慣。
Reindex API方案
Elasticsearch v6.3.1已經支持Reindex API,它對scroll、bulk做了一層封裝,能夠 對文檔重建索引而不需要任何插件或外部工具。
最基礎的命令:
POST _reindex
{
"source": {
"index": "music"
},
"dest": {
"index": "music_new"
}
}
響應結果:
{
"took": 180,
"timed_out": false,
"total": 4,
"updated": 0,
"created": 4,
"deleted": 0,
"batches": 1,
"version_conflicts": 0,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
},
"throttled_millis": 0,
"requests_per_second": -1,
"throttled_until_millis": 0,
"failures": []
}
注意:如果不手動創建新索引music_new的mapping信息,那麼Elasticsearch將啓動自動映射模板對數據進行類型映射,可能不是期望的類型,這點要注意一下。
version_type 屬性
使用reindex api也是創建快照後再執行遷移的,這樣目標索引的數據可能會與原索引有差異,version_type屬性可以決定樂觀鎖併發處理的規則。
reindex api可以設置version_type屬性,如下:
POST _reindex
{
"source": {
"index": "music"
},
"dest": {
"index": "music_new"
"version_type": "internal"
}
}
version_type屬性含義如下:
- internal:直接拷貝文檔到目標索引,對相同的type、文檔ID直接進行覆蓋,默認值
- external:遷移文檔到目標索引時,保留version信息,對目標索引中不存在的文檔進行創建,已存在的文檔按version進行更新,遵循樂觀鎖機制。
op_type 屬性和conflicts 屬性
如果op_type設置爲create,那麼遷移時只在目標索引中創建ID不存在的文檔,已存在的文檔,會提示錯誤,如下請求:
POST _reindex
{
"source": {
"index": "music"
},
"dest": {
"index": "music_new",
"op_type": "create"
}
}
有錯誤提示的響應,節選部分:
{
"took": 11,
"timed_out": false,
"total": 5,
"updated": 0,
"created": 1,
"deleted": 0,
"batches": 1,
"version_conflicts": 4,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
},
"throttled_millis": 0,
"requests_per_second": -1,
"throttled_until_millis": 0,
"failures": [
{
"index": "music_new",
"type": "children",
"id": "2",
"cause": {
"type": "version_conflict_engine_exception",
"reason": "[children][2]: version conflict, document already exists (current version [17])",
"index_uuid": "dODetUbATTaRL-p8DAEzdA",
"shard": "2",
"index": "music_new"
},
"status": 409
}
]
}
如果加上"conflicts": "proceed"配置項,那麼衝突信息將不展示,只展示衝突的文檔數量,請求和響應結果將變成這樣:
請求:
POST _reindex
{
"conflicts": "proceed",
"source": {
"index": "twitter"
},
"dest": {
"index": "new_twitter",
"op_type": "create"
}
}
響應:
{
"took": 12,
"timed_out": false,
"total": 5,
"updated": 0,
"created": 1,
"deleted": 0,
"batches": 1,
"version_conflicts": 4,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
},
"throttled_millis": 0,
"requests_per_second": -1,
"throttled_until_millis": 0,
"failures": []
}
query支持
reindex api支持數據過濾、數據排序、size設置、_source選擇等,也支持腳本執行,這裏提供一個簡單示例:
POST _reindex
{
"size": 100,
"source": {
"index": "music",
"query": {
"term": {
"language": "english"
}
},
"sort": {
"likes": "desc"
}
},
"dest": {
"index": "music_new"
}
}
小結
本篇介紹了零停機索引重建操作的三個方案,從自研功能、scroll bulk到reindex,我們作爲Elasticsearch的使用者,三個方案的參與度是逐漸弱化的,但穩定性卻是逐漸上升的,我們需要清楚地去了解各個方案的優劣,適宜的場景,然後根據實際的情況去權衡,哪個方案更適合我們的業務模型,僅供參考,謝謝。
專注Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公衆號:Java架構社區可以掃左邊二維碼添加好友,邀請你加入Java架構社區微信羣共同探討技術