一前言
大家好,我是小墨,be foolish,be hungry。本篇文章主要写redis的集群方案相关知识,欢迎大家多多斧正,如果觉得小墨我写得用心的话,可以点个赞啊。
redis提供原生的高可用方案有:
1,主从复制
2,哨兵机制
3,cluster
我们比较下:
- 复制:复制主要实现了数据的多机备份以及对于读操作的负载均衡和简单的故障恢复。缺陷是故障恢复无法自动化、写操作无法负载均衡、存储能力受到单机的限制。
- 哨兵:在复制的基础上,哨兵实现了 自动化 的 故障恢复。缺陷是 写操作 无法 负载均衡,存储能力 受到 单机 的限制。
- 集群:通过集群,Redis 解决了 写操作 无法 负载均衡 以及 存储能力 受到 单机限制 的问题,实现了较为 完善 的 高可用方案。
然后我们通过理解这三种方案原理与局限,然后我们可以看下工作环境下如何利用这原生方案来设计集群满足实际工作需要
二,集群方案原理
1,主从复制
redis主从节点复制过程如下:
1,从节点发送slaveof main_ip port
2,建立套接字连接,形成TCP长连接。可以理解为从服务器是主服务器的客户端
3,发送PING命令,有两个作用:
1)检查是否套接字读写状态是否正常
2)检查主服务器是否能够正常处理命令请求
ping后可能遇到情况:
4,身份验证
5,发送端口信息
主服务器接收到命令后,会记录从服务器所对应客户端状态的slave_listening_port状态
6,同步
从服务器将向主服务器发送PSYNC命令,将自己数据更新
7,命令传播
完成同步后,主服务器只需要一直将自己执行的写命令发到从服务器即可
8,心跳
默认一秒一次,从服务器向主服务器发REPLCONF ACK ,
起到以下作用:
1,检测网络连接状态
2,通过判断从服务器状态,如设置主服务器
那么会根据心跳包判断当前从服务器们的状态来拒绝不安全的状态进行执行写命令
3,检查命令丢失
当主服务器发现从服务器复制偏移量少于自己的复制偏移量,则会在复制积压缓冲区找到从服务器缺少数据发给从服务器
2,哨兵机制
sentinel机制是redis的高可用的一个解决方案: 由sentinel实例组成的sentinel系统监视各个服务器,并在被监视的主服务器下线时,升级该主服务器的某个从服务器为新的主服务器
该系统有三个角色
- sentinel系统:sentinel实例本质就是redis服务器,组成的sentinel系统
- 主节点
- 从节点
我们可以参考《redis设计与实现》,sentinel系统多个状态如下 - 初始化成功时:
- 主服务器下线
- 故障转移
- 主服务器降级
- 通过这几步可以实现对redis集群故障处理,具体不在本文赘述
3,cluster
cluster集群可以实现redis的负载均衡,提高redis缓冲层的写效率
我们看下集群的创建和运行过程,我简化如下
- A节点cluster meet B节点握手后加入集群
- B节点通过gossip协议传播A节点到所在的集群所有节点
- 分配槽,全部16384个分配后集群可以运行
- 计算键属于那个槽的方法: HASH_SLOT=CRC16(key) mod 16384
- 如果客户端向节点发送命令检查到该槽不为该节点负责,为B节点负责,返回MOVED B:PORT 。再由客户端去访问所在节点
集群的键空间被分割为16384个slots(即hash槽),slot是数据映射的基本单位,即集群的最大节点数量是16384(官方推荐最大节点数量为1000个左右)。集群中的每个Master节点负责处理16384个hash槽其中的一部分,当集群处于“stable”状态时(无slots在节点间迁移),任意一个hash slot只会被单个node所服务。
这种情况不支持“multi-key”操作,即执行命令需要在多个redis节点移动数据。为了兼容提出了"hash tags"操作,,每个key可以包含自定义的“tags”,在存储的时候根据tags计算此key应该映射到哪个node上。通过“hash tags”可以强制某些keys被保存到同一个节点上,如:
对于key为{foo}.student1、{foo}.student2,{foo}student3,这类key一定是在同一个redis节点上。因为key中“{}”之间的字符串就是当前key的hash tags, 只有key中{ }中的部分才被用来做hash,因此计算出来的redis节点一定是同一个!
进行下实际的linux redis数据操作
192.168.43.31:6103> set {user1000}.student1 1000
192.168.43.42:6101> set {user1000}.student2 1000
192.168.43.31:6101> keys *
4) {user1000}.student1
6) {user1000}.student2
三,集群架构
现在市面上比较常见的有三种redis集群架构:
1,主从复制+哨兵(小规模)
这一种方案是redis原生提供的高可用方案
具体sentinel系统操作过程我们上面介绍过了,client端访问系统的VIP,
系统缺陷:
1)主从切换过程会丢数据
2)这里只能写单点,如果想要动态的进行redis扩缩容实现不了。
2,proxy代理+redis分片+主从
这里proxy可以选择Codis和Twemproxy,现在这一套架构已经淘汰了Twemproxy,Codis成为主流,
Twemproxy:推特开源,最大缺点是无法平滑扩缩容
Codis:豌豆荚开源,解决了Twemproxy扩缩容的问题,而且兼容了Twemproxy,而且能发展起来的一个主要原因是它是在Redis官方集群方案漏洞百出的时候率先成熟稳定的,具体架构如下:
- 代理通过一种算法把要操作的key经过计算后分配到各个组中,这个过程叫做分片,所有的key分为1024个槽,每一个槽位都对应了一个分组
- 每个组都有主从节点组成,有sentinel系统来监控主从节点,选举从节点为主节点。
- proxy集群解决单点问题,不同proxy之间通过Zookeeper来保存映射关系,由proxy上来同步配置信息,其实它支持的不止zookeeper,还有etcd和本地文件。除了这个还会存储一些其他的信息,比如分组信息、代理信息等
- codis-ha,codis-ha实时监测proxy的运行状态,如果有异常就会干掉,它包含了哨兵的功能
3,去中心化的cluster集群模式
redis cluster作为去中心化的设计,在redis5.0.3版本后才支持。分片是客户端分片,跟codis的proxy分片不一样。
redis集群每个节点之间会进行ping,pong指令,不需要sentinel系统进行监视。
具体规则如下:
(1)每秒会随机选取5个节点,找出最久没有通信的节点发送ping消息
(2)每100毫秒(1秒10次)都会扫描本地节点列表,如果发现节点最近一次接受pong消息的时间大于cluster-node-timeout/2 则立刻发送ping消息
如果发现主节点下线了,需要进行从节点选举:
一共有四个因素影响选举的结果,分别是断开连接时长、优先级排序、复制数量、进程id,如果连接断开的比较久,超过了某个阈值,就直接失去了选举权,如果拥有选举权,那就看谁的优先级高,这个在配置文件里可以设置,数值越小优先级越高,如果优先级相同,就看谁从master中复制的数据最多,选最多的那个,如果复制数量也相同,就选择进程id最小的那个。
四,集群架构比较
这里我们主要比较这codis和cluster
codis | redis cluster | |
数据库数量 | 16 | 1 |
redis支持版本(以上) | 3.2.8 | 5.0.3 |
哈希槽 | 1024 | 16384 |
集群结构 | 代理 | 去中心化 |
部署 | 较复杂 | 简单 |
使用公司 | 阿里云: ApsaraCache, RedisLabs、京东、百度等 | AWS, 百度贴吧 |
这两种方案都是很成熟的redis高可用商用方案,我们其实可以注意到对于超大规模的redis集群,codis是更加合适,我翻到一篇唯品会的redis文章谈到在线有生产几十个cluster集群,约2千个instances,单个集群最大达到250+instances。
但是就支持难度而言,cluster部署,维护应该是更加简单的。
然后我翻到一些文章,讲到cluster一些缺点,我们来一一看下:
- Slave 在集群中充当“冷备”,不能缓解读压力,当然可以通过 SDK 的合理设计来提高 Slave 资源的利用率。
对于这个问题,其实是讲到是否需要支持读写分离的问题,我们要知道redis是异步复制的,不保证复制成功率,对于redis cluster官方建议不做读写分离,只将slave作为备用机器,要做的话也可以,这样的话如果是java语言的话得去修改下lettuce客户端的代码。 - 多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况。
这其实是因为redis cluster没有一个proxy层可以用于定制slot规则,默认使用hash去分配到对应的节点
然后我们思考下为什么redis cluster有这个集群大小的限制呢?
我们之前知道redis cluster的数据存储模块和分布式的逻辑模块是耦合在一起的,即除了数据存储之外,还需要保持着对节点之间的相互信息确认。
redis集群内节点,每秒都在发ping消息。规律如下
(1)每秒会随机选取5个节点,找出最久没有通信的节点发送ping消息
(2)每100毫秒(1秒10次)都会扫描本地节点列表,如果发现节点最近一次接受pong消息的时间大于cluster-node-timeout/2 则立刻发送ping消息
因此,每秒单节点发出ping消息数量为
数量=1+10*num(node.pong_received>cluster_node_timeout/2)
我们看下这个ping消息的字节数:每个消息中的消息头里面有个myslots的char数组,长度为16383/8,这其实是一个bitmap,每一个位代表一个槽,如果该位为1,表示这个槽是属于这个节点的。则这个数组myslots[CLUSTER_SLOTS/8]大小为16384÷8÷1024=2kb所以每个ping消息至少2 KB
假设200节点的集群,部署在20个物理机上每台划分为10个节点,cluster-node-timeout设置为15s,ping/pong带宽要达到25MB
所以集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。
参考文章
https://www.linuxidc.com/Linux/2019-08/159783.htm