[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 服务端系统研发,后面会大力经过微信公众号发送优质文章"】