随着业务规模扩展、用户量膨胀、并发量持续提升,原有的主从架构,已经远远达不到需求了,会有一些问题出现,如:
- 单机(单个主节点)的 CPU、内存、连接数、计算力都是有极限的,不能无限制的承载流量的扩增
- 超额的请求、大规模的数据计算,导致必然的慢响应
单机的吞吐无法承受持续扩增的流量时,最好的办法是从横向(scale out)和纵向(scale up)两方面进行扩展:
- 纵向扩展(scale up):提升单个实例的硬件资源,如 CPU、内存容量、SSD 容量等
- 优点:操作起来比较简易
- 缺点:
- 还是没法解决一些瓶颈问题,如持久化(AOF、RDB),遇到大数据量时,照样效率很低,响应慢
- 单台服务机硬件扩展也是有限制的,不可能无限操作
- 横向扩展(scale out):横向扩增 Redis 实例数,每个节点只负责一部分数据就可以(典型的分治思维)
- 优点:
- 分片的模式可以解决很多问题,包括单一实例节点的硬件扩容限制、成本限制
- 可以分摊压力,精细化治理,精细化维护
- 缺点:要面临分布式带来的一些问题,如:数据一致性、分布式事务、分布式锁等
- 优点:
集群模式
Redis 集群(Cluster)也是一种分布式数据库方案,集群通过分片(sharding)模式来对数据进行管理,并具备分片间数据复制、故障转移和流量调度的能力
具体做法:将数据划分为 16384()个哈希槽(slots),每个实例节点管理其中一部分槽位,槽位的信息会存储在各自所归属的节点中,集群之间的信息通过 Gossip 协议 进行交互,这样就可以在某一节点记录其他节点的哈希槽分配情况。以下图为例,该集群有 4 个 Redis 节点,每个节点负责集群中的一部分数据,数据量可以不均匀,比如性能好的实例节点可以多分担一些压力
当所有哈希槽都有节点进行管理时,集群处于 online 状态;否则,如果有一个哈希槽没有被管理到,集群就处于 offline 状态
集群的组群过程
集群是由一个个互相独立的节点(redis node)组成的,刚开始时,它们都是隔离、毫无联系的。需要通过一些操作,把它们聚集在一起,最终才能组成真正可协调工作的集群
各个节点的联通通过 CLUSTER MEET 命令完成:CLUSTER MEET <ip> <port>
具体做法:其中一个 node 向另一个 node(指定 ip 和 port)发送 CLUSTER MEET 命令,这样就可以让两个节点进行握手(handshake 操作) ,握手成功后,node 节点就会将握手另一侧的节点添加到当前节点所在的集群中。这样一步步地将需要聚集的节点都圈入同一个集群中
集群数据分片
Redis 官方提供的 Redis Cluster 方案,核心就是集群的实例节点与哈希槽(slots)之间的划分、映射与管理
哈希槽的划分
将整个 Redis 数据库划分为 16384 个哈希槽,集群中有 n 个实例节点,每个节点可以处理 0 ~ 16384 个槽点,把所有槽位瓜分完成。实际存储的数据键值信息必然归属于哈希槽的其中一个,哈希槽与数据键的映射是通过以下两个步骤完成的:
- 使用 CRC16 算法计算键,得出一个 16 bit 的值
- 将这个 16 bit 的值对 16384 取模,得到哈希槽的位置
如果想把某些 key 固定到某个 slot 上,可以用 hash tag 能力,强制 key 所归属的槽位等于 tag 所在的槽位
具体做法:在 key 中加个 {}
(如 test_key{1}
),使用 hash tag 后,客户端在计算 key 的 CRC16 时,只会计算 {}
中数据:
> cluster keyslot user:case{1}
(integer) 1024
> cluster keyslot user:favor
(integer) 1023
> cluster keyslot user:info{1}
(integer) 1024
哈希槽的映射
- 使用
cluster create
,会将 16384 个 slots 平均分配在集群实例上 - 使用
cluster addslots
可以指定具体的分配方法
redis-cli -h 192.168.0.1 –p 6379 cluster addslots 0,7120
redis-cli -h 192.168.0.2 –p 6379 cluster addslots 7121,9945
redis-cli -h 192.168.0.3 –p 6379 cluster addslots 9946,13005
redis-cli -h 192.168.0.4 –p 6379 cluster addslots 13006,16383
数据复制过程和故障转移
数据复制
Redis 集群中的每个实例节点都负责一些槽位,每个实例节点要有一个 Master 和至少一个 Slave 节点,节点之间保持 TCP 通信,通过和主从复制相同的方式同步数据。当 Master 宕机,Redis Cluster 自动会将对应的 Slave 节点选为 Master,来继续提供服务。主从切换之后,故障恢复的主节点,会转化成新主节点的从节点
与主从模式不同的是,主从节点之间并没有读写分离, Slave 只用作 Master 宕机的高可用备份,所以更合理来说应该是主备模式
故障检测
同哨兵机制,一个节点不能说明某个节点是否故障,需要占据多数的实例节点都认为某个节点故障时,cluster 才进行下线和主从切换的工作
主从故障转移
当一个从节点发现自己正在复制的主节点下线,并投票通过标记为「客观下线」,则开始对下线主节点进行故障转移,步骤如下:
- 如果只有一个 slave 节点,则从节点会执行
SLAVEOF no one
命令,成为新的主节点 - 如果是多个 slave 节点,则采用选举模式竞选出新的 master
- 新的主节点会撤销所有对已下线主节点的 slots 指派,并全部指派给自己
- 新的主节点向集群广播一条 PONG 消息,让集群中的其他节点知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽
客户端访问集群的过程
如何定位数据所在节点?
每个实例节点会将自己负责的哈希槽信息通过 Gossip 协议广播给集群中其他的实例,实现 slots 分配信息的扩散。即:每个实例都知道整个集群的哈希槽分配情况以及映射信息
- 客户端连接任一实例,获取到 slots 与实例节点的映射关系,并将该映射关系的信息缓存在本地
- 将需要访问的数据的 key,经过 CRC16 计算后,再对 16384 取模得到对应的 slot 索引
- 再将请求发送到对应的实例上