redis集群
一、redis集群种类
在redis3.0以前,redis集群包括两种类型:主从复制和哨兵模式,3.0以后推出了cluster集群模式
主从复制包括两种服务器(master和slave),Master主服务器;对主服务器进行复制的服务器称为:Slave从服务器。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,从服务器会接收主服务器发送归来的数据同步命令(同步过程在下面会说)。该模式master只能有一个,slave可以有多个。但是这种模式下会出现一个问题,当读写分离的情况下,主节点一旦宕机。数据就无法写入,因为只有主节点能够写入数据,在哨兵模式没出来以前,需要手动将一台slave使用slaveof no one提升为master要。
哨兵模式(Sentinel)就是为了解决主从复制的主节点宕机的情况(即高可用),由一个或多个Sentinel去监听任意多个主服务以及主服务器下的所有从服务器,监听主服务器的状态,如果主服务器下线,会通过选举机制将从服务器升级为主服务器。实现了服务灾备故障转移。
cluster集群模式是在redis3.0推出的一种高性能高可用集群的一种模式,在redis3.0出来以前,为了实现这种功能,有很多公司都有自己的一套高可用高性能的集群方案如:tweproxy实现代理分片。cluster集群通过分片来进行数据共享,并且提供了主从复制和故障转移的功能,cluster集群有多个节点组成(最少三个主节点才能构成集群),每个节点都是独立的,需要将节点连接起来形成集群。可以说cluster是集主从复制和哨兵与一身,并且增加了分片功能的一种集群模式。
二、redis的主从同步
redis主从同步。
全量复制:从服务器第一次连接主服务器的时候会进行的一定是全量同步的操作。
- mater服务器使用BGSAVE命令开启一个后台进程用于生成rdb文件,这个操作会消耗大量的cpu、内存和磁盘读写
- 主服务器会缓存所有接收到的写命令,当后台保存进程处理完以后,将rdb文件发送到从服务器,这个过程会消耗大量的网络资源,宽带和流量等
- 从服务器接收rdb文件以后,保存在磁盘上。载入文件的过程是阻塞的,无法处理请求命令
增量复制:redis的主从同步增量同步(slave初始化开始正常工作以后),主服务器会使用缓冲区记录未同步的所有写命令,并向从服务器发送命令。redis默认是尝试进行增量同步,如果不成功会向主服务器请求全量同步
2.8之前的sync同步复制,不管是第一次连接还是断开重连,都是执行以下全量同步的操作
2.8以后增加psync复制,2.8以后增加了部分同步的功能,在slave断线重连以后默认是通过psync的方式同步数据
部分同步主要包含以下三个部分
- 主服务器的复制偏移量和从服务器的复制偏移量:根据复制偏移量的差异就可以得知主从服务器之间从哪到哪的数据是没有同步的。
- 主服务器的复制积压缓冲区,默认大小为1M:复制积压缓存区其实是一个先进先出(FIFO)队列,当主服务器执行写命令时,它不仅会传播给从服务器,还会将该命令+复制偏移量加入队列中,而当从服务器断线重连后,如果它的复制偏移量对应的操作还存在于主服务器的队列中,说明断线没多久,此时进行增量同步操作,如果复制偏移量的值已经不在队列中了,说明已经被挤出去了,此时进行全量同步
- 服务器运行的id,用于存储服务器的标识。在从从服务器连接主服务器时,取到主服务器的运行id和从服务器的运行id进行对比,判断是执行部分同步还是完整同步。步骤:当主服务器和从服务器第一次建立连接时,主服务器会将字节的服务器id发送到从服务器上保存,当从服务器断开重连以后,会把这个id发送给主服务器,主服务器判断与自己的id是否一致,如果不一致则进行全量同步
加速复制:redis4以后增加的无磁盘复制。
- 正常情况下全量复制需要先在磁盘上创建一个rdb文件,然后加载这个文件发送到从服务器上。
- 在比较低速的磁盘上这种操作会给主服务器带来较大的压力(主服务器同时要接收写请求)
- 新版支持了无磁盘复制,直接将rdb文件通过网络发送给从服务器,不使用磁盘作为中间存储。但是数据可靠性得不到保障,所以默认是关闭的
- Repl-diskless-sync yes(默认是no,不开启),
三、哨兵模式--sentinel
redis提供了哨兵的命令,是一个独立的进程。哨兵通过发送命令给多个节点,等待redis各个节点服务器响应,从而监控运行的多个reids实例情况
当哨兵检测到master宕机了,会自动将其中的一个slave节点切换成master,通过通知其它的从服务器,修改配置文件切换主机
哨兵的工作任务
- 监控:sentinel会不断检查主服务器和从服务器是否正常运行
- 提醒:当被监控的某个redis服务器出现问题的时候,sentinel通过api通知管理员或者其它应用程序
- 故障转移:
- 服务器端:当主服务器不能正常工作时。sentinel会开始一次自动故障转移操作,它会将失效的主服务器中的一个从服务器升级为主服务器(选举策略),并让其它的从服务器连接新的主服务器。
- 客户端:当客户端长沙连接失效的主服务器时,键也会向客户端返回新的主服务器地址,使得集群可以使用新主服务器代替失效的服务器
一般哨兵都是使用多个哨兵进行监控,单个判断可能不准确(由于网络波动等),各个哨兵之间还会进行监控,形成多哨兵模式。
多哨兵模式下线名称介绍:
- 主观下线:单个sentinel实例对客户端做出的下线判断,比如网络波动导致接收不到请求等。即一个服务器在down-after-milliseconds选项所指定的时间内,对它发送ping命令的sentinel没有收到服务器的有效回复,那么sentinel就会将这个服务器标记为主观下线
- 客观下线:多个sentinel实例对同一个服务器做出sdown判断,并且通过SENTINEL is-master-down-by-addr命令相互交流以后,得出的服务器下线判断。客观下线只针对主服务器
- 仲裁:
- sentinel在给定的时间范围内,从其它sentinel那里收到了足够数量(配置文件里配置,一般是sentinel一半+1)的主服务下线报告,那么sentinel就会将服务器的状态从主观下线改为客观下线
- 当拥有认为主观下线的哨兵达到sentinel monitor所配置的数量时,就会发起一次投票,进行failover
四、集群模式--数据分片
4.1 redis cluster的特点
- 节点互通:redis集群所有的 Redis 节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
- 去中心化:Redis Cluster 不存在中心节点,每个节点都记录有集群的状态信息,并且通过 Gossip 协议,使每个节点记录的信息实现最终一致性;
- 客户端直连:客户端与 Redis 节点直连,不需要中间 Proxy 层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可;
- 数据分片:Redis Cluster 的键空间被分割为 16384 个 Slot,这些 Slot 被分别指派给主节点,当存储 Key-Value 时,根据 CRC16(key) Mod 16384的值,决定将一个 Key-Value 放到哪个 Slot 中;
- 多数派原则:对于集群中的任何一个节点,需要超过半数的节点检测到它失效(pFail),才会将其判定为失效(Fail);
- 自动故障转移Failover:当集群中某个主节点故障后(Fail),其它主节点会从故障主节点的从节点中选举一个“最佳”从节点升主,替代故障的主节点;
- 功能弱化:集群模式下,由于数据分布在多个节点,不支持单机模式下的集合操作,也不支持多数据库功能,集群只能使用默认的0号数据库;(redis本身觉得区分数据库是一个错误的选择,推荐使用一个库做处理)
- 集群规模:官方推荐的最大节点数量为 1000 个左右,这是因为当集群规模过大时,Gossip 协议的效率会显著下降,通信成本剧增。
4.2 redis分片
redis基于分片的思想,提出了哈希槽,即所谓的 Sharding思想,为了使得集群能够水平扩展,首先就是把数据按照规则分配到不同的节点上,常见的数据分片方法有:范围分片、哈希分片,一致性哈希算法和虚拟哈希槽等。
Cluster 采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383 整数槽内,计算公式:slot = CRC16(key) & 16383。每一个主节点负责维护一部分槽以及槽所映射的键值数据。
redis虚拟槽分区的特点:
- 解耦数据和节点之间的关系,简化了节点扩容和收缩难度。
- 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据
- 支持节点、槽和键之间的映射查询,用于数据路由,在线集群伸缩等场景。
Redis 集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。可以说,槽是 Redis 集群管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。
如果说redis集群需要扩展master节点,那新加入集群的master节点是没有分配槽位的,需要将集群的槽位移动和分配到加入该节点上。先要为新节点指定槽的迁移计划,确保迁移后每个节点负责相似数量的槽,从而保证这些节点的数据均匀。如果redis的节点需要下线,也需要将该节点的槽和数据迁移到其它的节点上。
4.1 一致性hash
rediscluster使用了一致性hash算法,固定了hash环为 0 ~ 16383,当服务器新增或者删除的时候,只对该服务器所持有的槽有影响,降低了影响范围。
4.2 数据倾斜
数据倾斜的原因:由于redis cluster基于hash分片的形式存储数据,当大量的数据通过hash以后都指定到了同一个几点上,就会导致数据的大量倾斜,如果单个节点的内存配置较低,还可能会导致服务不可用。比如热点数据
4.2.1 bigkey
bigkey导致的倾斜,某个实例上保存了bigkey,bigkey的value值很大(String类型),或者bigkey为大集合元素,会导致实例的数据量增加,内存增加
如果是String类型的,在存储数据的时候,尽量将没有用的信息去除,只保留一些关键数据;
如果是集合类型,根据集合里的主键,如用户id,进行一个范围的分片,将数据集合拆分,化整为零进行分散保存,减少单个实例的压力
4.2.2 slot分配倾斜
由于slot分配不均匀导致的倾斜,一般集群在默认构建的时候,以三个节点为例子,将0~16383按照顺序分配到三个节点上,假设大量的key集中在0~4000这个范围,那会数据倾斜在某个节点上。
这种情况一开始的时候很难去界定哪个槽会偏多,需要根据实事的情况,手动去移动槽到不同的节点上。
在 Redis Cluster 中,我们可以使⽤ 3 个命令完成 Slot 迁移:
- CLUSTER SETSLOT:使⽤不同的选项进⾏三种设置,分别是设置 Slot 要迁⼊的⽬标例,Slot 要迁出的源实例,以及 Slot 所属的实例。
- CLUSTER GETKEYSINSLOT:获取某个 Slot 中⼀定数量的 key。
- MIGRATE:把⼀个 key 从源实例实际迁移到⽬标实例
4.2.3 hash tag导致倾斜
hash tag支持在键值对里加一对花括号{},客户端会在计算key的CRC16值时,只对hash tag里的内容进行hash计算。
如果redis的key为key:{123}这种情况的时候,每次都会把这个数据分配到同一个slot当中。
使用hash tag的原因:redis在执行事务操作和范围查询的时候,主要是执行事务操作的时候,如分布式锁的lua脚本会使用。因为redis单个命令是原子性,如果是在一个事务里执行多个操作的时候,有可能key会分配到不同的slot上,需要指定同一个slot进行事务操作
解决方法:如果hash tah进行切片的数据量会导致大量的数据倾斜,应当避免大量使用hash tag切片。比如颗粒度大的分布式锁就可以使用hash tag
五、redis集群问题
5.1 集群脑裂
5.1.1 介绍
redis的集群脑裂分为两种,一种是sentinel脑裂,一种是cluster脑裂。两者之间原因大致相同,但是由于组成不同。
集群网络分区:
- client与master1的网络分区
- cluster集群的其他master节点与master1的网络分区
sentinel网络分区:
- client与master1的网络分区
- sentinel集群的网络分区
5.1.2 前置条件
- 对于cluster:cluster集群中超过半数的master节点, 与master1节点间, 出现网络断开;对于sentinel,sentinel集群中的master1节点中的sentinel出现网络断开
- client与master1的网络连接正常
由于客户端与master1的网络连接是正常的,说明客户端可以往master写数据,但是由于cluster集群中半数的master节点与master1的连接断开,就会导致脑裂的产生。
5.1.3 产生原因
- cluster集群判定master1进入FAIL状态
- slave1接收到master1进入FAIL状态的广播, 开始申请主从切换投票
- slave1被超过半数的master节点投票通过, slave1开始进入master状态,并广播新的master
- 对整个cluser集群来说, 原有的master1已经下线, master节点由slave1切换完成
- 对于cluster1来说, 现在最新的数据, 是由原来的slave1提供对于原有的master1原有的master1节点的数据将丢失,如果cluster集群判定重新上线, 则被判定为slave状态, 由原来的slave1主从切换后,全量复制一份slave1的数据
5.1.4 问题影响
- client的数据继续写入master1
- 但是对于cluster集群, 已判定master1进入FAIL状态, 并已选举出新的master节点
- 导致client写入master1的数据将丢失,(在集群判断master1上线以后会转成slave节点,并且重新复制新的master数据,期间写入的数据丢失)
5.1.5 解决策略
client心跳检测更新cluster集群信息, 减少与判定为FAIL的master1的连接时间(降低数据丢失时间,不能完全保证)。(了解codis+sentinel)
master1节点判断自己是否已与cluster集群网络隔离,如果已隔离,则拒绝写入
原理: master自身判断自己是否已经没有与从节点做过数据交换,如果过长时间没有做过数据交换, 则认为本节点已经被cluster集群判定下线,则本master节点拒绝写入。redis.conf配置如下:
#redis5以前的写法 min-slaves-to-write 3 min-slaves-max-lag 10 #redis5以后的写法 #master节点至少的slave节点数目, 否则master拒绝写入 min-replicas-to-write 1 #master节点至少与slave节点数据心跳ping时间, 超过该时间, 则master拒绝写入 min-replicas-max-lag 1
由于该配置是通过mater判定slave的复制时间最终觉得是否可以被执行写入,避免集群fail时,继续向旧的master写入
存在问题:
- master的FAIL状态判定, 与master节点的拒绝被写入, 是不同的判断路径
- master1进入FAIL, 是由cluster集群中的其他master节点负责判定
- master1节点拒绝写入, 则是master节点判断与slave节点的ping间隔
- cluster一主一从部署时, 主节点故障发生主从切换,新的主节点将拒绝写入 master宕机,slave切换为master,此时新的master没有slave 因为没有slave,导致min-replicas-max-lag判定超时, 导致新的master拒绝写入, 从而导致该cluster分片不可用
- master的FAIL状态判定, 与master节点的拒绝被写入, 是不同的判断路径
方案选择:
- 客户端通过proxy连接时,缩短proxy查询集群状态的周期
- 客户端直接连接cluster的master节点时,不做处理