Redis持久化
Redis持久化共有3种模式RDB、AOF和Redis4.0之后出来的混合持久化。
RDB
RDB持久化是Redis的默认选择。RDB模式中Redis将持久化文件存储在dump.rdb(配置)二进制文件中。可以对Redis进行设置,在N秒内数据集至少有M次改动,自动保存一次数据集(配置save);关闭RDB模式只需将所有save策略注释掉即可。
还可以手动执行命令生成RDB快照,进入Redis客户端执行命令save或bgsave可以生成dump.rdb文件;每次命令执行都会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件
bgsave(Copy-On-Write)
默认生成方式bgsave;bgsave运用了写时复制技术(Copy-On-Write),简单来说redis在生成快照文件的时候Redis还能做写入操作。具体实现是,主线程会fork一个bgsave的子进程,bgsave子进程会读取主线程中的内存数据并把他们写入快照文件中,这时候如果Redis发生了写数据,这时候修改的数据会被写成一个副本,然后bgsave会把副本数据写入快照文件,如果发生的是读数据,就不用管。
save
save就是直接对Redis进行阻塞,不允许任何写数据请求,然后生成快照
| save | bgsave | |
|---|---|---|
| 是否阻塞redis其它命令 | 阻塞 | 非阻塞 |
| IO | 同步 | 异步 |
| 优点 | 不会消耗额外内存 | 不阻塞客户端命令 |
| 缺点 | 阻塞客户端命令 | 需要fork子进程,消耗内存 |
相关配置
## RDB 文件
dbfilename "dump.rdb"
## 持久化目录
dir "/data/redis"
## 60 秒内有至少有 10000 个键被改动 执行一次bgsave
save 60 10000
文件结构

AOF
RDB模式从机制也可以看出丢数据的可能性极大。如果Redis停机就会导致没有写到快照文件的数据丢失。后面Redis增加了一种AOF持久化方案。它存储的是每一条指令而不是二进制文件;存储文件是appendonly.aof;
aof文件存储结构

这是一种resp协议格式数据,星号后面的数字代表命令有多少个参数,$号后面的数字代表这个参数有几个字符
AOF重写
AOF文件里可能有太多没用指令,所以AOF会定期根据内存的最新数据生成aof文件比如set a 1;set a 2;set a 3;会重写成set a 3
AOF还可以手动重写,进入redis客户端执行命令bgrewriteaof重写AOF
AOF重写redis会fork出一个子进程去做(与bgsave命令类似),不会对redis正常命令处理有太多影响
相关配置
## aof文件名称
appendfilename "appendonly.aof"
## 持久化目录
dir "/data/redis"
## 开启aof持久化
appendonly yes
## 每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全
appendfsync always
## 每秒fsync一次,足够快,并且在故障时只会丢失 1 秒钟的数据
appendfsync everysec
## 从不fsync,将数据交给操作系统来处理。更快,也更不安全的选择
appendfsync no
## AOF重写相关配置
## aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就很快,重写的意义不大
auto-aof-rewrite-min-size 64mb
## aof文件自上一次重写后文件大小增长了100%则再次触发重写
auto-aof-rewrite-percentage 100
RDB,AOF对比
| RDB | AOF | |
|---|---|---|
| 启动优先级 | 低 | 高 |
| 恢复速度 | 快 | 慢 |
| 数据安全性 | 容易丢数据 | 策略决定 |
| 体积 | 小 | 大 |
生产环境可以都启用,redis启动时如果既有rdb文件又有aof文件则优先选择aof文件恢复数据,因为aof一般来说数据更全一点。
混合持久化
混合持久化是Redsis4.0之后引入的。生产环境我们一般会利用AOF来恢复数据,即使RDB恢复数据更快但它不安全,会丢失数据。但AOF有一个致命的缺点就是恢复速度慢。混合持久化就是解决这个问题。
混合持久化是AOF的升级版,让AOF有了RDB的优点;
在执行AOF重写时,不在是将Resp协议简单的转化了,而是将这一刻之前的内存数据转化为RDB二进制文件,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一起都写入新的AOF文件,新的文件一开始不叫appendonly.aof;等到重写完新的AOF文件才会进行改名,覆盖原有的AOF文件,完成新旧两个AOF文件的替换.
于是aof文件不仅仅是存储的Resp协议,而是Resp和二进制文件。

