四、Redis集群

72 阅读13分钟

Redis集群

redis集群解决的问题

redis单机风险:

  • 机器宕机了,服务不可用
  • 单体服务器容量有限
  • 单体服务器处理大量请求压力大

解决问题思路:AKF

AKF是一个x-y-z三轴模型解决方案

x轴:为主机搭建从机,数据完全复制
解决问题:
1.单机服务器宕机不可用
2.主机用于增删改,从机用于读数据,减少单机压力

y轴:多搭建几台主机,不同主机上放不同的业务数据
解决问题:
1.单体服务器容量有限
2.减少服务器压力

z轴:为y轴上不同业务数据的主机再搭建主机,再次减少y轴上主机的容量压力
解决问题:
1.减少服务器容量压力

x、y、z轴的方案一起使用,从而解决单体redis存在的问题

CAP原则

数据一致性问题

概念:集群中各个节点数据保持一致

方案

强一致性:

特点:

  • 数据保持一致
  • 数据保持一致需要时间,在这段时间内系统不能使用,破坏可用性

弱一致性:

特点:

  • 数据会丢失
  • 系统可用

最终一致性:

特点:

  • 数据最终会保持一致
  • 在某段时间内数据不一致
  • 系统可用

可用性

概念:系统在时时刻刻都是可以给出响应的

分区容忍性

概念:集群在某个时刻,允许节点的数据不一致

现在的集群,分区容忍性是必须满足的,剩下就是在可用性和数据一致性中做选择。CAP原则只能满足其中两个原则

redis中的监控程序

问题和方案

集群中需要时刻检测主节点是否正常,如果不正常,让从节点顶上。这是由监控程序完成的。

监控程序可以是一个节点,也可以是一个集群

监控程序是一个集群会引发的问题:一个监控程序认为主节点正常,另一个认为不正常,出现脑裂现象

方案:

  • 多数服从少数
  • 超过n/2+1个监控节点认为正常就正常,认为不正常就不正常
  • 一般集群节点选择奇数。例如3个节点和4个节点。首先3个节点中有2个相同就行,成本是2/3;4个节点中有3个相同才行,成本是3/4。2/3<3/4,3个节点成本低,意味着发现问题的成本低,所以选择奇数

主从复制及数据同步

搭建集群的重要配置参数

1. replicaof配置
   格式:replicaof  主机IP  主机port
   含义:配置跟随的主机地址
   注意:如果自己当主机,使用replicaof no one
2. masterauth配置
   格式:masterauth 主机密码
   含义:配置主机密码
3. replica-serve-stale-data配置
   格式:replica-serve-stale-data yes
   含义:复制初始化阶段,从机的旧数据是否对外提供访问
   注意:在复制初始化阶段,从机会把主机发送来的数据放入临时文件中,此时客户端访问从机,只能访问旧数据
4. replica-read-only配置
   格式:replica-read-only yes
   含义:从机是否只提供读取操作
5. repl-diskless-sync配置
   格式:repl-diskless-sync yes
   含义:主从复制期间,主机数据是否要落盘,yes表示不落盘,no表示落盘
6. repl-backlog-size配置
   格式:repl-backlog-size 1mb
   含义:在增量复制机制中,如果从机断线,这段时间内的命令会存到积存队列中,这个参数规定了队列的大小。如果命令数据大小超过队列大小,新命令会覆盖旧命令,所以从机恢复后会触发全量复制
7. min-slaves-to-write配置
   格式:min-slaves-to-write 3
   含义:最少有3个从机正常运行时,主机才支持写操作,否则在操作主机时会报错
8. min-slaves-max-lag配置
   格式:min-slaves-max-lag 10
   含义:这个配置与min-slaves-to-write联合使用。如果3个从机中有一个断线9秒,此时主机还是可以写入的。但是1秒后,主机认为从机断线,禁止数据写入

数据同步

数据同步过程

第一种情况:集群刚搭建完成

复制初始化过程

命令发送:在从机刚连上主机时,从机向主机发送ping命令,确认连接正常。如果正常,主机返回pong。然后从机向主机发送replconf命令,把自己的端口号发送给主机。然后再发送psync命令开始数据同步

同步过程:主机会启动一个子进程,子进程向从机发送数据,从机将接收的数据放入临时rdb文件中,等待接收结束后恢复数据。在整个过程中,可以通过replica-serve-stale-data配置参数决定从机是否对外提供访问

复制同步阶段

