Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案

1. Redis集群

1.1 主从复制

1.1.1 简介

主从复制即将master中的数据即时,有效的复制到slave中。一个master可以拥有多个slave,一个slave只对应一个master。

职责

  • master:
    写数据
    执行写操作时,将出现变化的数据自动同步到slave
    读数据(可忽略)
  • slave:
    读数据
    写数据(禁止)

单机redis的风险

  • 服务器宕机,可能会造成数据丢失,对业务造成灾难性打击。
  • 容量瓶颈,单台服务器的容量有限,不易扩容。

解决方案
为了避免单点redis服务器故障,准备多台服务器,互相连通。将数据复制多个副本保存在不同的服务器上,链接在一起,并保证数据是同步的。即使有其中一台服务器宕机,其他服务器依然可以继续提供服务,实现Redis的高可用,同时实现数据冗余备份。

1.1.2 多台服务器链接方案

Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案

1.1.3 主从复制的作用

  • 读写分离:master写,slave读,提高服务器的读写负载能力
  • 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
  • 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
  • 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
  • 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案

1.1.4 主从复制的工作流程

总述
主从复制过程大体可以分为3个阶段

  • 建立连接阶段(即准备阶段)
  • 数据同步阶段
  • 命令传播阶段
    Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案

1.1.4.1 建立连接阶段

建立slave到master的链接,使master能够识别slave,并保存slave端口号。

  • 设置master的地址和端口,保存master信息
  • 建立socket链接
  • 发送ping命令(定时器任务)
  • 身份验证
  • slave发送端口信息,master进行保存
    至此,主从链接成功!

状态:

slave:
保存master的地址和端口
master:
保存slave的端口

流程如下图:
Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案
具体实现步骤(slave链接master):

建立连接的方式有多种,比如slave客户端发送连接命令,或者启动服务的时候指定连接参数,但是最常用的还是在配置文件中进行配置,这里也只描述配置文件的方式。

  • 服务器配置,配置文件加入以下命令就行

slaveof <masterip> <masterport>

授权访问

  • master配置文件设置密码

requirepass <password>

  • slave配置文件设置密码访问master

masterauth <password>

1.1.4.2 数据同步阶段

在slave初次链接master后,复制master中的所有数据到slave,将slave的数据库状态更新成master当前数据库状态。

  • slave请求同步数据
  • master创建RDB同步数据
  • slave接收并恢复RDB同步数据
  • slave请求部分同步数据(AOF)
    • slave在接收RDB数据的时候,可能又会有新的数据进入master,这部分新的数据的指令会被放在master的复制缓冲区,恢复完RDB数据后就会请求这部分数据的指令。
  • slave接收并恢复部分同步数据

注意:从slave开始请求同步数据到slave恢复完成RDB同步数据,这一阶段叫做 全量复制。而slave请求部分数据到恢复完成(AOF阶段),叫做部分复制或增量复制,详细过程如下图:
Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案
状态:

slave:
具有master端全部数据,包含RDB过程接收的数据
master:
保存slave在master复制缓冲区同步的位置(下次同步从该位置开始)

1.1.4.3 命令传播阶段

当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的状态,同步的动作称为命令传播。master将接受到的数据变更命令发送给slave,slave接受命令后执行命令。
总结:命令传播阶段的主要作用就是实时保持中从服务器的数据一致。

命令传播阶段出现的断网现象

  • 网络闪断闪连 - 忽略
  • 短时间网络中断 - 部分复制
  • 长时间网络中断 - 全量复制

1. 命令传播阶段的部分复制

部分复制的三个核心要素

  • 服务器的运行id (run id)
  • 主服务器的复制积压缓冲区
  • 主从服务器的复制偏移量

服务器的运行id (run id)

  • 概念
    服务器的运行Id是一个随机的十六进制字符,由40位字符组成。每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行Id
  • 实现方式
    服务器启动时,自动生成的。master首次连接slave时,会将自己的运行Id发送给slave,而slave会保存master的runid,通过info Server命令可以查看节点的runid。

2. 复制缓冲区

  • 概念
    • 复制缓冲区,又名复制积压缓冲区,是一个先进先出(FIFO)的队列,用于存储服务器执行过的命令,每次传播命令,master会将传播的命令记录下来,并存储在复制缓冲区
    • 复制缓冲区默认存储空间大小是1M,由于存储空间大小是固定的,当入队元素的数量大于队列长度时,最先入队的元素会被弹出,而新元素会被放入队列
  • 由来
    每台服务器启动时,如果开启有AOF或被连接成为master节点,就会创建复制缓冲区。
  • 组成
    • 偏移量
    • 字节值
  • 数据来源
    当master接收到主客户端的指令时,除了执行指令,还会将该指令储存到缓冲区。
  • 工作原理
    • 通过offset区分不同的slave当前数据传播的差异
    • master记录已发送的信息对应的offset
    • slave记录已接收的信息对应的offser

Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案
3. 复制偏移量

  • 概念
    一个数字,描述复制缓冲区中的指令字节位置
  • 分类
    • master复制偏移量:记录发送给所有slave的指令字节对应的位置(多个)
    • slave复制偏移量:记录slave接受master发送过来的指令字节对应的位置(一个)
  • 数据来源
    master端:发送一次记录一次
    slave端:接收一次记录一次
  • 作用
    同步信息,对比master与slave的差异,当slave断线后,恢复数据使用

