- 是做灾难恢复,数据恢复,也可以归类到高可用的一个环节里面去,比如你的redis整个挂了,然后redis就不可用了,你要做的事情是让redis变得可用,尽快变得可用
- 大量的请求过来,缓存全部无法命中,在redis里根本找不到数据,这个时候就死定了,缓存雪崩问题,所有请求,没有在redis命中,就会去MySQL数据库这种数据源头中去找,一下子MySQL承接高并发,然后就挂了
- redis持久化做得好,备份和恢复做到企业级的程度,那么即使你的redis故障了,也可以通过备份数据,快速恢复,一旦恢复立即对外提供服务;
Redis持久化RDB
优点:
- RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的方式,每个文件都代表了某一个时刻完成的数据快照,非常适合做冷备
- RDB对Redis对外提供的读写服务,影响非常小,可以让Redis保持高性能,因为redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可
- 相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复Redis数据,更快加速
缺点:
- 如果想要在Redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦Redis进程宕机,那么会丢失最近5分钟的数据,这个问题,也是RDB最大的缺点,就是不适合做第一有限的恢复方案,如果你依赖RDB做第一优先恢复方案,会导致数据丢失的比较多。
- RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致客户端提供的服务暂停数毫秒,甚至数秒,一般不要让RDB的间隔太长,否则每次生成的RDB文件太大了,对Redis本身的性能可能会有影响的
配置:
- save 60 1000:每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,就是当前Redis内存中的完整地数据快照 ,这个操作也被称之为snapshotting,快照。也可以手动调用save或者bgsave命令,同步或异步执行RDB快照生成,save可以设置多个,就是多个snapshotting检查点,每到一个检查点,就会去check一下,是否有指定的key数量发生了变更,如果有,就生成一个新的dump.rdb文件。
工作流程:
- Redis根据配置自己尝试去生成rdb快照文件
- fork一个子进程出来
- 子进程尝试将数据dump到临时的rdb快照文件中
- 完成rdb快照文件的生成之后,就替换之前旧的快照文件,dump.rdb文件,每次生成一个新的快照,都会覆盖之前的老快照,dump.rdb只有一个
模拟实验:
- 通过redis-cli SHUTDOWN这种方式去掉redis,其实是一种安全退出的模式,redis在退出的时候会将内存中的数据立即生成一份完整的rdb快照
- 用kill -9粗暴杀死redis进程,模拟redis故障异常退出,导致内存数据丢失的场景,这次就发现,redis进程异常被杀掉,数据没有dump文件,几条最新的数据就丢失了
Redis持久化AOF
优点:
- AOF可以更好地保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据,每隔1秒,就执行一次fsync操作,保证os cache中的数据写入磁盘中,redis进程挂了,最多丢掉1秒钟的数据;
- AOF日志文件以Append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易损坏,即使文件尾部破损,也很容易修复;
- AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite log的时候,会对其中的指导进行压缩,会创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可;
- AOF日志文件的命令通过非常刻度的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AO文件放回去,就可以通过恢复机制,自动恢复所有数据;
缺点:
- 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大;
- AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的,如果你要保证一条数据都不丢失,也是可以的,AOF的fsync设置成每写入一条数据,fsync一次,那就完蛋了,redis的QPS效率大降;
- 做数据恢复的时候,会比较慢,还有做冷备,定期的备份,不太方便,可能要自己手写复杂的脚本去做,做冷备不太合适;
配置:
- AOF持久化,默认是关闭的,默认是打开的RDB持久化,appendonly yes,可以打开AOF持久化机制,在生产环境里面,一般来说AOF都是要打开的,除非你说随便丢个几分钟的数据也无所谓,打开AOF持久化机制之后,redis每次即受到一条写命令,就会写入日志文件中,当然是先写入os cache的,然后每隔一定时间再fsync一下
- 而且即使APF和RDB都开启了,redis重启的时候,也是优先通过AOF进行数据恢复的,因为AOF数据比较完整。
- 可以配置AOF的fsync策略,有三种策略可以选择:
- 一种是每次写入一条数据就执行一次fsync;
- 一种是每隔一秒执行一次fsync;
- 一种是不主动执行fsync;
① always:每次写入一条数据,立即将这个数据对应的写日志fsync到磁盘上去,性能非常差,吞吐量很低,确保说redis里的数据一条都不丢,那就只能这样了;
② mysql => 内存策略,大量磁盘,QPS到多少,一两k。(QPS,每秒钟的请求数量 )
redis => 内存,磁盘持久化,QPS到多少,单机,一般来说,上万QPS没问题
③ everysec:每秒将os cache中的数据fsync到磁盘,这个最常用的,生产环境一般都这么配置,性能很高,QPS还是可以上万的;
④ no:仅仅redis负责将数据写入os cache就撒手不管了,然后后面os自己会时不时有自己的策略将数据刷入磁盘,不可控了;
模拟实验:
- kill -9杀掉Redis进程,重新启动redis进程,发现数据被恢复回来了,从AOF文件中恢复回来的。在appendonly.aof文件中,可以看到刚写的日志,他们其实就是先写入os cache的,然后1秒后才fsync到磁盘中,只有fsync到磁盘中了,才是安全的,要不然光是在os cache中,机器只要重启,就什么都没有了;
- redis进程启动的时候,直接就会从appendonly.aof中加载所有的日志,把内存中的数据恢复回来;
AOF rewrite:
- redis中的数据其实有限的,很多数据可能会自动过期,可能会被用户删除,可能会被reids用缓存清除的算法清理掉;
redis中的数据会不断淘汰掉旧的,就一部分常用的数据会被自动保留在redis内存中;
所以可能很多之前的已经被清理掉的数据,对应的写日志还停留在AOF中,AOF日志文件就一个,会不断地膨胀,到很大很大;
所有AOF会自动在后台每隔一定时间做rewrite操作,比如日志里面已经存放了针对100W数据的写日志了;redis内存只剩下10w;基于内存中当前的10w数据构建一套最新的日志,到AOF中,覆盖之前的老日志,确保AOF日志文件不会过大,保持跟redis内存数据量一致;
- 在redis.conf中,可以配置rewrite策略:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
比如说上一次AOF rewrite之后,是128mb;然后就会接着128mb继续写AOF的日志,如果发现增长的比例,超过了之前的100%,256mb,就可能会去触发一次rewrite;但是此时还要去跟min-size,64mb去比较,256mb>64mb,才会去触发rewrite
比如说上一次AOF rewrite之后,是128mb;
然后就会接着128mb继续写AOF的日志,如果发现增长的比例,超过了之前的100%,256mb,就可能会去触发一次rewrite;
但是此时还要去跟min-size,64mb去比较,256mb>64mb,才会去触发rewrite
工作流程:
- redis fork一个子进程;
- 子进程基于当前内存中的数据,构建日志,开始往一个新的临时的AOF文件中写入日志;
- redis主进程,接收到client新的写操作之后,在内存中写入日志,同时新的日志也继续写入旧的AOF文件;
- 子进程写完新的日志文件之后,reids主进程将内存中的新日志再次追加到新的AOF文件中;
- 用新的日志文件替换掉旧的日志文件;
RDB VS AOF
区别:
- RDB:可以做冷备,生成多个文件,每个文件都代表了某一个时刻的完整的数据快照;
AOF:也可以做冷备,只有一个文件,但是你可以,每隔一定时间,去copy一份这个文件出来;
- RDB:每次写,都是直接写redis内存,只是在一定的时候,才会将数据写入磁盘中;
AOF:每次都是要写文件的,虽然可以快速写入os cache中,但是还是有一定的时间开销的,速度肯定比RDB策略慢一些;
- AOF:存放的指令日志,做数据恢复的时候,其实是要回放和执行所有的指令日志,来恢复出来内存中的所有数据的;
RDB:就是一份数据文件,恢复的时候,直接加载到内存中即可;
同时工作:
- 如果RDB在执行snapshotting操作,那么redis不会执行AOF rewrite;如果redis再执行AOF rewrite,那么就不会执行RDB snapshotting;
- 如果RDB在执行snapshotting操作,此时用户执行BGREWRITEAOF命令,那么等RDB快照生成之后,才回去执行AOF rewrite;
- 同时有RDB snapshot文件和AOF日志文件,那么redis重启的时候,会优先使用AOF进行数据恢复,因为其中的日志更完整;
Redis主从架构
主从核心机制
- redis采用异步方式复制数据到slave节点,不过Redis2.8开始,slave node会周期性地确认自己每次复制的数据量;
- 一个master node是可以配置多个slave node的;
- slave node也可以连接其他的slave node;
- slave node做复制的时候,是不会block master node的正常工作的;
- slave node在做复制的时候,也不会block对自己的查询操作,他会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧的数据集,加载新的数据集,这个时候就会暂停对外服务了;
- slave node主要用来进行横向扩容,做读写分离,扩容的slave node可以提高读的吞吐量;
Redis哨兵模式
主要功能
- 集群监控,负责监控redis master和slave进程是否正常工作;
- 消息通知,如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员;
- 故障转移,如果master node挂掉了,会自动转移到slave node上;
- 配置中心,如果故障转移发生了,通知client客户端新的master地址
分布式
- 故障转移时,判断一个master node是宕机了,需要大部分的哨兵同意才行,涉及到了分布式的选举的问题;
- 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了;
核心知识
- 哨兵至少需要3个实例,来保证自己的健壮性;
- 哨兵+redis主从的部署架构,是不会保证数据零丢失的,只能保证redis集群的高可用性;
- 对于哨兵+redis主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练;
数据丢失
(一)异步复制
- 因为 master => slave 的复制是异步的,所以可能有部分数据还没有复制到slave,master就宕机了,此时这些部分数据就丢失了;
(二)脑裂
① 某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着;
② 此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master;
③ 这个时候,集群里就会有两个master,也就是所谓的脑裂;
④ 此时虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,还继续写向旧的master的数据可能也丢失了;
⑤ 因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据
(三)解决
min-salves-to-write 1
min-slaves-max-lag 10
要求至少有一个slave,数据复制和同步的延迟不能超过10秒
如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master你就不会再接收任何请求了;
上面两个配置可以减少异步复制和脑裂导致的数据丢失;
- 减少异步复制的数据丢失
有了min-slaves-max-lag这个配置,就可以确保说,一旦slave复制数据和ack延时太长,就认为可能master宕机后损失的数据太多了,那么就拒绝写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低的可控范围内
- 减少脑裂的数据丢失
如果一个master出现脑裂,跟其他slave丢了连接,那么上面两个配置就可以确保说,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求;
这样脑裂后的旧master就不会接受client的新数据,也就避免了数据丢失;
上面的配置就确保了,如果跟任何一个slave丢了连接,在10秒后发现没有slave给自己ack,那么就拒绝新的写请求;
因此在脑裂场景下,最多就丢失10秒的数据
底层原理
(一)sdown和odown两种失败状态转换机制
-
- sdown是主观宕机,就一个哨兵如果自己觉得一个master宕机了,那么就是主观宕机;
sdown达成的条件很简单,如果一个哨兵ping一个master,超过了is-master-down-milliseconds指定的毫秒数之后,就主观认为master宕机;
- odown是客观宕机,如果quorum数量的哨兵都觉得一个master宕机了,那么就是客观宕机;
sdown和odown转换的条件很简单,如果一个哨兵在指定时间内,收到了quorum指定数量的其他哨兵也认为那个master是sdown了,那么就认为是odown了,客观认为master宕机;
(二)哨兵集群的自动发现机制
哨兵互相之间的发现,是通过redis的pub/sub系统实现的,每个哨兵都会往 _sentinel_:hello 这个channel里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在;
每隔两秒钟,每隔哨兵都会往自己监控的某个master+slaves对应的_sentinel_:hello channel 里发送一个消息,内容是自己的host、ip和runid还有对这个master的监控配置;
每个哨兵也会去监听自己监控的每个master+slaves对应的_sentinel_:hello channel,然后去感知到同样在监听这个master+slaves的其他哨兵的存在;
每个哨兵还会跟其他哨兵交换对master的监控配置,互相进行监控配置的同步;
(三)slave => master选举算法
-
- 如果一个master被认为odown了,而且majority烧饼都允许了主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个slave来,会考虑slave的一些信息:
① 跟master断开连接的时长;
② slave优先级;
③ 复制offset;
④ run id;
- 如果一个slave跟master断开连接已经超过了down-after-milliseconds的10倍,外加master宕机的时长,那么slave就会被认为不适合选举为master
(down-after-milliseconds * 10)+milliseconds_since_master_is_in_SDOWN_state
- 接下来会对slave进行排序
① 按照slave优先级进行排序,slave priority 越低,优先级就越高;
② 如果slave priority相同,那么看replica offset,哪个slave复制了越多的数据,offset越靠后,优先级就越高;
③ 如果上面两个条件都相同,那么选择一个run id比较小的那个slave;
瓶颈
- 如果你的数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个G,单机足够了;
- replication,一个master,多个slave,要几个slave跟你的要求的读吞吐量有关系,然后自己搭建一个sentinal集群,去保证redis主从架构的高可用性,就可以了;
Reids cluster
介绍
cluster
- 自动将数据进行分片,每个master上放一部分数据;
- 提供内置的高可用支持,部分master不可用时,还是可以继续工作的;
cluster端口
- 每个redis要开放两个端口号,比如一个是6379,另外一个就是加10000的端口号,比如16379;
- 16379端口号是用来进行节点间通信的,也就是cluster bus的东西,集群总线。cluster bus的通信,用来进行故障检测,配置更新,故障转移授权;
cluster bus用了另一种二进制的协议,主要用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间;
hash slot算法
- redis cluster有固定的16384个hash slot,对每个key计算CRC16值,然后对16384取模,可以获取key对应的hash slot;
redis cluster中每个master都会持有部分slot,比如有3个master,那么可能每个master持有5000多个hash slot
- hash slot让node的增加和一处很简单,增加一个master,就将其他的master的hash slot移动部分过去,减少一个master,就将它的hash slot移动到其他master上去;
移动hash slot的成本是非常低的;
客户端的api,可以对指定的数据,让他们走同一个hash slot,通过hash tag来实现;
核心原理(节点间的内部通信机制)
基础通信原理
(一)节点间采取gossip协议进行通信
-
- 跟集中式不同,不是将集群元数据(节点信息,故障,等等)集中存储在某个节点上,而是互相之间不断通信,保持整个集群所有节点的数据是完整的;
- 集中式:
好处在于,元数据的更新和读取,时效性非常好,一旦元数据出现了变更,立即就更新到集中式的存储中,其他节点读取的时候立即就可以感知到;
不好在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力;
- gossip:
好处在于:元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;
缺点:元数据更新有延时,可能导致集群的一些操作会有一些滞后;
(二)10000端口
-
- 每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口;
- 每个节点每隔一段时间都会往另外几个节点发送ping信息,同时其他几个节点接收到ping之后返回pong
(三)交换的信息
-
- 故障信息,节点的增加和移除,hash clot信息,等等;
gossip协议
- gossip协议包含多种消息,包括ping、pong、meet、fail等;
- meet:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信;
redis-trib.rb add-node命令:其实内部就是发送了一个gossip meet消息,给新加入的节点,通知那个节点去加入我们的集群;
- ping:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据;
每个节点每秒都会频繁发送ping给其他的集群,ping,频繁的互相之间交换数据,互相进行元数据的更新;
- pong:返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新;
- fail:某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了;
ping消息深入
- ping很频繁,而且要携带一些元数据,所以可能会加重网络负担;
每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点
- 当然如果发现某个节点通信延时达到了cluster_node_timeout/2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了;
- 所以cluster_node_timeout可以调节,如果调节比较大,那么会降低发送频率;
所以每次ping,一个是带上自己节点的信息,还有就是带上1/10其他节点的信息,发送出去,进行数据交换;
至少包含3个其他节点的信息,最多包含【总节点-2】个其他节点信息;
Redis回收算法
介绍
- redis是会在数据达到一定程度之后,超过了一个最大的限度之后,就会将数据进行一定的清理,从内存中清理掉一些数据;
- redis默认情况下就是使用LRU策略的,因为内存是有限的;
- LRU:Least Recently Used,最近最少使用算法;
将最近一段时间内,最少使用的一些数据,给干掉。比如说有一个key,在最近一个小时内,只被访问了一次,还有一个key在最近一个小时内,被访问了一万次
缓存清理设置
- maxmemory,设置redis用来存放数据的最大的内存大小,一旦超出这个内存大小之后,就会立即使用LRU算法清理掉部分数据;
- 对于64bit的机器,如果maxmemory设置为0,那么就默认不限制内存的使用,直到耗尽机器中所有的内存为止;
- maxmemory-policy,可以设置内存达到最大限制后,采取什么策略来处理;
清理策略
- noeviction:如果内存使用达到了maxmemory,client还要继续写入数据,那么就直接报错给客户端;
- allkeys-lru:就是我们常说的LRU算法,移除掉最少使用的那些keys对应的数据;
- volatile-lru:也是采取LRU算法,但是仅仅针对那些设置了指定存活时间(TTL)的key才会清理掉;
- allkeys-random:随机选择一些key来删除掉;
- volatile-random:随机选择一些设置了TTL的key来删除掉;
- volatile-ttl:移除掉部分keys,选择那些TTL时间比较短的keys;