复制初始化过程结束后,开始复制同步阶段。在复制初始化阶段,主机会接收到增删改命令,这些命令存储在replication buffer中,到了复制同步阶段才会将buffer中的命令同步给从机。待同步完成后,主从机保持一个长连接,主机通过此连接将后续的增删改操作同步给从机。

第二种情况:从机断线重连上主机

如果从机在某段时间与主机断开连接后又连上,此时主从机会进行一次同步。在从机断开连接的这段时间内,主机会将增删改的命令存入环形缓存replication backlog buffer。此缓存如果已经存满,会覆盖之前的命令。所以,当从机连接上主机时,如果环形缓存已满,就需要进行全量同步,否则增量同步。

数据同步的时机:

  • 集群刚搭建好,从机刚连上主机,进行一次全量同步
  • 由于网络原因,从机与主机断开,后来又重新连上,进行一次同步,至于是全量同步还是增量同步,要看缓存replication backlog buffer是否满了来判断。此缓存大小由repl-backlog-size字段设置。
  • 主机每次的增删改操作,都会进行同步
  • redis的数据同步是弱一致性的。主机将数据发送给从机后就不管了,直接给客户端返回成功。此时从机可能会落盘失败,如果客户端去从机查询数据的话,获取的是脏数据。

哨兵

概念:

哨兵是监控程序,用于替代人工检测redis集群的节点故障,报警和故障转移

配置:

  1. port配置项
    格式:port 26379
    含义:哨兵程序的端口号
  2. sentinel monitor配置项
    格式:sentinel monitor 集群名称 主节点IP 主节点port 通过票数
    含义:指明哨兵监控的节点地址,以及选举主节点时通过的票数

启动方式:

  1. 进入redis的bin目录下,执行以下命令
  2. redis-sentinel sentinel的配置文件路径

Redis分布式方案

redis集群下,业务数据存储方案

在3.0版本之前,redis是不支持集群的。很多第三方组件在客户端侧实现了redis集群,它们实现的数据存储方案有很多种。在3.0版本之后,redis实现了集群部署。redis自身使用的是哈希槽的数据存储方案。

方案一:

根据业务类型不同划分数据,将数据分别存入各自的redis节点中

弊端:如果数据的业务类型不好划分,方案不可取

方案二:

通过哈希算法划分数据,存入各自redis节点中

弊端:如果以后增加集群节点,通过哈希算法取旧值取不到

方案三:

通过随机算法将数据存入redis节点中

弊端:要取数据时,自己也不知道取哪个节点获取

方案四:

通过一致性哈希算法,将数据存入redis节点中

弊端:新增节点,导致部分数据查询不到,出现缓存击穿

方案模型:

  1. 假设存在3个节点,x、y、z,有一个哈希算法,无论入参是什么,求出的结果在[0, 10]范围内
  2. 假设每个节点根据哈希算法求出结果是:x对应3、y对应6、z对应9
  3. 此时出现三个区间:[3, 6],[6, 9],[9, 3]
  4. 当有数据a要存入,假设根据哈希算法求出结果是4,4在[3, 6]范围内,那么数据a存入x节点;如果哈希算法求出的结果是7,那么数据存入y节点;如果哈希算法求出的结果是2,那么数据存入z节点。

redis分布式方案问题

问题1:

当有很多客户端连接redis服务器时,每个服务器连接压力很大

解决方案:客户端和服务器间加一个nginx反向代理服务器,做负载均衡

问题2:

增加一台redis服务器,可能出现缓存击穿的情况。除非把所有的数据重新哈希后存入对应的节点

解决方案:

  1. 先假设有很多redis服务器
  2. 每个实际的redis服务器分配多个虚拟节点,其实是槽点,且每个服务器存储了实际服务器与槽点的对应关系
  3. 当一个请求过来,客户端会随机选择一个服务器连接上,把数据发送给服务器,服务器进行哈希,算出哈希值
  4. 根据实际服务器与槽点映射关系,确定需要的数据在哪个服务器上,把消息发送给客户端
  5. 客户端根据返回的消息进行重定向,请求另一台服务器,拿到数据
  6. 当新增一台服务器时,根据哈希算法求出这台服务器应该有哪些槽点,把这些槽点分配给服务器,并且把槽点对应的数据也同步给服务器
  7. Redis用14位表示槽点数,总共有2^14 = 16384个槽点