相关配置
开启混合持久化必须先启动AOF
## 开启混合持久化
aof-use-rdb-preamble yes
Redis数据备份策略
- 写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份
- 每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
- 每次copy备份的时候,都把太旧的备份给删了
- 每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏
Redis数据恢复
将之前备份的rdb或aof文件copy到当前redis配置下的.dir目录,数据自动恢复
Redis高性能
大家都知道Redis是单线程的,但单线程为什么Redis性能这么高?
首先Redis单线程只是从网络IO和读写操作是单线程的。但Redis的持久化,AOF重写,集群数据同步这些都是另外的线程操作的。
Redis性能高主要有以下几个原因
- 所有数据都是在内存操作的,内存运算速度不必多说。
- 单线程避免了线程上下文切换,其实多线程并不一定比单线程块,线程太多了上下文切换太频繁性能可能还比不上单线程
Redis的IO多路复用
Redis怎么处理大量客户端的连接?
当大量的客户端连接连接Redis,Redis利用epoll实现多路复用,将连接信息放入队列,然后取队列中的连接,分发给不同的处理器。大概原理类似于NIO
注意:Redis是单线程就需要避免耗时操作,和存储大key,避免阻塞线程,像类似于一般keys这种命令要禁止使用
线上生产QPS一般比较高,执行耗时操作会阻塞住redis,大量连接请求Redis会Hang住,导致线上CPU剧增,导致服务器宕机,服务器宕机Redis不能请求导致请求打到DB,最后数据库宕机
如果真要用keys 可以改成scan
禁用命令(重定义命令)
redis.conf
rename-command KEYS ""
Redis主从复制
Redis主从架构一般只是用于备份数据,做的更多的就是通过程序实现读写分离(Redis自身不支持,需实现);Redis的主从架构不支持高可用,也就是主节点宕机从节点不会顶上去,一般生产不会单纯的使用主从架构,要么哨兵或集群架构
全量复制
- 当一个slave节点启动时会向master节点发送一个psync命令
- master收到命令就开始持久化数据生产dumb.rdb文件(这里不管他是否开启或关闭rdb持久化)
- 持久化过程中,redis会继续接受请求,这些修改的数据缓存在内存
- 持久化完毕后会将rdb数据发给slave,slave收到数据会持久化数据,然后加载到内存,然后master将之前缓存的数据发给slave
- 后面master与slave建立长连接同步数据,保持数据一致
部分复制
master与slave会在网络断开重连后进行部分复制
- master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据
- master和它所有的slave都维护了复制的数据下标offset和master进程id
- 网络连接断开后,slave会请求master继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制
主从复制风暴
主节点有多个slave节点,都同时发送psync命令给master导致主节点压力过大这就是主从复制风暴。
Redis哨兵高可用架构
哨兵架构其实就是对Redis主从的一次升级,redis主从架构有个缺陷就是不是高可用,哨兵架构可以解决这种问题
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。
哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
哨兵leader选举流程
// TODO
职责
- 哨兵集群会重新选举出新的redis主节点
- 客户端只需连接哨兵便可访问主从,主从的变化对客户端是不可见的
缺陷
哨兵架构虽然一定程度解决了Redis的可用性,但还是有一定的访问瞬断问题,而且哨兵模式只有一个主节点对外提供服务,没法支持很高的并发,且单个主节点内存也不宜设置得过大,否则会导致持久化文件过大,影响数据恢复或主从同步的效率
架构

