如何平滑切換線上 Elasticsearch 索引

前言

哈嘍,大家好,我是asong,今天與大家聊一聊如何平滑切換線上的ES索引。使用過ES的朋友們都知道,修改索引真的是一件費時又費力的工作,所以我們應該在創建索引的時候就儘量設計好索引能夠滿足需求,當然這幾乎是不可能的,畢竟存在着萬惡的產品經理,所以掌握 " 平滑切換線上的ES索引 " 就很必要,接下來我們就來看一看如何實現!

前置條件

能夠平滑切換線上的ES索引需要有兩個先決條件,只有滿足了這兩個條件才能去執行接下來的平滑切換操作,否則一切操作都是白費。

前置條件之使用別名訪問索引

重建索引的問題是必須更新應用中的索引名稱,索引別名就是用來解決這個問題的。索引別名就像一個快捷方式或軟連接,可以指向一個或多個索引,也可以給任何一個需要索引名的 API 來使用。別名 帶給我們極大的靈活性,允許我們做下面這些:

索引與索引別名的關係,我們畫個圖來說一下:

上圖中user_index就是索引別名,user_index_v1user_index_v2user_index_v3分別是三個索引,這裏索引別名user_indexuser_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場景不同,所以平滑切換的步驟會稍有偏差,但是都離不開這幾個步驟:

  1. 創建新索引

  2. 同步數據 / 數據遷移到新索引

  3. 切換索引

先介紹一下數據遷移和切換索引使用什麼指令操作:

使用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"
  }
}'

介紹一下上面幾個字段的意義:

更多_redinx api使用方法可以移步官方文檔學習:https://www.elastic.co/guide/en/elasticsearch/reference/5.6/docs-reindex.html

上面只是舉一個簡單的例子,具體要在數據遷移中使用哪些參數需要根據場景而定。

什麼時候可以選擇數據遷移:

  1. 當我們新創建的索引只改變了mapping結構時,例如:刪除字段,更新字段的類型,這種場景就可以直接使用_reindex進行數據遷移;

  2. 新創建的索引中添加了新字段,但是新的字段都是由老的字段計算得到的,這種情況,也可以使用_reindex進行數據遷移,api中使用script參數,編寫你的腳本即可。

注意:使用_redindex接口時要注意一個問題,接口會在reindex結束後返回,接口超時控制只有30s,如果reindex時間過長,建議加上wait_for_completion=false參數,這樣redindex就變成異步任務,返回的是taskID,查看進度可以通過 _tasks API 進行查看。

ES中兩種方式管理別名:_alias用於單個操作,_aliases用於執行多個原子級操作。

因爲我們這裏要做的是切換索引,主要分爲兩個步驟:

  1. 移除當前索引與索引別名的關聯

  2. 將新建的索引與索引別名進行關聯

所以我們可以選擇_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中數據同步採用的消息隊列推送完成的,所以在切換索引時要考慮數據損失的問題。

這裏我們可以列舉幾種方案如下:

這裏總共列舉了 5 種方案,我也不推薦具體使用那個方案比較好,各有利弊,大家可以根據自己的業務場景來進行選擇。

這裏以選擇方案四爲例子,給出我的腳本數據,作爲樣例;

#!/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

#!/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