Redis Cluster 如何解决数据量大、高可用和高并发问题?
Redis 从 3.0 版本开始,提供了官方的集群支持,也就是 Redis Cluser。Redis Cluster 相比于单个节点的 Redis,能保存更多的数据,支持更多的并发,并且可以做到高可用,在单个节点故障的情况下,继续提供服务。
为了能够保存更多的数据,和 MySQL 分库分表的方式类似,Redis Cluster 也是通过分片的方式,把数据分布到集群的多个节点上。
Redis Cluster 是如何来分片的呢?它引入了一个“槽(Slot)”的概念,这个槽就是哈希表中的哈希槽,槽是 Redis 分片的基本单位,每个槽里面包含一些 Key。每个集群的槽数是固定的 16384(16 * 1024)个,每个 Key 落在哪个槽中也是固定的,计算方法是:
代码语言:javascript复制HASH_SLOT = CRC16(key) mod 16384
这个算法很简单,先计算 Key 的 CRC 值,然后把这个 CRC 之后的 Key 值直接除以 16384,余数就是 Key 所在的槽。这个算法就是我们上节课讲过的哈希分片算法。
这些槽又是如何存放到具体的 Redis 节点上的呢?这个映射关系保存在集群的每个 Redis 节点上,集群初始化的时候,Redis 会自动平均分配这 16384 个槽,也可以通过命令来调整。这个分槽的方法,也是我们上节课讲到过的分片算法:查表法
客户端可以连接集群的任意一个节点来访问集群的数据,当客户端请求一个 Key 的时候,被请求的那个 Redis 实例先通过上面的公式,计算出这个 Key 在哪个槽中,然后再查询槽和节点的映射关系,找到数据所在的真正节点,如果这个节点正好是自己,那就直接执行命令返回结果。如果数据不在当前这个节点上,那就给客户端返回一个重定向的命令,告诉客户端,应该去连哪个节点上请求这个 Key 的数据。然后客户端会再连接正确的节点来访问。
解决分片问题之后,Redis Cluster 就可以通过水平扩容来增加集群的存储容量,但是,每次往集群增加节点的时候,需要从集群的那些老节点中,搬运一些槽到新节点,你可以手动指定哪些槽迁移到新节点上
分片可以解决 Redis 保存海量数据的问题,并且客观上提升了 Redis 的并发能力和查询性能。但是并不能解决高可用的问题,每个节点都保存了整个集群数据的一个子集,任何一个节点宕机,都会导致这个宕机节点上的那部分数据无法访问
那 Redis Cluster 是怎么解决高可用问题的?
增加从节点,做主从复制。Redis Cluster 支持给每个分片增加一个或多个从节点,每个从节点在连接到主节点上之后,会先给主节点发送一个 SYNC 命令,请求一次全量复制,也就是把主节点上全部的数据都复制到从节点上。全量复制完成之后,进入同步阶段,主节点会把刚刚全量复制期间收到的命令,以及后续收到的命令持续地转发给从节点。
最后我们看一下,Redis Cluster 是如何应对高并发的
一般来说,Redis Cluster 进行了分片之后,每个分片都会承接一部分并发的请求,加上 Redis 本身单节点的性能就非常高,所以大部分情况下不需要再像 MySQL 那样做读写分离来解决高并发的问题。默认情况下,集群的读写请求都是由主节点负责的,从节点只是起一个热备的作用。当然了,Redis Cluster 也支持读写分离,在从节点上读取数据。
以上就是 Redis Cluster 的基本原理,你可以对照下图来加深理解
Redis Cluster 整体的架构完全就是照抄 MySQL 构建集群的那一套东西(当然,这些设计和方法也不是 MySQL 发明的),抄作业抄的就差把名字一起也抄上了
为什么 Redis Cluster 不适合超大规模集群?
Redis Cluster 的优点是易于使用。分片、主从复制、弹性扩容这些功能都可以做到自动化,通过简单的部署就可以获得一个大容量、高可靠、高可用的 Redis 集群,并且对于应用来说,近乎于是透明的
所以,Redis Cluster 是非常适合构建中小规模 Redis 集群,这里的中小规模指的是,大概几个到几十个节点这样规模的 Redis 集群
但是 Redis Cluster 不太适合构建超大规模集群,主要原因是,它采用了去中心化的设计。刚刚我们讲了,Redis 的每个节点上,都保存了所有槽和节点的映射关系表,客户端可以访问任意一个节点,再通过重定向命令,找到数据所在的那个节点。那你有没有想过一个问题,这个映射关系表,它是如何更新的呢?比如说,集群加入了新节点,或者某个主节点宕机了,新的主节点被选举出来,这些情况下,都需要更新集群每一个节点上的映射关系表。
如何用 Redis 构建超大规模集群?
- Redis Cluster 不太适合用于大规模集群,所以很多大厂,都选择自己去搭建 Redis 集群。这里面,每一家的解决方案都有自己的特色,但其实总体的架构都是大同小异的。
- 一种是基于代理的方式,在客户端和 Redis 节点之间,还需要增加一层代理服务。这个代理服务有三个作用。
- 第一个作用是,负责在客户端和 Redis 节点之间转发请求和响应。客户端只和代理服务打交道,代理收到客户端的请求之后,再转发到对应的 Redis 节点上,节点返回的响应再经由代理转发返回给客户端。
- 第二个作用是,负责监控集群中所有 Redis 节点状态,如果发现有问题节点,及时进行主从切换。第三个作用就是维护集群的元数据,这个元数据主要就是集群所有节点的主从信息,以及槽和节点关系映射表
用 HAProxy Keepalived 来代理 MySQL 请求的架构是类似的,只是多了一个自动路由分片的功能而已
当然,客户端不用每次都去查询元数据,因为这个元数据是不怎么变化的,客户端可以自己缓存元数据,这样访问性能基本上和单机版的 Redis 是一样的。如果某个分片的主节点宕机了,新的主节点被选举出来之后,更新元数据里面的信息。对集群的扩容操作也比较简单,除了迁移数据的工作必须要做以外,更新一下元数据就可以了
虽然说,这个元数据服务仍然是一个单点,但是它的数据量不大,访问量也不大,相对就比较容易实现。我们可以用 ZooKeeper、etcd 甚至 MySQL 都能满足要求。这个方案应该是最适合超大规模 Redis 集群的方案了,在性能、弹性、高可用几方面表现都非常好,缺点是整个架构比较复杂,客户端不能通用,需要开发定制化的 Redis 客户端,只有规模足够大的企业才负担得起
小结
从小到大三种构建 Redis 集群的方式
- 小规模的集群建议使用官方的 Redis Cluster,在节点数量不多的情况下,各方面表现都不错。
- 再大一些规模的集群,可以考虑使用 twemproxy 或者 Codis 这类的基于代理的集群架构,虽然是开源方案,但是已经被很多公司在生产环境中验证过。
- 相比于代理方案,使用定制客户端的方案性能更好,很多大厂采用的都是类似的架构