Redis高可用集群架构
Redis3.0以后引入了集群功能,Redis高可用集群是由多个主从小集群组合而成是去中心的,可水平扩展,主要功能可以进行数据分片,主从小集群选举等功能。
数据分片
Redis集群将数据划分为16384个slots槽位。每个节点负责一部分槽位,槽位存储于每个节点中。客户端连接Redis集群时会将槽位分配情况缓存在客户端,方便客户端定位到目标节点。
槽位分配情况有时会和客户端缓存不一致,这时候就需要槽位校验调整。当客户端发到错误的数据节点的时候,服务端会向客户端发送一个跳转指令并带着正确的节点地址,客户端转向正确的目标节点发送数据命令并更新本地槽位缓存。
槽位定位算法:HASH_SLOT = CRC16(key) mod 16384
Redis集群节点间的通信机制
Redis集群节点间通过gossip协议进行通信。
gossip协议进行通信是断断续续的,所以所有元数据的更新不是及时的,缺点是所有节点跟新会有一定的延迟,优点是降低了节点压力。
- meet:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信
- ping:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据(类似自己感知到的集群节点增加和移除,hash slot信息等)
- pong: 对ping和meet消息的返回,包含自己的状态和其他信息,也可以用于信息广播和更新
- fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了
Redis集群选举原理
redis配置文件有一个这样的配置cluster-node-timeout,这个配置表示当某个节点timeout时间失联表示这个节点宕了,需要进行主从切换,注这个timeout不宜配置太小,否则会频繁进行主从切换
选举过程:
- slave发现自己的master节点挂了,会发起选举,由于master的slave不仅只有一个slave此时就会开始选举
- slave将自己的当前选举周期加1,并广播消息
- 其他小集群的master节点收到消息,会返回第一个发起请求的节点ack,注意这里在一个选举周期内只会发送一次ack
- slave统计自己收到的ack是否超过整个redis大集群master个数的一半,超过一半选举为master
- slave广播消息给其他所有节点我已经是master,你们不要选举了
这里有个问题如果每个slave节点收到的ack个数是一样的,怎么办?
这时候slave会将自己的选举周期加1再来一次选举。Redis为了避免这个情况在slave发送请求给其他master的时候有个延时机制并不是立刻发送的,
延时时间*DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms*
其中SLAVE_RANK表示此slave已经从master复制数据的总量的rank,rank越小数据越新延迟时间越短,选举为master几率更高。
Redis集群对批量操作命令的支持
原来的单机节点所有的key都落在一个节点上批量命令没有问题,但现在是集群我们对于批量命令一些key不知道落在哪个节点上,就会出现问题,所以对于类似mset,mget这样的多个key的原生批量操作命令,redis集群只支持所有key落在同一slot的情况
如果有多个key一定要用mset命令在redis集群上操作,则可以在key的前面加上{XX},这样参数数据分片hash计算的只会是大括号里的值,这样能确保不同的key能落到同一slot里去
示例如下:
mset {user}:1:name dm {user}:1:age 18
假设name和age计算的hash slot值不一样,但是这条命令在集群下执行,redis只会用大括号里的 user1 做hash slot计算,所以算出来的slot值肯定相同,最后都能落在同一slot。
Q&A
Redis如何解决脑裂问题
在任何的主从架构不可避免的都会遇到脑裂问题。
Redis没有解决脑裂问题的,所以一定程度上是有脑裂导致的丢失数据的情况。
原因分析
现redis大集群小其中一个小集群(1主2从)主节点由于分区原因(网络抖动)导致slave认为master挂了(其实没挂),slave开始选举,成功选举了一个新master.现在这个小集群有2个master,数据也会写到这2个master中,当网络分区恢复原来的master变为slave,原来master写入的数据就丢失了。
解决方案
redis有个配置min-replicas-to-write 1
配置表示写数据成功最少同步的slave数量,这个数量可以模仿大于半数机制配置,比如集群总共三个节点可以配置1,加上leader就是2,超过了半数
这样原来的问题就不会发生了,因为网络分区的原来的master节点不会再写入数据了。不过不建议这么用,因为redis本身最好用的就是高性能,现在这样性能必然不会太高,而且数据丢失对于缓存而言不算什么大不了再从DB去拿呗
集群是否完整才能对外提供服务
这个问题主要看Redis配置
redis有个配置cluster-require-full-coverage为no时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用,如果为yes则集群不可用。
Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?
因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中一个挂了,是达不到选举新master的条件的。
奇数个master节点可以在满足选举该条件的基础上节省一个节点,比如三个master节点和四个master节点的集群相比,大家如果都挂了一个master节点都能选举新master节点,如果都挂了两个master节点都没法选举新master节点了,所以奇数的master节点更多的是从节省机器资源角度出发说的