Redis集群

238 阅读10分钟

前言

本文是极客时间Redis核心技术与实战的学习笔记

Redis是一个高性能的key-value数据库,在线上充当着非常重要的角色,尽管有RDB和AOF机制保证Redis尽量少丢失数据,也依旧解决不了Redis单点故障的问题,因此需要寻找其它方案解决这个问题,目前的解决方案有主从复制、Redis集群

主从复制

既然一个节点存在单点故障的问题,那么最常规的做法便是多部署几个节点,多保存几份数据,多份数据便一定会存在一个问题:数据同步问题

Redis提供了主从库模式,以保证副本的一致,主从库之间采用的是读写分离的方式

读操作:主库、从库都可以接收

写操作:只有主库可以接收,然后,主库将写操作同步给从库

image.png

如何同步

image.png

  1. 主从库建立连接、协商同步
  2. 主库执行bgsave命令,生成RDB文件,发送给从库,从库清空当前数据库,加载RDB文件(避免从库同步前保存了其他数据,影响当前的数据)
  3. 将第二过程中新接收到的写命令再次发送给从库(主从全量同步过程中会把新写入的数据记录在replication buffer中)
验证

搭建本地环境验证主从复制模式,主要验证四个问题

  1. 主从复制是否会同步全量数据
  2. 主从节点之间的关系是否是maste-slave关系
  3. 从节点是否可以写入数据
  4. master节点宕机,slave节点会不会上位,成为master节点

image.png

image.png

如上图

  1. 本人想要模拟三个节点的主从复制,复制了两份Redis配置文件
  2. 修改新复制的两份配置文件的端口、RDB持久化文件名、pidfile配置
  3. 启动三个Redis服务

image.png

6379作为master节点,查看master节点中的数据

image.png

  1. 6380节点一开始没有数据,执行replicaof命令之后,很清楚地看到同步了master节点中数据
  2. 执行info replication命令,可以看到6380是6379的slave

image.png 6381也做了同样的操作

image.png

向从节点6380写入数据失败

image.png 向主节点6379写入数据成功

image.png

master节点短暂宕机,恢复之后依旧是master节点

主-从-从

从上文可以看到,一次全量复制中,对于主库来说,需要完成两个耗时的操作:生成RDB文件和传输RDB文件

如果从库数量很多,而且都要和主从进行全量复制的话,主库就会忙于fork子进程生成RDB文件,fork这个操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求速度变慢,此外,传输RDB文件也会占用主库的网络带宽,同样会给主库的资源使用带来压力

基于以上的原因,衍生出了 主-从-从模式,从库可以和从库交互,减轻主库上的压力

image.png

哨兵模式

在主从模式中,master节点宕机之后,slave节点不会切换为master节点,这样会导致一个问题:无法写入数据,这在产线上显然是无法接受的。当然,我们可以在从节点上执行 slaveof no one使slave节点变成master节点,但是,在分秒必争的产线上,手动切换是较为低效的方式,所以就有了哨兵模式

基本流程
  1. 监控:哨兵进程在运行时,周期性地给所有的主从库发送ping命令,检测它们是否仍然在线运行,如果从库没有在规定时间内响应哨兵的ping命令,哨兵会把它标记为下线状态,如果主库没有在规定时间内响应哨兵的ping命令,哨兵就会判定主库下线
  2. 选主:主库挂了以后,哨兵就需要从很多个从库里,按照一定的规则选择一个从库实例作为新的主库
  3. 通知:哨兵会把新主库的连接信息发给其他从库,让它们执行replicaof命令,和新主库建立连接,并进行数据复制。同时,哨兵会把新主库的连接信息通知给客户端,让它们把请求操作发给新主库上
如何判断主库下线

先来解释一个名词,主观下线:哨兵进程会使用ping命令检测自己和主、从库间的网络连接情况,用来判断实例的状态,如果主、从库没有在规定时间内响应,哨兵就会先把它标记为 主观下线

在集群网络压力较大、网络拥塞、主库压力较大的情况下,单个哨兵可能存在误判问题,主库实际并没有下线,哨兵误以为它下线了

为了减少误判问题,通常会采用多实例组成的集群模式进行部署(哨兵集群),引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况

如下图所示

image.png

如何选取新主库

按照以下优先级

  1. 筛选掉网络连接状态不好的节点
  2. 优先级最高的从库:通过slave-priority配置项给不同的从库设置不同的优先级
  3. 和旧主库同步程度最接近的从库
  4. ID号小的从库
验证

搭建本地环境,主要验证两个问题

  1. 主库挂了,是否会从从库中选取新的主库
  2. 选取新的主库后,旧主库回来是什么状态

配置sentinel.conf配置文件

// zmaster表示为监控对象起的服务器名称
// 1表示至少有多少个哨兵同意,才能迁移主节点
sentinel monitor zmaster 127.0.0.1 6379 1

