作者:王悦

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 不够”聪明“,可以把锅丢给中间件。当然实现中间件的成本和收益成正比,需要根据实际情况选择。

分类: 技术分享