作者:王悦
Copyright (C) 2021 wingYue
本文来源:原创投稿
*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
redis 作为一个高性能的内存数据库被广泛应用于各类系统中,比如排行榜、评分服务等等。我们在选择 redis 时除了考虑其提供的数据类型和性能是否满足业务需求之外,非常重要的一点是考虑高可用性。redis 提供了 redis sentinel 来完成高可用机制,sentinel 会监控 redis 主从实例,提供自动故障切换功能。
但是随之而来一个问题是 client 在故障切换后如何得知当前的 master 实例地址?
主从架构方法一: 使用 redis sentinel 的服务发现
redis sentinel 提供了一个服务发现机制,连接 sentinel 执行“SENTINEL get-master-addr-by-name”,会返回当前 master 实例地址,故障切换后,返回结果也会更新为新的master实例地址:
有很多“聪明”的 redis client 库都实现了基于该服务发现机制的自动连接,只要将 sentinel 的地址列表传入,clinet 会通过 sentinel 获取当前最新的 master 地址,然后使用获取的地址连接。比如 jedis:
sentinels.add(new HostAndPort("192.168.0.31",26379).toString());
sentinels.add(new HostAndPort("192.168.0.32",26379).toString());
sentinels.add(new HostAndPort("192.168.0.33",26379).toString());
pool = new JedisSentinelPool(masterName, sentinels, config, TIMEOUT,password);
...
// 获取连接:
Jedis jedis = pool.getResource();
try {
jedis.set("hello", "jedis");
} finally {
jedis.close();
}
jedis 作为一个优秀的 redis 客户端,其使用的订阅 sentinel的方式是时刻在内存中维护最新的 master 地址。而一些其他的 client 则是在每次获取连接时,先询问 sentinel 最新 master 地址,然后再执行 redis 连接,这样每次操作都需要发送两次请求,并不是非常高效。另一种方式是使用 VIP :
主从架构方法二:绑定 VIP
我们维护一个 VIP ,使其始终绑定在 master 节点上,这样 client 连接时就可以无脑地连接 VIP 地址。VIP 的维护可以通过 sentinel 的 client-reconfig-scrip t脚本实现,每次 sentinel 监控的主从实例发生故障切换后,sentinel 都会调用该脚本并传入最新的 master 地址,我们可以在脚本内实现VIP的绑定和解绑操作。
绑定 VIP 不依赖于 client 的“聪明”,通过自定义脚本实现,比较灵活可控,但是 VIP 对于一些外网访问场景无法支持。当然由于脚本是自定义的,比如有 DNS 系统,则可以将绑定 VIP 换成绑定 DNS ,去提供外网的访问能力。
主从架构方法三: 使用keepalived VRRP
方法三与方法二类似,都是通过 VIP 提供服务入口,方法三使用 keepalived 的 VRRP 来实现VIP绑定,不依赖于 sentinel 的 reconfig 脚本。
主从架构方法四:中间件代理
一些公有云的 redis 服务都提供了一个 Proxy 地址用于 client 的访问,该地址后面实际就对应了一个中间件代理。实现一个中间件除了需要开发成本,还需要在运行时维护中间件本身的高可用,当然花了成本就会带来收益。中间件除了实现基础的连接转发以外,还能提供更多的高阶功能,比如阿里云的 redis Proxy 提供了:
1、proxy 将写命令发送到 master 节点,将读命令根据权重发送到 master 或 slave 节点
2、proxy 会下线异常的只读节点,待节点恢复后再重新启用
以上我们是针对 redis 主从架构,讨论了故障切换后 client 如何能够连接上正确的 master 节点。
下面我们针对 redis 集群架构下,讨论 client 如何正确连接上本次操作涉及的 slot 需要访问的节点?
集群架构方法一:请求重定向
重定向指的是 client 会随机挑选一个 redis 实例进行请求操作,redis 实例收到请求后会计算 key 对应的 slot ,如果在本地则直接处理,否则会返回一个 MOVED 给 client ,指明正确的实例地址,让 client 进行重定向。比如 redis-cli 工具就是使用的重定向方式:
redis-cli 在收到 MOVED 响应后会自动重定向到指定的地址,这样做的一个问题是可能一次操作会需要两次请求,比较影响性能。
集群架构方法二:本地缓存 slots 列表
“聪明”的 client (比如 JedisCluster)会在启动时通过访问集群中任意节点,获取 slots 信息,在本地缓存一份所有 slots 所对应的节点列表。这样当 slots 没有发生迁移时,能够保证操作请求被直接发送到正确的节点上。而当集群中发生了 slots 迁移之后,某些请求会返回错误,此时 client 会重新更新缓存中的 slots 列表,然后再次请求。由于 slots 迁移并不是一个高频操作,这样的做法对总体性能影响不大。
集群架构方法三:中间件代理
万能的中间件,和主从架构类似,这里就不详细讨论了,如果 client 不够”聪明“,可以把锅丢给中间件。当然实现中间件的成本和收益成正比,需要根据实际情况选择。