如何平滑切換線上 Elasticsearch 索引
前言
哈嘍,大家好,我是
asong
,今天與大家聊一聊如何平滑切換線上的ES
索引。使用過ES
的朋友們都知道,修改索引真的是一件費時又費力的工作,所以我們應該在創建索引的時候就儘量設計好索引能夠滿足需求,當然這幾乎是不可能的,畢竟存在着萬惡的產品經理,所以掌握 " 平滑切換線上的ES
索引 " 就很必要,接下來我們就來看一看如何實現!
前置條件
能夠平滑切換線上的ES
索引需要有兩個先決條件,只有滿足了這兩個條件才能去執行接下來的平滑切換操作,否則一切操作都是白費。
前置條件之使用別名訪問索引
重建索引的問題是必須更新應用中的索引名稱,索引別名就是用來解決這個問題的。索引別名就像一個快捷方式或軟連接,可以指向一個或多個索引,也可以給任何一個需要索引名的 API 來使用。別名 帶給我們極大的靈活性,允許我們做下面這些:
-
在運行的集羣中可以無縫的從一個索引切換到另一個索引
-
給多個索引分組
-
給索引的一個子集創建
視圖
索引與索引別名的關係,我們畫個圖來說一下:
上圖中user_index
就是索引別名,user_index_v1
、user_index_v2
、user_index_v3
分別是三個索引,這裏索引別名user_index
與user_index_v1
進行了關聯,所以我們搜索的時候使用索引別名,也就是去索引user_index_v1
上查詢。假設現在我們不想使用索引user_index_v1
了,想使用索引user_index_v2
,那麼直接使用_aliases
操作執行原子操作 (後面介紹具體使用),將索引別名user_index
與索引user_index_v2
進行關聯,現在使用索引別名user_index
搜索的就是索引user_index_v2
的數據了。
前置條件之足夠空間
既然要重建ES
索引,就一定保證你有足夠的空間存儲數據,可以使用如下指令查看ES
每個節點的可用磁盤空間:
curl http://localhost:9200/_cat/allocation\?v
獲得結果如下:
如何平滑切換
因爲大家使用的ES
場景不同,所以平滑切換的步驟會稍有偏差,但是都離不開這幾個步驟:
-
創建新索引
-
同步數據 / 數據遷移到新索引
-
切換索引
先介紹一下數據遷移和切換索引使用什麼指令操作:
- 數據遷移
使用ES
中提供的reindex api
就可以將數據copy
到新索引中,比如:
curl --location --request POST 'http://localhost:9200/_reindex' \
--header 'Content-Type: application/json' \
--data-raw '{
"conflicts": "proceed",
"source": {
"index": "user_index_v1"
},
"dest": {
"index": "user_index_v2",
"op_type": "create",
"version_type": "external"
}
}'
介紹一下上面幾個字段的意義:
-
"source":{"index": "user_index_v1"}
:這裏代表我們要遷移數據的源索引; -
"dest":{"index": "user_index_v2"}
:這裏代表我們要遷移的目標索引; -
"conflicts": "proceed"
:默認情況下,版本衝突會導致_reindex
操作終止,可以設置這個字段使該請求遇到衝突時不會終止,而是統計衝突數量; -
"version_type": "extrenal"
:這個字段介紹起來比較複雜,且聽我細細道來。_reindex
指令會生成源索引的快照,它的目標索引必須是一個不同的索引 [新索引],以便避免版本衝突。如果不設置version_type
字段,默認爲internal
,ES
會直接將文檔轉存儲到目標索引中 (dest index
),直接覆蓋任何具有相同類型和id
的document
,不會產生版本衝突。如果把version_type
設置爲extertral
,那麼ES
會從源索引 (source index
) 中讀取version
字段,當遇到具有相同類型和id
的document
時,只會保留new version
,即最新的version
對應的數據。此時可能會有衝突產生,比如當把op_tpye
設置爲create
,對於產生的衝突現象,返回體中的failures
會攜帶衝突的數據信息【類似詳細的日誌可以查看】。 -
op_type
:op_type
參數控制着寫入數據的衝突處理方式,如果把op_type
設置爲create
【默認值】,在_reindex API
中,表示寫入時只在dest index
中添加不存在的doucment
,如果相同的document
已經存在,則會報version confilct
的錯誤,那麼索引操作就會失敗。【這種方式與使用_create API
時效果一致】。
更多_redinx api
使用方法可以移步官方文檔學習:https://www.elastic.co/guide/en/elasticsearch/reference/5.6/docs-reindex.html
上面只是舉一個簡單的例子,具體要在數據遷移中使用哪些參數需要根據場景而定。
什麼時候可以選擇數據遷移:
-
當我們新創建的索引只改變了
mapping
結構時,例如:刪除字段,更新字段的類型,這種場景就可以直接使用_reindex
進行數據遷移; -
新創建的索引中添加了新字段,但是新的字段都是由老的字段計算得到的,這種情況,也可以使用
_reindex
進行數據遷移,api
中使用script
參數,編寫你的腳本即可。
注意:使用_redindex
接口時要注意一個問題,接口會在reindex
結束後返回,接口超時控制只有30s
,如果reindex
時間過長,建議加上wait_for_completion=false
參數,這樣redindex
就變成異步任務,返回的是taskID
,查看進度可以通過 _tasks API
進行查看。
- 切換索引
ES
中兩種方式管理別名:_alias
用於單個操作,_aliases
用於執行多個原子級操作。
因爲我們這裏要做的是切換索引,主要分爲兩個步驟:
-
移除當前索引與索引別名的關聯
-
將新建的索引與索引別名進行關聯
所以我們可以選擇_alisases
執行原子操作:
curl --location --request POST 'http://localhost:9200/_aliases' \
--header 'Content-Type: application/json' \
--data-raw '{
"actions": [
{"remove": {"index": "user_index_v2", "alias": "user_index"}},
{ "add": {"index": "user_index_v1", "alias": "user_index"}}
]
}'
舉例子
假設我們有一個user_index_v1
,他的mapping
結構如下;
{
"mappings":{
"properties":{
"id":{
"type":"byte"
},
"Name":{
"type":"text"
},
"Age":{
"type":"byte"
}
}
}
}
現在這個v1
索引中,我們的id
字段使用的byte
類型,顯然範圍是比較小的,隨着數據量增多,id
數值的增大,該字段已經不能滿足存儲需求了,所以需要把它換成long
類型,因此可以創建v2
索引:
{
"mappings":{
"properties":{
"id":{
"type":"long"
},
"Name":{
"type":"text"
},
"Age":{
"type":"byte"
}
}
}
}
現在我們就來考慮一下,如何平滑的進行索引切換。這裏假設我們ES
中數據同步採用的消息隊列
推送完成的,所以在切換索引時要考慮數據損失的問題。
這裏我們可以列舉幾種方案如下:
-
方案一:直接創建
v2
索引,使用_aliases
切換索引,進行數據遷移,優點是直接切換別名和索引的關聯,簡單方便,缺點是出現問題回退到舊索引,會有數據損失,直接切換到v2
索引會導致服務在數據沒有遷移完之前不可用。 -
方案二:創建
v2
索引,添加v2
索引與別名的關聯,進行數據遷移,_alias
操作解除別名和v2
索引的關聯。優點是不會造成服務不可用,缺點是在解除別名和v1
關聯之前,一個別名關聯兩個索引,單索引操作無法執行,只能搜索,搜索也會出現數據重複,並且也會造成數據損失。 -
方案三:創建
v2
索引,添加v2
索引與別名的關聯,修改代碼寫入操作使用v2
索引,搜索操作使用別名索引,進行數據遷移,解除v1
索引與別名的關聯,優點是搜索和寫入操作分開了,缺點是回退需要修改代碼,並且會出現數據損失,如果v2
索引不可用了,不能立刻回退索引。 -
方案四:創建
v2
索引,進行數據遷移,然後切換索引;優點是同步數據到 v2 期間搜索功能正常使用,回退無數據損失;缺點是會造成數據丟失。 -
方案五:創建
v2
索引,添加兩個別名索引read
和write
,添加別名read
和v1
索引、v2
索引的關聯,添加別名write
和v2
索引的關聯,進行數據遷移,解除別名read
和v1
索引的關聯;優點是搜索和寫入分開了,更新索引時只需要創建新索引,數據同步完成後,解除別名read
和舊索引關聯即可;缺點是數據遷移完成之前,搜索結果會出現重複,回退到舊索引,會有數據損失。
這裏總共列舉了 5 種方案,我也不推薦具體使用那個方案比較好,各有利弊,大家可以根據自己的業務場景來進行選擇。
這裏以選擇方案四爲例子,給出我的腳本數據,作爲樣例;
- 創建
user_index_v2
索引:
#!/bin/bash
url=$1
index=$2
echo `curl --location --request GET ${url}/${index}`
echo `curl --location --request PUT ${url}/${index} \
--header 'Content-Type: application/json' \
--data-raw '{
"mappings":{
"properties":{
"id":{
"type":"long"
},
"Name":{
"type":"text"
},
"Age":{
"type":"byte"
}
}
}
}'`
echo `curl --location --request GET ${url}/${index}`
運行指令:./create_index.sh http://localhost:9200 user_index_v2
- 進行數據遷移 (數據量比較大時建議分批
and
異步處理)
#!/bin/bash
url=$1
echo `curl --location --request POST ${url}/'_reindex?wait_for_completion=false' \
--header 'Content-Type: application/json' \
--data-raw '{
"conflicts": "proceed",
"source": {
"index": "user_index_v1"
},
"dest": {
"index": "user_index_v2",
"op_type": "create",
"version_type": "external"
}
}'`
運行指令:./reindex.sh http://localhost:9200
- 切換索引
#!/bin/bash
url=$1
aliasIndex=$2
oldIndex=$3
newIndex=$4
echo `curl --location --request GET ${url}/${aliasIndex}`
echo `curl --location --request POST ${url}/_aliases --header 'Content-Type: application/json' --data-raw '{"actions": [{"remove": {"index": "'$oldIndex'", "alias": "'$aliasIndex'"}},{ "add": {"index": "'$newIndex'", "alias": "'$aliasIndex'"}}]}'`
echo `curl --location --request GET ${url}/${aliasIndex}`
運行指令:./aliases.sh http://localhost:9200 user_index user_index_v1 user_index_v2
總結
本文例舉了幾種平滑切換ES
索引的方案,可以看出修改索引真不是一件容易的事情,要考慮的事情比較多,所以最好在第一次創建索引的時候就多考慮一下以後的使用場景,確定好字段和類型,這樣就可以避免重建ES
索引。當然隨着產品的需求變更,重建ES
索引也是不可避免的,上面幾種僅供大家參考,根據自己的場景去選擇就好啦。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/8VQxK_Xh-bkVoOdMZs4Ujw