redis学习总结
2019-12-01 15:21:34 举报
AI智能生成
登录查看完整内容
相似推荐
查看更多
Redis缓存策略
Redis主从复制
适配小组学习计划
Redis 课程笔记
redis权威指南
redis
学习总结
Redis技术梳理
学习类目
Redis解决缓存击穿
redis学习总结
作者其他创作
大纲/内容
当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap)
交换会让 Redis 的性能急剧下降,对于访问量比较频繁的 Redis 来说,这样龟速的存取效率基本上等于不可用
为了限制最大使用内存,Redis 提供了配置参数 maxmemory 来限制内存超出期望大小 ,超出后执行淘汰策略
noeviction:不淘汰key(默认)
volatile-lru : 淘汰过期集合的key中最少使用
volatile-ttl : 淘汰过期集合的key中,剩余寿命最短
volatile-lfu:从所有配置了过期时间的键中淘汰最近没有使用的key (4.0新增)
volatile-random : 随机淘汰过期集合的key
allkeys-lru : 淘汰全部key中最少使用
allkeys-lfu:从所有键中淘汰最近没有使用的key (4.0新增)
allkeys-random 随机淘汰全部key
淘汰策略
优先使用 allkeys-lru 策略。这样,可以充分利用 LRU 这一经典缓存算法的优势,把最近最常访问的数据留在缓存中,提升应用的访问性能。非常适用业务数据中有明显的冷热数据区分
如果业务应用中的数据访问频率相差不大,没有明显的冷热数据区分,建议使用 allkeys-random 策略,随机选择淘汰的数据就行
如果你的业务中有置顶的需求,比如置顶新闻、置顶视频,那么,可以使用 volatile-lru 策略,同时不给这些置顶数据设置过期时间。这样一来,这些需要置顶的数据一直不会被删除,而其他数据会在过期时根据 LRU 规则进行筛选
使用建议
淘汰机制
主写,从读,同步写指令给从节点
当从节点刚刚加入到集群时,它必须先要进行一次快照同步,同步完成后再继续进行增量同步。
快照同步是一个非常耗费资源的操作,它首先需要在主库上进行一次 bgsave 将当前内存的数据全部快照到磁盘文件中,然后再将快照文件的内容全部传送到从节点
从节点将快照文件接受完毕后,立即执行一次全量加载,加载之前先要将当前内存的数据清空。加载完毕后通知主节点继续进行增量同步
在整个快照同步进行的过程中,主节点的复制 buffer 还在不停的往前移动,如果快照同步的时间过长或者复制 buffer 太小,都会导致同步期间的增量指令在复制 buffer 中被覆盖
这样就会导致快照同步完成后无法进行增量复制,然后会再次发起快照同步,如此极有可能会陷入快照同步的死循环。
快照同步
Redis 同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存 buffer 中,然后异步将 buffer
中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一样的状态,一边向主节点反馈自己同步到哪里了 (偏移量)
因为内存的 buffer 是有限的,所以 Redis 主库不能将所有的指令都记录在内存 buffer 中
Redis 的复制内存 buffer 是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容。
配置:repl_backlog_size
增量同步
主从架构的核心原理
发生原因:主从库间的命令复制是异步进行的。
一方面,主从库间的网络可能会有传输延迟,所以从库不能及时地收到主库发送的命令,从库上执行同步命令的时间就会被延后
另一方面,即使从库及时收到了主库的命令,但是,也可能会因为正在处理其它复杂度高的命令(例如集合操作命令)而阻塞
主从数据不一致
在硬件环境配置方面,我们要尽量保证主从库间的网络连接状况良好
Redis 的 INFO replication 命令可以查看主库接收写命令的进度信息(master_repl_offset)和从库复制写命令的进度信息(slave_repl_offset),所以,我们就可以开发一个监控程序,先用 INFO replication 命令查到主、从库的进度,然后,我们用 master_repl_offset 减去 slave_repl_offset,这样就能得到从库和主库间的复制进度差值了
如果某个从库的进度差值大于我们预设的阈值,我们可以让客户端不再和这个从库连接进行数据读取,这样就可以减少读到不一致数据的情况。不过,为了避免出现客户端和所有从库都不能连接的情况,我们需要把复制进度差值的阈值设置得大一些
开发一个外部程序来监控主从库间的复制进度。
处理方法
Redis 3.2 之前的版本,那么,从库在服务读请求时,并不会判断数据是否过期,而是会返回过期数据
在 3.2 版本后,Redis 做了改进,如果读取的数据已经过期了,从库虽然不会删除,但是会返回空值,这就避免了客户端读到过期数据
客户端在从库中访问留存的过期数据,从库并不会触发数据删除
EXPIRE 和 PEXPIRE:它们给数据设置的是从命令执行时开始计算的存活时间。从库执行命令时间点不同,导致过期时间的结束点与主库不一致
EXPIREAT 和 PEXPIREAT:它们会直接把数据的过期时间设置为具体的一个时间点。主从节点上的时钟若没有保持一直保持一致,也可能会读取到过期数据
主从库命令执行时间不一致
读到过期数据
cluster-node-timeout 配置项
protected-mode 配置项
配置项设置得不合理从而导致服务挂掉
Redis主从同步与故障切换,有哪些坑?
主从同步
哨兵+主从 实现redis 集群高可用
集群监控:负责监控Redis master和slave是否征正常进行工作
哨兵进程会使用 PING 命令检测它自己和主、从库的网络连接情况,用来判断实例的状态。如果哨兵发现主库或从库对 PING 命令的响应超时了,那么,哨兵就会先把它标记为“主观下线
主观下线
避免误判,一般用于判断主库是否下线
误判一般会发生在集群网络压力较大、网络拥塞,或者是主库本身压力较大的情况下
任何一个实例只要自身判断主库“主观下线”后,就会给其他实例发送 is-master-down-by-addr 命令。接着,其他实例会根据自己和主库的连接情况,做出 Y 或 N 的响应,Y 相当于赞成票,N 相当于反对票
一个哨兵获得了仲裁所需的赞成票数后,就可以标记主库为“客观下线”。这个所需的赞成票数是通过哨兵配置文件中的 quorum 配置项设定的。例如,现在有 5 个哨兵,quorum 配置的是 3,那么,一个哨兵需要 3 张赞成票,就可以标记主库为“客观下线”了。这 3 张赞成票包括哨兵自己的一张赞成票和另外两个哨兵的赞成票
哨兵集群,少数服从多数
客观下线
怎么判断主库下线
消息通知:如果某个Redis实例挂了,那么哨兵会发送通知给系统管理员
第一,拿到半数以上的赞成票
第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值
由哪个哨兵执行主从切换(Leader 选举)
故障转移:如果master node挂了,会自动转移到slave node上
配置中心:如果故障转移发生,通知客户端最新的master node的地址
min-slaves-to-write 1 表示主节点必须至少有一个从节点在进行正常复制,否则就停止对外写服务,丧失可用性
min-slaves-max-lag 10 表示如果 10s 没有收到从节点的反馈,就意味着从节点同步不正常
Sentinel 无法保证消息完全不丢失,但是也尽可能保证消息少丢失。它有两个选项可以限制主从延迟过大
指在主从集群中,同时有两个主节点,它们都能接收写请求
不同的客户端会往不同的主节点上写入数据。而且,严重的话,脑裂会进一步导致数据丢失
脑裂
min-slaves-to-write:这个配置项设置了主库能进行数据同步的最少从库数量;(N)
min-slaves-max-lag:这个配置项设置了主从库间进行数据复制时,从库给主库发送 ACK 消息的最大延迟(以秒为单位)。(T)
主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主库就不会再接收客户端的请求了。
原主库是假故障,它在假故障期间也无法响应哨兵心跳,也不能和从库进行同步,自然也就无法和从库进行 ACK 确认了。这样一来,min-slaves-to-write 和 min-slaves-max-lag 的组合要求就无法得到满足,原主库就会被限制接收客户端请求,客户端也就不能在原主库中写入新数据了
配置建议是,假设从库有 K 个,可以将 min-slaves-to-write 设置为 K/2+1(如果 K 等于 1,就设为 1),将 min-slaves-max-lag 设置为十几秒(例如 10~20s
怎么应对脑裂问题?
哨兵机制
https://segmentfault.com/a/1190000017578588
我们在业务层生成数据时,要尽量避免把过多的数据保存在同一个键值对中
如果 bigkey 正好是集合类型,我们还有一个方法,就是把 bigkey 拆分成很多个小的集合类型数据,分散保存在不同的实例上
保存了 bigkey
Slot 分配不均衡
Hash Tag
原因
数据量倾斜:在某些情况下,实例上的数据分布不均衡,某个实例上的数据特别多
数据访问倾斜:虽然每个集群实例上的数据量相差不大,但是某个实例上的数据是热点数据,被访问得非常频繁
数据倾斜
实例间的通信开销会随着实例规模增加而增大
限定集群规模
cluster-require-full-coverage参数可以允许部分节点故障,其他节点还可以对外提供访问
如果某个主节点没有从节点,那么该主节点挂了,整个集群不可用
Redis Cluster
redis是单线程的,在数据量大的时候,keys * 操作获取全部的key就会导致 Redis 服务卡顿,其余的指令延后或超时报错
提供 limit 参数,可以控制每次返回结果的最大条数
返回的结果可能会有重复,需要客户端去重复
遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的
sacn 是通过游标分步进行的,不会阻塞线程
可以遍历每个节点,分别进行SCAN操作,然后在客户端合并结果
风险:大量相同前缀的key被分配到同一个哈希槽里面了,会导致数据在哈希槽之间分布不均衡。如果要用这个方法,需要评估下key的分布情况
Redis cluster模式下不支持跨节点的SCAN操作
keys 和 scan
Redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。
它采用 IO 多路复用机制同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器进行处理
多个 Socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket
将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理
Redis线程模型
多线程IO,但多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程
支持多种数据类型的区分编码,包括空值、浮点数、布尔值、有序的字典集合、无序的集合
在 RESP 2 中,客户端和服务器端的通信内容都是以字节数组形式进行编码的,客户端需要根据操作的命令或是数据类型自行对传输的数据进行解码,增加了客户端开发复杂度
RESP 3 通信协议
实例会在服务端记录客户端读取过的 key,并监测 key 是否有修改。一旦 key 的值发生变化,服务端会给客户端发送 invalidate 消息,通知客户端缓存失效了
服务端对于记录的 key 只会报告一次 invalidate 消息,也就是说,服务端在给客户端发送过一次 invalidate 消息后,如果 key 再被修改,此时,服务端就不会再次给客户端发送 invalidate 消息
只有当客户端再次执行读命令时,服务端才会再次监测被读取的 key,并在 key 修改时发送 invalidate 消息
设计的考虑是节省有限的内存空间。毕竟,如果修改后客户端不再访问这个 key 了,而服务端仍然记录 key 的修改情况,就会浪费内存资源
普通模式
服务端会给客户端广播所有 key 的失效情况
如果 key 被频繁修改,服务端会发送大量的失效广播消息,这就会消耗大量的网络带宽资源
实际应用时,我们会让客户端注册希望跟踪的 key 的前缀,当带有注册前缀的 key 被修改时,服务端会把失效消息广播给所有注册的客户端
即使客户端还没有读取过 key,但只要它注册了要跟踪的 key,服务端都会把 key 失效消息通知给这个客户端
广播模式
RESP 3 协议
要获得失效消息通知的客户端,就需要执行订阅命令 SUBSCRIBE,专门订阅用于发送失效消息的频道 _redis_:invalidate
同时,再使用另外一个客户端,执行 CLIENT TRACKING 命令,设置服务端将失效消息转发给使用 RESP 2 协议的客户端
重定向模式
RESP 2 协议
如果数据被修改了或是失效了,如何通知客户端对缓存的数据做失效处理?
实现服务端协助的客户端缓存,加速业务应用访问
6.0
基于内存操作,高效的数据结构,及采用IO多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率
1. 不会因为线程创建导致的性能消耗;2. 避免上下⽂切换引起的 CPU 消耗,没有多线程切换的开销;3. 避免了线程之间的竞争问题,⽐如添加锁、释放锁、死锁等,不需要考虑各种锁问题。4. 代码更清晰,处理逻辑简单。
单线程的好处
优点:通常可以增加系统吞吐量,充分利⽤ CPU 资源
缺点:线程间上下文切换,共享资源间的锁竞争,代码的复杂度同时也增加
多线程
因为Redis是基于内存实现的,其中最主要的瓶颈是机器内存大小和网络带宽,CPU不是重要的瓶颈,而且单线程容易实现,就顺理成章的使用单线程了
为什么不使用多线程
为什么快?
SSCAN 多次迭代返回,避免一次返回大量数据
当你需要执行排序、交集、并集操作时,可以在客户端完成,而不要用 SORT、SUNION、SINTER 这些命令,以免拖慢 Redis 实例
集合全量查询和聚合操作
可异步执行
过期时间参数上,加上一个一定大小范围内的随机数,这样,既保证了 key 在一个邻近时间范围内被删除,又避免了同时过期造成的压力
bigKey删除,key过期
删除数据库
RDB,AOF,Fork子进程的时候
主从同步,全量复制加载RDB文件
内部阻塞式操作
Redis阻塞点
mem_fragmentation_ratio 大于 1 但小于 1.5。这种情况是合理的
mem_fragmentation_ratio 大于 1.5 。这表明内存碎片率已经超过了 50%。一般情况下,这个时候,我们就需要采取一些措施来降低内存碎片率了
一个 mem_fragmentation_ratio 的指标,它表示的就是 Redis 当前的内存碎片率
INFO memory
如何判断是否有内存碎片?
内因:内存分配器的分配策略
外因:键值对大小不一样和删改操作
内存碎片是如何形成的?
active-defrag-ignore-bytes 100mb:表示内存碎片的字节数达到 100MB 时,开始清理;
active-defrag-threshold-lower 10:表示内存碎片空间占操作系统分配给 Redis 的总空间比例达到 10% 时,开始清理。
active-defrag-cycle-min 25: 表示自动清理过程所用 CPU 时间的比例不低于 25%,保证清理能正常开展;
active-defrag-cycle-max 75:表示自动清理过程所用 CPU 时间的比例不高于 75%,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致响应延迟升高。
为了尽可能减少碎片清理对 Redis 正常请求处理的影响,自动内存碎片清理功能在执行时,还会监控清理操作占用的 CPU 时间,而且还设置了两个参数,分别用于控制清理操作占用的 CPU 时间比例的上、下限,既保证清理工作能正常进行,又避免了降低 Redis 性能
启用内存碎片 config set activedefrag yes
内存碎片自动清理
主库的数据还没有同步到从库,结果主库发生了故障,等从库升级为主库后,未同步的数据就丢失了
数据丢失
拓展
哈希桶中的元素保存的并不是值本身,而是指向具体值的指针
链地址法:同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接。
Hash冲突
给哈希表 2 分配更大的空间,例如是当前哈希表 1 大小的两倍;
规避一次性拷贝大量数据,导致线程阻塞,无法服务其他请求
每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries
渐进式 rehash。
定时任务,每100ms,执行一次
把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;
释放哈希表 1 的空间。
rehash操作导致阻塞问题
当字符串长度小于1M前,成倍扩容,大于1M后每次只扩容1M最大为512M
每次修改后会使设置的过期时间失效
存在额外的内存分配,自己维护了一个sds结构,所以大数据量下,特别占用内存,可能会导致RDB在生成的时候造成阻塞
String
List类型是一个链表结构的集合,并且是有序的集合,其值是可以重复的
List类型有点类似数组的概念,所以具有下标,可以针对指定下标进行操作
既可以做【栈-先进后出】使用,也可以做【队列-先进先出】使用,所以常用来做异步队列使用
List
Hash类型是String类型的field和value的映射表
hash 可以对用户结构中的每个字段单独存储。这样当我们需要获取用户信息时可以进行部分获取
而以整个字符串的形式去保存用户信息的话就只能一次性全部读取,这样就会比较浪费网络流量。
但是hash 结构的存储消耗要高于单个字符串
Hash
相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的,即可以用来做去重的处理
以对Set实施交集(Inter--InterStore)、并集(Union--UnionStore)和差集(Diff--DiffStore)的操作
Set
根据score,可以排序分值(学生成绩),时间(心跳在线时间或者用户关注时间),作为延时队列,限流
Zset
list/set/hash/zset 这四种数据结构是容器型数据结构,它们共享下面两条通用规则:
如果容器不存在,那就创建一个,再进行操作
如果容器里元素没有了,那么立即删除容器,释放内存
容器型数据结构的通用规则
基础数据结构
HyperLogLog 提供不精确的去重计数方案(统计UV),标准误差为81%
pfmerge指令:用于将多个 pf 计数值累加在一起形成一个新的 pf 值(合并多个页面的UV值)
无法知道某一个值是不是已经在 HyperLogLog 结构里面(仅提供添加,统计,合并三个操作)
HyperLogLog
可以理解为一个不怎么精确的 set 结构,通过在add前用bf.reserve指令显式创建,设定参数提高准确率
initial_size参数表示预计放入的元素数量,initial_size参数设置错误率,错误率越低,需要的空间越大
特性:当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在
解决去重的问题。比如我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重
处理缓存穿透
解决问题
布隆过滤器
实现附近的人的功能 参考地址:https://juejin.im/post/5da40462f265da5baf410a11
GeoHash
redis 5.0后版本出现的数据结构 可以支持广播模式的可持久化的消息队列
Stream
Bitmap
数据结构
先用setnx ex来获取锁,并设置过期时间,过期时间主要用来防止业务代码异常,锁没有释放的情况导致死锁的情况
解锁时,get获取值对比是不是自身,如果是才进行删除,使用lua脚本保证操作原子性
redssion 中的 watch dog自动延期机制
默认设置过期时间为30秒,只要客户端成功获得锁,就会启动watch dog。每10秒检查一次客户端是否还持有锁,如果有,则延长过期时间
redis分布式锁过期时间到了业务没执行完问题
客户端1对某个redis master实例,写入了锁,此时会异步复制给对应的master slave实例
但是这个过程中一旦发生redis master宕机,主备切换,redis slave变为了redis master
接着就会导致,客户端2来尝试加锁的时候,在新的redis master上完成了加锁,而客户端1也以为自己成功加了锁。
此时就会导致多个客户端对一个分布式锁完成了加锁
RedLock算法:https://www.jianshu.com/p/fba7dd6dcef5
主从异步复制导致的问题
分布式锁
分库分表的自增id
INCR指令实现简单限流
计数器
缓存:热点数据
pub/sub:主题订阅者模式 实现1:N的消息队列,但是消费者下线的时候会导致消息丢失,所以当redis宕机的时候,pub/sub的数据不会持久化
Redis5.0后提供 Stream 解决了pub/sub消息不能持久化的问题。https://blog.csdn.net/enmotech/article/details/81230531
list结构作为队列,使用rpush/lpush操作入队列,使用lpop 和 rpop来出队列。缺点:没有等待队列里有值就直接消费,可以通过在应用层引入sleep机制来降低请求频率
list中还有另一个指令,BLPOP key [keys ...] timeout:阻塞直到队列有消息或者超时,当 timeout 为 0 是表示阻塞时间无限制,会导致空闲连接问题,服务器会主动断开,抛出异常,客户端需要捕获处理,或重试,并且只能供一个消费者消费
异步队列
使用zset结构,将消息序列化成一个字符串作为 zset 的value,这个消息的到期处理时间作为score
用多个线程轮询 zset 获取到期的任务进行处理,多个线程是为了保障可用性,万一挂了一个线程还有其它线程可以继续处理
2,监听过期key的方式
延时队列
解决服务器分布式部署时,由于负载均衡策略,分发用户请求到不同的服务器,导致session不一致的问题
去重,布控过滤器
uv pv网站类访问统计
应用
同一时间大面积的缓存失效,或者redis宕机,瞬间所有查询打到DB上,导致DB奔溃。
Redis 缓存实例发生故障宕机了,无法处理请求,这就会导致大量请求一下子积压到数据库层,从而发生缓存雪崩
缓存雪崩
分散key的过期时间
通过搭建缓存的高可用,避免缓存挂掉导致无法提供服务的情况
使用本地缓存时,即使分布式缓存挂了,也可以将 DB 查询到的结果缓存到本地,避免后续请求全部到达 DB 中
事前预防
服务的熔断和限流
事后防控
解决方案
被动写:当从缓存中查不到数据时,然后从数据库查询到该数据,写入该数据到缓存中
1、网关层(比如说nginx)做限制,对每秒请求超出一定阈值的ip做拉黑处理
2、在代码层面做限制,过滤不符合的查询条件
3、缓存空对象。当从 DB 查询数据为空,我们仍然将这个空结果进行缓存,并设置过期时间
1、根据 KEY 查询【BloomFilter 缓存】。如果不存在对应的值,直接返回;如果存在,继续向下执行。
2、根据 KEY 查询在【数据缓存】的值。如果存在值,直接返回;如果不存在值,继续向下执行。
3、查询 DB 对应的值,如果存在,则更新到缓存,并返回该值。
4、使用布隆过滤器。在缓存服务的基础上,构建 BloomFilter 数据结构,在 BloomFilter 中存储对应的 KEY 是否存在,如果存在,说明该 KEY 对应的值不为空。需要提前将已存在的数据放在布隆过滤器里面
解决方法
缓存穿透
某个极度“热点”数据在某个时间点过期时,恰好在这个时间点对这个 KEY 有大量的并发请求过来,导致DB奔溃
1、获取分布式锁,直到成功或超时。如果超时,则抛出异常,返回。如果成功,继续向下执行。
2、获取缓存。如果存在值,则直接返回;如果不存在,则继续往下执行。因为,获得到锁,可能已经被“那个”线程去查询过 DB ,并更新到缓存中了
3、查询 DB ,并更新到缓存中,返回值。
1、使用互斥锁。请求发现缓存不存在后,去查询 DB 前,使用分布式锁,保证有且只有一个线程去查询 DB ,并更新到缓存。
1、获取缓存。通过 VALUE 的过期时间,判断是否过期。如果未过期,则直接返回;如果已过期,继续往下执行。
2、通过一个后台的异步线程进行缓存的构建,也就是“手动”过期。通过后台的异步线程,保证有且只有一个线程去查询 DB。
3、同时,虽然 VALUE 已经过期,还是直接返回。通过这样的方式,保证服务的可用性,虽然损失了一定的时效性。
2、手动过期。缓存上从不设置过期时间,功能上将过期时间存在 KEY 对应的 VALUE 里
缓存击穿
并发的场景下,导致读取老的 DB 数据,更新到缓存中
缓存和 DB 的操作,不在一个事务中,可能只有一个 DB 操作成功,而另一个 Cache 操作失败,导致不一致。
在写请求时,先淘汰缓存之前,先获取该分布式锁。
在读请求时,发现缓存不存在时,先获取分布式锁。
1、将缓存可能存在的并行写,实现串行写。
2、先更新数据库,再淘汰缓存,依赖缓存的过期时间设置
数据双写不一致???
缓存相关
RDB 快照,对redis中的数据执行周期性的全量持久化到磁盘,恢复快
save操作阻塞主进程,会导致服务一段时间不可用
bgsave 是fork子进程来执行save操作,仅在fork子进程的时候发生阻塞
客户端执行命令save和bgsave会生成快照
根据配置文件save m n规则进行自动快照
主从复制时,从库全量复制同步主库数据,此时主库会执行bgsave命令进行快照
客户端执行数据库清空命令FLUSHALL时候,触发快照
客户端执行shutdown关闭redis时,触发快照
RDB生成快照可自动触发,也可以使用命令手动触发,以下是redis触发执行快照条件
Redis在持久化时,如果是采用BGSAVE命令或者BGREWRITEAOF的方式,那Redis会fork出一个子进程来读取数据,从而写到磁盘中。总体来看,Redis还是读操作比较多。如果子进程存在期间,发生了大量的写操作,那可能就会出现很多的分页错误(页异常中断page-fault),这样就得耗费不少性能在复制上。而在rehash阶段上,写操作是无法避免的。所以Redis在fork出子进程之后,将负载因子阈值提高,尽量减少写操作,避免不必要的内存写入操作,最大限度地节约内存。
写时复制技术来避免对正在写入的数据进行阻塞和拷贝
2、子进程尝试将数据dump到临时的rdb快照文件中
3、完成rdb快照文件的生成之后,就替换之前的旧的快照文件
RDB持久化机制的工作流程
灵活设置备份频率和周期
非常适合冷备份,对于灾难恢复来说,RDB 是非常不错的选择。我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上
性能最大化。对于 Redis 的服务进程而言,在开始持久化时,它唯一需要做的只是 fork 出子进程,极大避免了服务进程进行IO操作
相比于 AOF 机制,RDB 的恢复速度更更快
优点
如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔一段时间生成一次
RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒
缺点
RDB
写后日志:减少指令检查的开销,避免阻塞当前指令执行,如果是实时写入磁盘,则可能会阻塞下个指令执行,而且因为不存在事务,日志也有可能丢失,导致最后恢复的数据不完整。
AOF 追加 更新指令 到本地文件,达到一定的量后会对文件进行瘦身
always: 每次写入一条数据,同步追加到aof文件,性能非常非常差,吞吐量很低; 确保说redis里的数据一条都不丢失
everysec: 每秒将os cache中的数据追加到aof文件。默认的配置,宕机时丢失一秒的数据
no: 仅仅redis负责将数据写入os cache就撒手不管了,然后后面os自己会时不时根据自己的策略将数据刷入磁盘,不可控制(即redis不负责更新aof文件,何时更新由宿主机自己决定,Linux64位大概是30秒一次)
AOF持久化配置
aof文件是追加的形式增量递增的 ,因为内存的容量是有限的,且redis的有些数据会有过期的概念,和淘汰机制的存在,会导致一开始存储的指令没有实际对应的数据。
带来的性能问题:1.操作系统对文件大小有限制,不能无限增大。 2.过大的文件,追加记录效率低。 3.过大的文件,Redis异常关闭重启之后,进行重写操作时,执行时间长,影响Redis可用性。
redis fork一个子进程
redis主进程,接收到client新的写操作之后,在内存中写入日志,同时新的日志也继续写入旧的AOF文件
子进程写完新的日志文件之后,redis主进程将内存中的新日志再次追加到新的AOF文件中,最后替换旧的文件
重写工作流程
父子进程同时操作,资源竞争的问题
重写过程中失败,AOF文件被污染,无法用于恢复,分开不同文件,执行失败删除即可
为什么不使用同个文件?
AOF 重写
该机制可以带来更高的数据安全性,即数据持久性。Redis 中提供了 3 种同步策略,即每秒同步、每修改(执行一个命令)同步和不同步
日志文件的写入操作采用的是 append 模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容
AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,\t\t\t\t 只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据
子进程基于当前内存中的数据,构建日志,开始往一个新的临时的AOF文件中写入日志
对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
根据同步策略的不同,AOF 在运行效率上往往会慢于 RDB
AOF
结合RDB恢复数据快,AOF丢失数据少的优点
配置设置:aof-use-rdb-preamble yes
在 AOF 重写日志时
fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件
主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件
写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件
步骤
aof+rdb混合持久化
持久化的意义:故障恢复
AOF,存放的指令日志,做数据恢复的时候,其实是要回放和执行所有的指令日志,来恢复出来内存中的所有数据的
RDB,就是一份数据文件,恢复的时候,直接加载到内存中即可
持久化恢复数据的时候
持久化方案:主从的时候 主节点只做aof (避免aof重写?) 从节点使用aof+rdb混合持久化
持久化
过期的时候不会立即删除相应的数据
优点:尽量减少删除操作对 CPU 资源的使用,对于用不到的数据,就不再浪费时间进行检查和删除了
缺点:导致大量已经过期的数据留存在内存中,占用较多的内存资源 (优化方式,主从删除)
惰性删除:就是指在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。
1、默认会每秒进行十次过期扫描
2、从过期字典中随机 20 个 key,删除这 20 个 key 中已经过期的 key
3、如果过期的 key 比率超过 1/4,那就重复步骤 1
4、同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms
主动过期:redis会将所有设置过期时间的key放在一个独立的字典里面,以后会定时遍历这个字典来删除到期的 key()
虽然定期删除策略可以释放一些内存,但是,Redis 为了避免过多删除操作对性能产生影响,每次随机检查数据的数量并不多。如果过期数据很多,并且一直没有再被访问的话,这些数据就会留存在 Redis 实例中。业务应用之所以会读到过期数据,这些留存数据就是一个重要因素。
从节点过期key处理:slave不会过期key,只会等待master过期key。如果master过期了一个key,或者通过LRU淘汰了一个key,那么会模拟一条del命令发送给slave。
过期策略
MULTI 显式开启事务
暂存到队列,并未实际执行
执行相应的命令
发送提交事务的命令 EXEC
MULTI、EXEC
对于这种情况,在命令入队时,Redis 就会报错并且记录下这个错误。此时,我们还能继续提交命令操作。等到执行了 EXEC 命令之后,Redis 就会拒绝执行所有提交的命令操作,返回事务失败的结果
事务中的所有命令都不会再被执行了,保证了原子性
在执行 EXEC 命令前,客户端发送的操作命令本身就有错误
在执行完 EXEC 命令以后,Redis 实际执行这些事务操作时,就会报错。不过,需要注意的是,虽然 Redis 会对错误命令报错,但还是会把正确的命令执行完
事务的原子性就无法得到保证
事务操作入队时,命令和操作的数据类型不匹配,但 Redis 实例没有检查出错误
如果 Redis 开启了 AOF 日志,那么,只会有部分的事务操作被记录到 AOF 日志中。我们需要使用 redis-check-aof 工具检查 AOF 日志文件,这个工具可以把未完成的事务操作从 AOF 文件中去除。这样一来,我们使用 AOF 恢复实例后,事务操作不会再被执行,从而保证了原子性
如果 AOF 日志并没有开启,那么实例重启后,数据也都没法恢复了,此时,也就谈不上原子性了
在执行事务的 EXEC 命令时,Redis 实例发生了故障,导致事务执行失败
能否保证原子性?
在这种情况下,事务本身就会被放弃执行,所以可以保证数据库的一致性
命令入队时就报错
在这种情况下,有错误的命令不会被执行,正确的命令可以正常执行,也不会改变数据库的一致性。
命令入队时没报错,实际执行时报错
能否保证一致性?
作用:在事务执行前,监控一个键或者多个键的值得变化情况,当事务调用EXEC命令执行时,WATCH会先检查这部分的值有没有被其他客户端修改过,如果有则放弃事务的执行
需要通过WATCH机制进行保证
如果没有WATCH机制,则不保证
在执行 EXEC 命令前
隔离性可以保证,因为redis对命令的操作为单线程执行,提交EXEC命令后,会将队列中的命令执行完,再执行其余命令
在执行 EXEC 命令后
并发执行的隔离性:
AOF和RDB都存在数据丢失的情况,
持久性
Redis 事务
redis
0 条评论
回复 删除
下一页