[TOC]redis
對於redis集羣而言,通常業務方使用的時候,會在服務端對key作hash策略,hash算法通常能夠分爲:一致性hash、hash取模等,固然還有其餘經常使用算法。一致性hash在擴縮容的時候比較麻煩,所以公司層面要求都要使用hash取模,然而,若是當前線上已是一致性hash,那麼要更改hash算法爲hash取模,那麼咱們該如何作?算法
咱們的解決方案要可以平滑過渡,不能影響業務正常運行,所以,咱們能夠經過雙寫策略來實現,正如我前面的文章《線上redis遷移思路》裏面說的同樣,雙寫是萬能的。 基於此,咱們能夠經過先雙寫,再去掉一致性hash的方案來解決bash
舉例說明,假如目前是2個一致性hash節點(實例),那麼要調整爲2個取模方式節點的步驟大體以下微信
業務上雙寫一致性hash的2個節點和取模2個節點,此時,取模節點裏面的數據是新寫的數據,只寫不讀併發
經過寫遷移工具,掃描全部一致性hash的節點的列表(key列表),從一致性hash節點get數據,而後set到取模節點。這種狀況理論上會出現瞬間的併發問題(好比get後有新數據,最終set進去老數據,不過只是在瞬間會產生),不過不要緊,即使有髒數據(數據不一致),也會再下一步的check工具裏面處理好。app
數據驗證和check工具修復異步
業務切換讀到新的取模節點async
以下代碼來源於閃聊項目,也是閃聊實際經歷過切換方案工具
配置裏面, 針對須要進行調整的redis實例,增長新的redis實例配置(取模相關),以下ui
[redis.gunread_new]
shard = "compat"
servers = ["192.168.xxx.xxx:6380;;1", "192.168.xxx.xxx:6381;;1"]
複製代碼
setupRedis裏面增長新的redis實例配置
// 遍歷全部redis pool也就是全部redis類別實例
for _, name := range conf.RedisPoolNames {
func(instance string) {
// 原有的redis實例
clusterConfig.Configs = conf.Redis[instance]
if len(clusterConfig.Configs) == 0 {
logger.Errorf(nil, "get redis config for %s failed", instance)
return
}
currentCluster := newRedisCluster(instance, clusterConfig)
// 同時加載新的redis實例,並經過SetDualWrite賦值給dualWrite.
dualInstance := instance + "_new"
clusterConfig.Configs = conf.Redis[dualInstance]
if len(clusterConfig.Configs) > 0 {
dualWriteCluster := newRedisCluster(dualInstance, clusterConfig)
currentCluster.SetDualWrite(dualWriteCluster)
logger.Infof(nil, "set redis dual write to %v", instance)
}
redisClusterMap[instance] = currentCluster
}(name)
}
複製代碼
增長開關控制,默認打開雙寫開關 這點須要重點說明一下,在實際工程應用中,咱們的項目可能有部分功能須要再某個版本啓用,某個版本棄用;或者某個新增的功能,爲了防止異常須要可以有個開關配置,隨時能夠開啓這個功能或者關閉這個功能;或者在流量高峯,咱們須要關閉掉或者降級某個功能。諸如這類型的需求,一個比較推薦的作法就是增長開關配置,全局的開關,抽象出一個開關模型出來。
如:
type Switch struct {
Name string
On bool
listeners []ChangeListener
}
func (s *Switch) TurnOn() {
s.On = true
s.notifyListeners()
}
func (s *Switch) TurnOff() {
s.On = false
s.notifyListeners()
}
var AsyncProcedure = &Switch{Name: "demo.msg.procedure.async", On: true}
當咱們打開開關的時候執行
if switches.AsyncProcedure.IsOn() {
}
複製代碼
client操做的時候redis實例的時候,如寫數據的時候,對每個操做都進行雙寫處理
func (r *Cluster) ZAdd(key string, scoremembers ...interface{}) (int, error) {
if len(scoremembers)%2 != 0 {
return 0, fmt.Errorf("zadd for %v expects even number of score members", key)
}
// 若是雙寫開關打開,而且有雙寫的實例,就異步寫這個新的實例
if r.dualWrite != nil && r.writeDual {
go r.dualWrite.ZAdd(key, scoremembers...)
}
args := append([]interface{}{key}, scoremembers...)
return redis.Int(r.doWrite(r.getClient(key), "ZADD", args...))
}
複製代碼
這樣以後就開始了雙寫,而後須要作的就是check數據
作一個check工具
這個要分爲兩步走,首先,同步老的數據到新的集羣裏面;同步完以前,要 經過check 工具校驗全部數據是否相等,並進行相關補償調整
全部這些步驟搞定後,當check完數據後,咱們就能夠再在配置裏面去掉老一致性hash的配置,只保留新的hash取模的配置
如
[redis.gunread] // 把原有的配置的server地址換爲_new的地址
shard = "compat"
servers = ["192.168.xxx.xxx:6378;;1", "192.168.xxx.xxx:6379;;1"]
[redis.gunread_new] // 去掉這個_new的配置
shard = "compat"
servers = ["192.168.xxx.xxx:6380;;1", "192.168.xxx.xxx:6381;;1"]
複製代碼
【"歡迎關注個人微信公衆號:Linux 服務端系統研發,後面會大力經過微信公衆號發送優質文章"】