Redis核心原理与Redis6新特性


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数据备份策略

  1. 写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份
  2. 每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
  3. 每次copy备份的时候,都把太旧的备份给删了
  4. 每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏

Redis数据恢复

将之前备份的rdb或aof文件copy到当前redis配置下的.dir目录,数据自动恢复

Redis高性能

大家都知道Redis是单线程的,但单线程为什么Redis性能这么高?

首先Redis单线程只是从网络IO和读写操作是单线程的。但Redis的持久化,AOF重写,集群数据同步这些都是另外的线程操作的。

Redis性能高主要有以下几个原因

  1. 所有数据都是在内存操作的,内存运算速度不必多说。
  2. 单线程避免了线程上下文切换,其实多线程并不一定比单线程块,线程太多了上下文切换太频繁性能可能还比不上单线程

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的主从架构不支持高可用,也就是主节点宕机从节点不会顶上去,一般生产不会单纯的使用主从架构,要么哨兵或集群架构

全量复制

  1. 当一个slave节点启动时会向master节点发送一个psync命令
  2. master收到命令就开始持久化数据生产dumb.rdb文件(这里不管他是否开启或关闭rdb持久化)
  3. 持久化过程中,redis会继续接受请求,这些修改的数据缓存在内存
  4. 持久化完毕后会将rdb数据发给slave,slave收到数据会持久化数据,然后加载到内存,然后master将之前缓存的数据发给slave
  5. 后面master与slave建立长连接同步数据,保持数据一致

部分复制

master与slave会在网络断开重连后进行部分复制

  1. master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据
  2. master和它所有的slave都维护了复制的数据下标offset和master进程id
  3. 网络连接断开后,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

职责

  1. 哨兵集群会重新选举出新的redis主节点
  2. 客户端只需连接哨兵便可访问主从,主从的变化对客户端是不可见的

缺陷

哨兵架构虽然一定程度解决了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不宜配置太小,否则会频繁进行主从切换

选举过程:

  1. slave发现自己的master节点挂了,会发起选举,由于master的slave不仅只有一个slave此时就会开始选举
  2. slave将自己的当前选举周期加1,并广播消息
  3. 其他小集群的master节点收到消息,会返回第一个发起请求的节点ack,注意这里在一个选举周期内只会发送一次ack
  4. slave统计自己收到的ack是否超过整个redis大集群master个数的一半,超过一半选举为master
  5. 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节点更多的是从节省机器资源角度出发说的


文章作者: dm
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 dm !
评论
 上一篇
Sentinel基本使用 Sentinel基本使用
前言在一个微服务系统中,经常会出现一个服务依赖于多个服务,而一个调用逻辑会调用多个服务的情况出现。如果出现某个服务提供者出现故障不可用,就会导致服务消费者不可用,又由于是同步调用最后线程全部阻塞在服务消费者身上,最后导致服务消费者也不可用。
2022-12-01
下一篇 
Redis生产实践与性能优化 Redis生产实践与性能优化
缓存穿透缓存穿透是指访问一个不存在的key(这个key不在缓存层),穿透了缓存层直接打到了DB,如果访问量大的话是有把DB打崩的可能性。 解决方案: 缓存空对象,即使访问的是一个不存在的对象,我们也可以吧访问的key值缓存,value直接
2022-11-01
  目录