硬核!Redis知识总结,建议收藏
导语 | 最近线上因为redis出现了一些故障,总结一下redis知识点,下次及早发现问题。
一、Redis概览
Redis和memcache的区别,Redis支持的数据类型应用场景
redis支持的数据结构更丰富(string,hash,list,set,zset)。memcache只支持key-value的存储。
redis原生支持集群,memcache没有原生的集群模式。
二、Redis单线程模型
(一)redis单线程处理请求流程
redis采用IO多路复用机制来处理请求,采用reactor IO模型,处理流程如下:
首先接收到客户端的socket请求,多路复用器将socket转给连接应答处理器。
连接应答处理器将AE_READABLE事件与命令请求处理器关联(这里是把socket事件放入一个队列)。
命令请求处理器从socket中读到指令,再内存中执行,并将AE_WRITEABLE事件与命令回复处理器关联。
命令回复处理器将结果返回给socket,并解除关联。
(二)redis单线程效率高的原因
非阻塞IO复用(上图流程), I/O多路复用分派事件,事件处理器处理事件(这个可以理解为注册的一段函数,定义了事件发生的时候应该执行的动作), 这里分派事件和处理事件其实都是同一个线程。
纯内存操作效率高。
单线程反而避免了多线程切换。
三、Redis过期策略
(一)对key设置有效期,redis的删除策略: 定期删除+惰性删除。
定期删除指的是redis默认每100ms就随机抽取一些设置了过期事件的key,检查是否过期,如果过期就删除。如果redis设置了10万个key都设置了过期时间,每隔几百毫秒就要检查10万个key那CPU负载就很高了,所以redis并不会每隔100ms就检查所有的key,而是随机抽取一些key来检查。
但这样会导致有些key过期了并没有被删除,所以采取了惰性删除。意思是在获取某个key的时候发现过期了,如果key过期了就删除掉不会返回。
这两个策略结合起来保证过期的key一定会被删除
(二)最大内存淘汰(maxmemory-policy)
如果redis内存占用太多,就会进行内存淘汰。有如下策略:
noeviction: 如果内存不足以写入数据, 新写入操作直接报错。
allkeys-lru: 内存不足以写入数据,移除最近最少使用的key(最常用的策略)。
allkeys-random: 内存不足随机移除几个key。
volatile-lru: 在设置了过期时间的key中,移除最近最少使用。
volatile-random: 设置了过期的时间的key中,随机移除几个。
四、Redis主从模式保证高并发
和高可用(哨兵模式)
(一)读写分离
单机的Redis的QPS大概就在上万到几万不等,无法承受更高的并发。读写分离保证高并发(10W+QPS):对于缓存来说一般都是支撑高并发读,写请求都是比较少的。采用读写分离的架构(一主多从),master负责接收写请求,数据同步到slave上提供读服务,如果遇到瓶颈只需要增加slave机器就可以水平扩容。
(二)主从复制机制
redis replication机制:
redis采取异步复制到slave节点。
slave节点做复制操作的时候是不会block自己的,它会使用旧的数据集来提供服务,复制完成后,删除旧的数据集,加载新的数据集,这个时候会暂停服务(时间很短暂)。
如果采用了主从架构,master需要开启持久化。如果master没有开启持久化(rdb和aof都关闭了)。master宕机重启后数据是空的,然后经过复制就把所有slave的数据也弄丢了。即使采用高可用的的哨兵机制,可能sentinal还没有检测到master failure,master就自动重启了,还是会导致slave清空故障。
(三)主从同步流程
当slave启动时会发送一个psync命令给master。
如果是重新连接master,则master node会复制给slave缺少的那部分数据。
如果是slave第一次连接master,则会触发一次全量复制(full resynchronization)。开始full resynchronization 的时候,master会生成一份rdb快照,同时将客户端命令缓存在内存,rdb生成完后,就发送给slave,slave先写入磁盘在加载到内存。然后master将缓存的命令发送给slave。
(三)哨兵(sentinal)模式介绍
哨兵是redis集群架构的一个重要组件,主要提供如下功能:
集群监控:负责监控master和slave是否正常工作。
消息通知:如果某个redis实例有故障,哨兵负责发消息通知管理员。
故障转移: 如果master node发生故障,会自动切换到slave。
配置中心:如果故障转移发生了,通知客户端新的master地址。
哨兵的核心知识:
哨兵至少三个,保证自己的高可用。
哨兵+主从的部署架构是用来保证redis集群高可用的,并非保证数据不丢失。
哨兵(Sentinel)需要通过不断的测试和观察才能保证高可用。
为什么哨兵只有两个节点无法正常工作
假设哨兵集群只部署了2个哨兵实例,quorum=1 master宕机的时候,s1和s2只要有一个哨兵认为master宕机j就可以进行切换,并且会从s1和s2中选取一个来进行故障转移。这个时候是需要满足majority,也就是大多数哨兵是运行的,2个哨兵的majority是2,如果2个哨兵都运行着就允许执行故障转移。如果M1所在的机器宕机了,那么s1哨兵也就挂了,只剩s2一个,没有majorityl来允许执行故障转移,虽然集群还有一台机器R1,但是故障转移也不会执行。如果是经典的三哨兵集群,如下:
此时majority也是2,就算M1所在的机器宕机了,哨兵还是剩下两个s2和s3,它们满足majority就可以允许故障转移执行。
哨兵核心底层原理
sdown和odown两种失败状态:
sdown是主观宕机,就是一个哨兵觉得master宕机了,达成条件是如果一个哨兵ping master超过了is-master-down-after-milliseconds指定的毫秒数后就认为主观宕机。
odown是客观宕机,如果一个哨兵在指定时间内收到了majority(大多数) 数量的哨兵也认为那个master宕机了,就是客观宕机。
哨兵之间的互相发现:哨兵是通过redis的pub/sub实现的。
五、Redis数据的恢复(Redis的持久化)
(一)RDB
RDB原理
RDB(Redis DataBase)是将某一个时刻的内存快照(Snapshot),以二进制的方式写入磁盘的过程。RDB有两种方式save和bgsave:
save: 执行就会触发Redis的持久化,但同时也是使Redis处于阻塞状态,直到RDB持久化完成,才会响应其他客户端发来的命令
bgsave: bgsave会fork()一个子进程来执行持久化,整个过程中只有在 fork() 子进程时有短暂的阻塞,当子进程被创建之后,Redis的主进程就可以响应其他客户端的请求了。
RDB配置
除了使用save和bgsave命令触发之外,RDB支持自动触发。自动触发策略可配置Redis在指定的时间内,数据发生了多少次变化时,会自动执行bgsave命令。在redis配置文件中配置:
m 秒内,如果 Redis 数据至少发生了 n 次变化,那么就自动执行BGSAVE命令。
save m n
RDB优缺点
RDB的优点:
RDB会定时生成多个数据文件,每个数据文件都代表了某个时刻的redis全量数据,适合做冷备,可以将这个文件上传到一个远程的安全存储中,以预定好的策略来定期备份redis中的数据。
RDB对redis对外提供读写服务的影响非常小,redis是通过fork主进程的一个子进程操作磁盘IO来进行持久化的。
相对于AOF,直接基于RDB来恢复reids数据更快。
RDB的缺点:
如果使用RDB来恢复数据,会丢失一部分数据,因为RDB是定时生成的快照文件。
RDB每次来fork出子进程的时候,如果数据文件特别大,可能会影响对外提供服务,暂停数秒(主进程需要拷贝自己的内存表给子进程, 实例很大的时候这个拷贝过程会很长)
latest_fork_usec代表fork导致的延时
Redis上执行INFO命令查看latest_fork_usec
当RDB比较大的时候, 应该在slave节点执行备份, 并在低峰期执行
(二)AOF
AOF原理
redis对每条写入命令进行日志记录,以append-only的方式写入一个日志文件,redis重启的时候通过重放日志文件来恢复数据集。(由于运行久了AOF文件会越来越大,redis提供一种rewrite机制,基于当前内存中的数据集,来构建一个更小的AOF文件,将旧的庞大的AOF文件删除) rewrite即把日志文件压缩, 通过bgrewriteaof触发重写AOF rewrite后台执行的方式和RDB有类似的地方,fork一个子进程,主进程仍进行服务,子进程执行AOF 持久化,数据被dump到磁盘上。与RDB不同的是,后台子进程持久化过程中,主进程会记录期间的所有数据变更(主进程还在服务),并存储在server.aof_rewrite_buf_blocks中;后台子进程结束后,Redis更新缓存追加到AOF文件中,是RDB持久化所不具备的AOF的工作流程如下:
Redis执行写命令后,把这个命令写入到AOF文件内存中(write系统调用)。
Redis根据配置的AOF刷盘策略,把AOF内存数据刷到磁盘上(fsync系统调用)。
根据rewrite相关的配置触发rewrite流程。
AOF配置
appendonly: 是否启用AOF(yes|no)
appendfsync: 刷盘的机制
always:主线程每次执行写操作后立即刷盘,此方案会占用比较大的磁盘IO资源,但数据安全性最高。
everysec:主线程每次写操作只写内存就返回,然后由后台线程每隔1秒执行一次刷盘操作(触发fsync系统调用),此方案对性能影响相对较小,但当Redis宕机时会丢失1秒的数据。
no:主线程每次写操作只写内存就返回,内存数据什么时候刷到磁盘,交由操作系统决定,此方案对性能影响最小,但数据安全性也最低,Redis 宕机时丢失的数据取决于操作系统刷盘时机。
auto-aof-rewrite-percentage: 当aof文件相较于上一版本的aof文件大小的百分比达到多少时触发AOF重写。举个例子,auto-aof-rewrite-percentage选项配置为100,上一版本的aof文件大小为100M,那么当我们的aof文件达到200M的时候,触发AOF重写。
auto-aof-rewite-min-size:最小能容忍aof文件大小,超过这个大小必须进行AOF重写。
AOF优缺点
AOF的优点:
可以更好的保证数据不丢失,一般AOF每隔1s通过一个后台线程来执行fsync(强制刷新磁盘页缓存),最多丢失1s的数据。
AOF以append-only的方式写入(顺序追加),没有磁盘寻址开销,性能很高。
AOF即使文件很大, 触发后台rewrite的操作的时候一般也不会影响客户端的读写,(rewrite的时候会对其中指令进行压缩,创建出一份恢复需要的最小日志出来)在创建新的日志文件的时候,老的文件还是照常写入,当新的文件创建完成后再交换新老日志。
但是还是有可能会影响到主线程的写入, 如:
当磁盘的IO负载很高,那这个后台线程在执行AOF fsync刷盘操作(fsync系统调用)时就会被阻塞住, 紧接着,主线程又需要把数据写到文件内存中(write系统调用),但此时的后台子线程由于磁盘负载过高,导致fsync发生阻塞,迟迟不能返回,那主线程在执行write系统调用时,也会被阻塞住,直到后台线程fsync执行完成后,主线程执行write才能成功返回。这时候主线程就无法响应客户端的请求,可能会导致客户端请求redis超时。
具体类似: https://blog.csdn.net/mmgithub123/article/details/124507846
AOF日志文件通过非常可读的方式进行记录,这个特性适合做灾难性的误操作的紧急恢复,比如不小心使用flushall清空了所有数据,只要rewrite没有发生,就可以立即拷贝AOF,将最后一条flushall命令删除,再回放AOF恢复数据
AOF的缺点:
同一份数据,因为AOF记录的命令会比RDB快照文件更大。
AOF开启后,支持写的QPS会比RDB支持写的QPS要低,毕竟AOF有写磁盘的操作。
(三)总结
总结AOF和RDB该如何选择两者综合使用,将AOF配置成每秒fsync一次。RDB作为冷备,AOF用来保证数据不丢失的恢复第一选择,当AOF文件损坏或不可用的时候还可以使用RDB来快速恢复
六、Redis集群模式(redis cluster)
在主从部署模式上,虽然实现了一定程度的高并发,并保证了高可用,但是有如下限制:
master数据和slave数据一模一样,master的数据量就是集群的限制瓶颈。
redis集群的写能力也受到了master节点的单机限制。
在高版本的Redis已经原生支持集群(cluster)模式,可以多master多slave部署,横向扩展Redis集群的能力。Redis Cluster支持N个master node,每个master node可以挂载多个slave node。
(一)redis cluster介绍
自动将数据切片,每个master上放一部分数据。
提供内置的高可用支持,部分master不可用时还是能够工作。
redis cluster模式下,每个redis要开放两个端口:6379和10000+以后的端口(如16379)。16379是用来节点之间通信的,使用的是cluster bus集群总线。cluster bus用来做故障检测,配置更新,故障转移授权。
(二)redis cluster负载均衡
redis cluster 采用一致性hash+虚拟节点 来负载均衡redis cluster有固定的16384个slot (2^14),对每个key做CRC16值计算,然后对16384 mod可以获取每个key的slot。redis cluster每个master都会持有部分 slot,比如三个master那么 每个master就会持有5000多个slot。hash slot让node的添加和删除变得很简单,增加一个master,就将其他master的slot移动部分过去,减少一个就分给其他master,这样让集群扩容的成本变得很低。
(三)cluster基础通信原理(gossip协议)
与集中式不同(如使用zookeeper进行分布式协调注册),redis cluster使用的是gossip协议进行通信。并不是将集群元数据存储在某个节点上,而是不断的互相通信,保持整个集群的元数据是完整的。gossip 协议所有节点都持有一份元数据,不同节点的元数据发生了变更,就不断的将元数据发送给其他节点,让其他节点也进行元数据的变更。
集中式的好处:元数据的读取和更新时效性很好,一旦元数据变化就更新到集中式存储,缺点就是元数据都在一个地方,可能导致元数据的存储压力。对于gossip来说:元数据的更新会有延时,会降低元数据的压力,缺点是操作是元数据更新可能会导致集群的操作有一些滞后。
(四)redis cluster主备切换与高可用
判断节点宕机:如果有一个节点认为另外一个节点宕机,那就是pfail,主观宕机。如果多个节点认为一个节点宕机,那就是fail,客观宕机。跟哨兵的原理一样。
对宕机的master,从其所有的slave中选取一个切换成master node,再次之前会进行一次过滤,检查每个slave与master的断开时间,如果超过了 cluster-node-timeout * cluster-slave-validity-factor就没有资格切换成master。
从节点选取:每个从节点都会根据从master复制数据的offset,来设置一个选举时间,offset越大的从节点,选举时间越靠前,master node开始给slave选举投票,如果大部分master(n/2+1)都投给了某个slave,那么选举通过(与zk有点像,选举时间类似于epochid)。
整个流程与哨兵类似,可以说redis cluster集成了哨兵的功能,更加的强大。
Redis集群部署相关问题redis机器的配置,多少台机器,能达到多少qps?
机器标准:8核+32G
集群: 5主+5从(每个master都挂一个slave)
效果: 每台机器最高峰每秒大概5W,5台机器最多就是25W,每个master都有一个从节点,任何一个节点挂了都有备份可切换成主节点进行故障转移。
脑裂问题哨兵模式下:master下挂载了3个slave,如果master由于网络抖动被哨兵认为宕机了,执行了故障转移,从slave里面选取了一个作为新的master,这个时候老的master又恢复了,刚好又有client连的还是老的master,就会产生脑裂,数据也会不一致,比如incr全局id也会重复。
redis对此的解决方案是:min-slaves-to-write 1至少有一个slave连接min-slaves-max-lag 10 slave与master主从复制延迟时间如果连接到master的slave数小于最少slave的数量,并且主从复制延迟时间超过配置时间,master就拒绝写入12.client连接redis多tcp连接的考量首先redis server虽然是单线程来处理请求, 但是他是多路复用的, 单tcp连接肯定是没有多tcp连接性能好, 多路复用一个io周期得到的就绪io事件越多, 处理的就越多。这也不是绝对的, 如果使用pipeline的方式传输, 单连接会比多连接性能好, 因为每一个 pipeline的单次请求过多也会导致单周期到的命令太多, 性能下降多少个连接比较合适这个问题, redis cluser控制在每个节点100个连接以内。
作者简介
郝斌
腾讯后台开发工程师
腾讯云后台开发工程师, 担任全国七次人口普查、全国健康码、国家税务总局电子发票云项目应用架构师, 对后台架构领域有比较丰富经验。
推荐阅读