1.1.4.4 数据同步和命令传播阶段详细流程图

Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案

1.1.4.5 心跳机制

进入命令传播阶段后,master与slave间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线。

  • master心跳
    • 指令:PING
    • 周期:由repl-ping-slave-period决定,默认10秒
    • 作用:判断slave是否在线
    • 查询: info命令,获取slave最后一次链接时间间隔,lag项维持在0或1视为正常
  • slave心跳指令
    • 指令:REPLCONF ACK{offset}
    • 周期:1秒
    • 作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令
    • 作用2:判断master是否在线

心跳阶段注意事项

  • 当slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步操作

min-slave-to-write 2
min-slave-max-lag 10

slave数量少于2个或者多有slave延迟都大于等于10秒时,强制关闭master写功能,停止数据同步

  • slave数量和延迟是由slave发送REPLCONF ACK命令做确认

完整流程图
Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案

1.2 哨兵模式

哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master。

1.2.1 哨兵的作用

  • 监控
    不断地检查master和slave是否正常运行。
    master存活检测、master与slave运行情况检测。
  • 通知(提醒)
    当被监控的服务器出现问题时,向其他(哨兵,客户端)发送通知
  • 自动故障转移
    断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址。

注意:哨兵也是一台redis服务器,只是不提供数据服务,通常哨兵配置数量为单数

1.2.2 哨兵的工作原理

1). 主从切换
哨兵在主从切换中经历三个阶段:

  • 监控
  • 通知
  • 故障转移

1.1) 监控阶段

  • 同步各个节点的状态信息
    • 获取各个sentinel的状态(是否在线)- ping
    • 获取master的状态 - info
      • master属性
        • runid
        • role:master
      • 各个slave的详细信息
    • 获取所有slave的状态(根据master中的slave信息)- info
      • slave属性
        • runid
        • role:slave
        • master_host、master_port
        • offset

        • Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案

1.2) 通知阶段
当被监控的服务器出现问题时,向其他(哨兵,客户端)发送通知,保证每个哨兵信息的对等。
Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案
1.3) 故障转移阶段
太复杂了,记不住。想了解的同学,去百度吧。

1.3 集群

1.3.1 简介

现状问题
业务发展过程中遇到的峰值瓶颈

  • redis提供的服务OPS可以达到10万/秒,当前业务OPS已经达到20万/秒
  • 内存单机容量达到256G,当前业务需求内存容量1T

集群架构
集群就是使用网络将若干台计算机联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果。

集群的作用

  • 分散单台服务器的访问压力,实现负载均衡
  • 分散单台服务器的存储压力,实现可扩展性
  • 降低单台服务器宕机带来的业务灾难

1.3.2 集群的搭建

集群具体怎么搭建的,其实也不难,百度一下就可以了,这里就不写具体步骤了,只描述下注意事项。

  • 搭建集群至少需要六台服务器(至少3主,每个主服务器至少1个从服务器)
  • 启动集群命令
    redis-cli --cluster create --cluster-replicas 1 172.16.88.168:6379 172.16.88.168:6380 172.16.88.168:6381 172.16.88.168:6389 172.16.88.168:6390 172.16.88.168:6391
    • cluster-replicas 1:代表每台主服务器都要有1台从服务器
    • 这6台redis服务器前3个会设置成主,后3个设置成从;如果是8台的话,前4台主,后4台从。

1.3.3 进群中数据是如何存储的?

由于主服务器有多台,当添加新数据的时候,具体会保存到哪台主服务器呢?

  • 一个 Redis 集群包含 16384 个插槽
  • 每个主服务器会分配一部分插槽
  • 新添加一条数据时,会根据key计算出保存到哪个插槽。

Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案
录入或读取数据
在 redis-cli 每次录入、查询键值,redis 都会计算出该 key 应该送往的插槽,如果不是该客户端对应服务器的插槽,redis 会报错,并告知应前往的 redis 实例地址和端口。

  • 录入/读取 数据的时候可以通过 redis-cli -c 命令实现重定向,如果录入/读取 的key计算出的插槽不在该服务器会自动重定向到正确的服务器。
    Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案

1.3.4 主从切换/故障恢复

在一个集群中,如果一台主服务器挂了,redis是怎么处理的?如果主服务器挂了超过一定时间从服务器会晋升为主服务器,原来的主服务器再次上线的话会变成从服务器。配置如下:

  • 设置节点失联事件,超过该时间(ms),集群自动进行主从切换

cluster-node-timeout 15000

2. 企业级解决方案