这个解决方案与一致性哈希算法的区别在于,对数据进行哈希运算后对16384进行求余,再将数据放到对应的槽点下。因为redis主机再怎么增加都不可能超过16384个,所以在增加主机时,只需要指定哪些槽点数据迁移到新主机即可。下次要查询数据,在根据key值进行哈希运算后对16384进行求余,算出在哪个槽点后去对应主机上取数据即可。

问题三:

不同节点上的数据无法做聚合操作

解决方案:redis出于性能考虑,没有提供解决方案,开发者需要有意识的将需要进行聚合的数据放到一个节点中

Redis分布式方案部署

twenproxy代理部署

predixy代理部署

redis自身搭建集群:

部署步骤:

  1. 修改redis实例的配置文件,将其改成集群节点
    • bind配置项后面要写真实IP
    • 将cluster-enabled yes配置项打开
    • 将cluster-config-file nodes.conf配置项打开
    • 将cluster-node-timeout 5000配置项打开
    • 将appendonly yes配置项打开
  2. 启动每个redis实例
  3. 在任何一个主机实例上,执行redis-cli --cluster create IP1:PORT1 IP2:PORT2... --cluster-replicas n

命令介绍:

  1. redis-cli --cluster create命令 格式:redis-cli --cluster create IP1:PORT1 IP2:PORT2... --cluster-replicas n 含义:创建redis集群,create后面是每个实例的真实地址,n表示每个主机拥有的从机数目。这个命令还会为每个主机分配槽点,槽点是平均分配的
  2. redis-cli --cluster reshard命令 格式:redis-cli --cluster reshard IP:PORT 含义:这个命令可以重新分配槽点和数据,用于解决数据倾斜问题 使用步骤:
    1. 连接一台主机后,要求输入需要分配的槽点数目
    2. 槽点数目输入后,要求输入接收这些槽点的节点ID
    3. 要求输入划分出槽点的节点ID,可以输入多个节点
    4. 要想结束输入,在命令行输入done命令即可

注意事项:

在构建redis集群时,各个节点上的配置文件中,bind配置后面要写真实IP

高并发下的Redis缓存

高并发下的问题

缓存击穿

现象:

大量的高并发请求访问数据库,导致数据库短时间内压力巨大,最终奔溃

原因:

Redis作为缓存,有些key设置了过期时间,或者采用了LRU/LFU的方案,导致某些key在某一时刻被删除,此时正好大量的并发请求访问这个key。

解决方案:

1.大量请求过来后,都在缓存中获取不到key
2.让所有请求线程执行setnx命令,由于Redis是单线程的,有一个能够执行成功,相当于加了把锁
3.执行成功的那个线程访问数据库,把数据更新到缓存中,然后删除锁
4.其他请求访问redis拿到数据

方案问题:访问数据的线程挂掉了,锁一直没有删除 进阶方案:给锁设置过期时间

进阶方案问题:数据库堵塞,访问数据库的线程没有挂掉,只是有些慢,导致锁过了有效时间 进阶方案:开启一个线程,监测redis缓存中数据有没有更新,没有更新就延迟一下锁的有效时间,直到锁的有效时间上限

缓存穿透

现象:

客户端恶意请求不存在的数据,导致大量请求访问数据库

方案1:

使用布隆过滤器

布隆过滤器部署方案:
方案1:在客户端侧部署布隆过滤器
方案2:在客户端侧写算法,过滤机制在redis中
方案3:在redis服务端部署布隆过滤器

方案缺陷:
1.布隆过滤器还是有概率让一些不存在的数据透过
2.布隆过滤器不支持删除和修改操作

方案2:

不存在的key在redis中设置空key,并且设置较短过期时间

缓存雪崩

现象:

大量的key在同一时间过期删除,导致大量请求访问数据库

方案1:

均匀分散key的过期时间

方案2:

有些业务场景下,key的过期时间不能修改,使用缓存击穿的策略也是可行的

方案3:

客户端代码中在key过期时间节点,对于此时的请求,延迟请求到达redis的时间,可以延迟几秒访问redis

分布式锁

方案:

1.使用setnx命令
2.给锁添加过期时间,防止加锁的进程挂掉
3.起一个监控进程,不停的更新锁的时间,防止加锁进程没有挂掉,但是业务处理时间过长,导致锁过期的现象出现

注意:

redis最求的是效率,而锁是降低效率的东西,与redis理念相违背。 其实redis不适合作为分布式锁,zookeeper更适合。