image.png

  1. redis-sentinel /usr/local/etc/sentinel.conf启动哨兵模式
  2. 从日志中也看得很清楚,监控对象为6379节点,slave为6380、6381

image.png

将master节点6379手动shutdwon

image.png

image.png

  1. 可以清楚地看到master节点切换为6381节点
  2. 在6381节点执行info replication命令,确实变成了master节点

image.png 6379旧master节点故障恢复,变成了从节点

问题

什么是脑裂问题?

当出现网络问题时,一个节点和其他节点无法连接,就会出现下图的场景,这个时候哨兵误以为主库下线,会重新选举主库,便会出现双主库的情况,不同的客户端可以往不同的主库写入数据

image.png

为什么脑裂问题会导致数据丢失?

主从切换后,从库升级为新主库,其它从库会执行replicaof命令,和新主库全量同步,假如出现网络情况下旧主库网络恢复良好,回来之后依旧需要同步新主库的全量数据,那么在脑裂期间客户端写入的新数据就会被清空,导致数据丢失

如何避免脑裂问题?

我们可以利用Redis的两个配置来避免脑裂问题

  • min-slaves-to-write:主库能进行数据同步的最少从库数量
  • min-slaves-max-lag:主从库间进行数据复制时,从库给主库发送 ACK 消息的最大延迟

假设min-slaves-to-write为A,min-slaves-max-lag为B,这两个配置的作用是,主库连接的从库至少有A个,和主库进行数据同步时延迟不能超过B秒,否则,主库不会再接收客户端的请求

有了这两个配置,发生脑裂问题时,该主库没有足够的从库,或者其它从库不能进行数据同步,那么客户端自然不能对该主库写入数据了

集群模式

以上的方案在数据量小的情况下已经够用了,在数据量大的情况下还是存在两个问题

  1. 单个节点容量不够:每个节点都存储了全量的数据,假设一个节点内存只有10G,现在有20G的数据,无法满足存储需求
  2. 并发写:只有master节点可以进行写操作,在写多的场景下,单个节点容易出现瓶颈

于是出现了Redis切片集群来解决这些问题

如何存储数据

Redis Cluster采用Slot来处理数据和实例之间的映射关系,首先根据键值对的key,按照CRC16算法计算一个16bit的值,然后16bit值对16384取模,得到0~16383范围内的模数(Slot),一个切片集群共有16384个Slot,这些槽类似于数据分区,每个健值对都会根据它的key,被映射到一个Slot中,我们在部署Redis Cluster时,Redis会自动把这些槽平均分布在集群实例上。例如,如果集群中有N个实例,每个实例上的槽个数为16384/N个,当然,不同的实例,内存大小不一,可以根据实际情况手动分配不同实例Slot的数量

客户端如何定位数据
  1. 键值对数据所处的Slot是可以通过计算得到的,这个计算可以在客户端发送请求时来执行
  2. 客户端和集群实例建立连接后,实例会把Slot的分配信息发送给客户端
  3. Redis实例之间会共享Slot的映射关系
  4. Redis实例变更时,客户端无法感知到实例的变更,可以通过重定向机制获取数据,比如:客户端给一个实例发送读操作时,这个实例上并没有相应的数据,实例会返回一个响应结果,这个结果中包含了新实例的访问地址,客户端再发送一个新的请求给新的实例

Redis Cluste节点之间的关系如下图

image.png

验证

搭建本地集群环境,主要验证两个问题

  1. 集群模式下slot的分配情况
  2. 客户端如何存储数据,是否会发生重定向

首先在配置文件中修改以下三个配置,然后删除遗留的rbd、aof文件

# 打开集群模式
cluster-enabled yes
# 设定节点配置文件名
cluster-config-file nodes-6382.conf
# 设定节点失联时间(毫秒),在此规定时间内未响应,会切换主从
cluster-node-timeout 15000

image.png

  1. 启动redis服务之后,redis服务之间还没有形成集群,redis-cli --cluster create 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382还需要使用此命令搭建集群
  2. 本次实验只搭建了三个master节点,如果想要给三个master节点配置从节点,可以使用此命令redis-cli --cluster create 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 --replicas 1,这样就需要6个redis实例了
  3. 通过日志,可以看到slot的分配情况

image.png

在6381节点新增数据,实例提示该slot并不属于自己,会提示一个重定向地址

image.png

在该重定向地址新增数据,新增成功 image.png

如上图,使用此客户端连接方式 redis-cli -c -h 127.0.0.1 -p 6381,客户端会自动帮我们重定向

问题

为什么redis是16384个slot

github.com/redis/redis… 这是官方作者的回答

www.cnblogs.com/rjzheng/p/1… 这篇文章的解答很好

参考资料

极客时间Redis核心技术与实战

zhuanlan.zhihu.com/p/308534431