2.1 缓存雪崩(多个key过期)

1. 出现原因
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

2. 解决方案

  • 构建多级缓存架构
    nginx缓存+redis缓存+ehcache缓存
  • 使用队列或锁
    高并发情况下不适用
  • 让缓存失效时间分散开
    比如在原有的失效时间上加个1-5分钟的随机值。
  • 热点数据特殊处理
    如果是临时热点数据,比如秒杀,就让秒杀结束后再失效。
    如果一直都是热点数据,就设置永久key。

3. 总结
解决方案各种各样,只要能保证不要让key短时间内集中失效就可以,特别是热点数据。

2.2 缓存击穿(单个key过期)

1. 出现原因
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,缓存中读不到数据就去数据库读取,引起数据库压力瞬间增大,造成过大压力。

2. 解决方案

  • 预先设置热点数据
    在一些热点数据高峰访问之前,提前加入redis缓存,延长过期时间。
  • 使用锁
    如果请求到redis中的数据为空就加锁,获取到锁再去判断下redis中有没有数据,多个请求进来的只要一个获取到锁,这样就只有这一个去请求数据库,请求到数据库后放入缓存,然后释放锁。其他线程获取到锁后回再去判断redis,这时候redis已经有值了,就不会去请求数据库了。只不过用锁回降低效率,要慎用。

3. 总结
在涉及架构的时候,提前考虑到这些问题,尽量避免问题的发送。一旦发送了缓存击穿,根据情况去解决,解决方案也不是一成不变的,能解决问题就好。

2.3 缓存穿透

出现原因
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,导致数据库频繁请求不存在得数据,数据库压力增大。这种情况很可能是黑客攻击。

解决方案

  • 对空值进行缓存
    对数据库查询位null得数据也进行缓存,value就位null,然后设置个短暂的过期时间。
  • 白名单策略
    使用bitmaps类型定义一个可访问的白名单,名单id作为bitmaps的偏移量,每次访问和bitmaps里面的id进行比较,如果访问id不在bitmaps里面就进行拦截。

2.4 分布式锁

分布式锁常见的三种实现方式有:redis、zeekooper、数据库。这里只分析Redis实现分布式锁的方式。

Redis实现分布式锁
通过setnx命令设置锁,通过del命令删除锁。

问题1:锁一直不释放怎么办?
比如加锁之后代码出现异常,导致后面的del命令没有执行,锁一直不会释放怎么办?

解决方案
给锁设置过期时间,指定时间没有释放就自动释放。

expire key second


问题2:过期时间设置失败怎么办?
比如加完锁了,代码刚运行到设置过期时间还未执行,这是时候服务器宕机了,导致设置过期时间失败。

解决方案
通过一条命令,在设置锁的同时设置过期时间。

#nx:key不存在时才生效;ex:过期时间
set key value nx ex second


问题3:释放了别人的锁怎么办(1)?
假如A线程获取到了锁也设置了过期时间了,在执行的过程中卡住了,时间到了就自动释放锁了。这时B线程抢到了锁,正在执行,A线程反应过来了继续往下执行,执行完了要手动释放锁,这时锁在B线程呢,那么A就会把B线程的锁释放掉,这种问题怎么办?

解决方案
设置锁的时候将value设置为UUID,这样没过线程获取到的锁的值都是不一样的,手动释放锁的时候先判断下释放的锁的value和自己拥有锁的value是否相同,相同才释放。

代码效果如下:
Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案


问题4:释放了别人的锁怎么办(2)?
在问题3中,手动释放锁的时候需要通过UUID进行比较防止误删。但是,假如A线程刚比较完,自动释放时间就到了,自动释放了,然后B线程快速获取到了锁,而A线程比较完就执行手动操作了,这时A线程就会把B线程的锁给释放掉?

解决方案
要保证比较UUID和手动释放锁的原子性,通过lua(撸啊)脚本可以保证他们的原子性。

#这句话的意思时判断redis中所得value跟我们传过来的是否相同,相同就调用删除key的方法,不相同就返回0
if redis.call(‘get’,KEYS[1])==ARGV[1] then return redis.call(‘del’,KEYS[1]) else return 0 end

如下图:
Redis 手把手教程(3/3) - Redis集群及常见企业级解决方案


其他说明
通过Redisson的看门狗机制也可以实现分布式锁,当一个锁快过期的时候,看门狗会自动延长有效期。


总结
为了确保分布式锁可用,实现分布式锁的时候至少要确保满足以下4个条件:

  • 互斥性。在任意时刻,只有一个客户端能拥有锁。
  • 不会发生死锁。即使一个客户端持有锁的期间崩溃没有手动释放锁,也要保证后续其他客户端依然可以获取锁。
  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端不能把别人的锁给释放了。
  • 加锁和解锁必须具有原子性。加完锁,在解锁之前,其他客户端不能干扰。
上一篇:腾讯发布物联网安全技术规范


下一篇:(04)Eclipse中使用Git