Redis高级
Redis高级
Redis单线程
Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端」这个过程是由一个线程(主线程)来完成的。
但Redis的其他功能,比如关闭文件、AOF 刷盘、释放内存等,其实是由额外的线程执行的。例如执行 unlink key / flushdb async/ flushall async 等命令,会把这些删除操作交给后台线程来执行,好处是不会导致 Redis 主线程卡顿。
Redis命令工作线程是单线程的,但是,整个Redis来说,是多线程的;
之所以 Redis 为「关闭文件、AOF 刷盘、释放内存」这些任务创建单独的线程来处理,是因为这些任务的操作都是很耗时的,如果把这些任务都放在主线程来处理,那么 Redis 主线程就很容易发生阻塞,这样就无法处理后续的请求了。
Redis 采用单线程为什么还这么快?
- 基于内存操作: Redis 的所有数据都存在内存中,因此所有的运算都是内存级别的,所以他的性能比较高;
- 数据结构简单: Redis 的数据结构是专门设计的,而这些简单的数据结构的查找和操作的时间大部分复杂度都是 0(1),因此性能比较高;
- Redis 采用单线程模型可以避免了多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题。
- 多路复用和非阻塞 I/O: Redis使用 I/O多路复用功能来监听多个 socket连接客户端,这样就可以使用一个线程来检查多个 Socket 的就绪状态,在单个线程中通过记录跟踪每一个 socket(I/O流)的状态来管理处理多个 I/O 流,减少线程切换带来的开销,同时也避免了 I/O 阻塞操作;
Redis6.0之前一直采用单线程的主要原因
- 使用单线程模型是 Redis 的开发和维护更简单,因为单线程模型方便开发和调试;
- 即使使用单线程模型也并发的处理多客户端的请求,主要使用的是IO多路复用和非阻塞IO;
- 对于Redis系统来说,主要的性能瓶颈是内存或者网络带宽而并非 CPU。
为什么逐渐加入多线程特性?
删除一个很大的数据时,因为是单线程原子命令操作,这就会导致 Redis 服务卡顿,于是在 Redis 4.0 中就新增了多线程的模块,主要是为了解决删除数据效率比较低的问题的。
使用惰性删除可以有效的解决性能问题, 在Redis4.0就引入了多个线程来实现数据的异步惰性删除等功能
但是其处理读写请求的仍然只有一个线程,所以仍然算是狭义上的单线程。
在Redis6/7中引入了I/0多线程的读写,这样就可以更加高效的处理更多的任务了,Redis只是将I/O读写变成了多线程,而命令的执行依旧是由主线程串行执行的,因此在多线程下操作 Redis不会出现线程安全的问题。
多线程开启:
- 设置
io-thread-do-reads
配置项为yes,表示启动多线程。 - 设置线程个数
io-threads
。关于线程数的设置,官方的建议是如果为4核的CPU,建议线程数设置为2或3,如果为8核CPU建议线程数设置为6,安程数一定要小于机器核数,线程数并不是越大越好。
因此, Redis 6.0 版本之后,Redis 在启动的时候,默认情况下会额外创建 6 个线程(这里的线程数不包括主线程):
- Redis-server : Redis的主线程,主要负责执行命令;
- bio_close_file、bio_aof_fsync、bio_lazy_free:三个后台线程,分别异步处理关闭文件任务、AOF刷盘任务、释放内存任务;
- io_thd_1、io_thd_2、io_thd_3:三个 I/O 线程,io-threads 默认是 4 ,所以会启动 3(4-1)个 I/O 多线程,用来分担 Redis 网络 I/O 的压力。
BigKey
多大算BigKey?
bigkey是指key对应的value所占的内存空间比较大
大 key 会带来的影响:
- 内存占用过高。大Key占用过多的内存空间,可能导致可用内存不足,从而触发内存淘汰策略。
- 性能下降。对于大Key的操作,如读取、写入、删除等,都会消耗更多的CPU时间和内存资源,进一步降低系统性能。
- 阻塞工作线程。如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。
- 引发网络阻塞。每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
- 主从同步延迟。由于大Key占用较多内存,同步过程中需要传输大量数据,这会导致主从之间的网络传输延迟增加,进而影响数据一致性。
- 数据倾斜。在Redis集群模式中,某个数据分片的内存使用率远超其他数据分片,无法使数据分片的内存资源达到均衡。
如何发现BigKey?
redis-cli --bigkey
最好选择在从节点上执行该命令。因为主节点上执行时,会阻塞主节点;
如果没有从节点,那么可以选择在 Redis 实例业务压力的低峰阶段进行扫描查询,以免影响到实例的正常运行;或者可以使用 -i 参数控制扫描间隔,避免长时间扫描降低 Redis 实例的性能。
BigKey如何删除?
分批次删除和异步删除
-
String:一般用del,如果过于庞大使用unlink key 删除
-
hash
使用 hscan 每次获取部分field-value,再使用 hdel 删除每个field, 最后删除field-value
- list
使用 ltrim 渐进式逐步删除,直到全部删除完成
- set
使用 sscan 每次获取部分元素,再使用 srem 命令删除每个元素
- zset
使用 zscan 每次获取部分元素,在使用 zremrangebyrank 命令删除每个元素
生产上限制 keys * /flushdb/flushall等危险命令以防止误删误用?
通过配置设置禁用这些命令,redis.conf在SECURITY这一项中
不用keys *避免卡顿,那该用什么? scan, sscan, hscan, zscan
- cursor : 游标
- pattern:匹配的模式
- count:指定数据集返回多少数据,默认10
SCAN 命令是一个基于游标的迭代器,每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。
SCAN的遍历顺序非常特别,它不是从第一维数组的第零位一直遍历到末尾,而是采用了高位进位加法来遍历。之所以使用这样特殊的方式进行遍历,是考虑到字典的扩容和缩容时避免槽位的遍历重复和遗漏。
分别说说三种写回策略,在持久化 BigKey 的时候,会影响什么?
- Always 策略就是每次写入 AOF 文件数据后,就执行 fsync() 函数;
- Everysec 策略就会创建一个异步任务来执行 fsync() 函数;
- No 策略就是永不执行 fsync() 函数;
当使用 Always 策略的时候,如果写入是一个大 Key,主线程在执行 fsync() 函数的时候,阻塞的时间会比较久,因为当写入的数据量很大的时候,数据同步到硬盘这个过程是很耗时的。
当使用 Everysec 策略的时候,由于是异步执行 fsync() 函数,所以大 Key 持久化的过程(数据同步磁盘)不会影响主线程。
当使用 No 策略的时候,由于永不执行 fsync() 函数,所以大 Key 持久化的过程不会影响主线程
AOF 重写机制和 RDB 快照(bgsave 命令)的过程,都会分别通过 fork()
函数创建一个子进程来处理任务。会有两个阶段会导致阻塞父进程(主线程):
- 创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;
- 创建完子进程后,如果父进程修改了共享数据中的大 Key,就会发生写时复制,这期间会拷贝物理内存,由于大 Key 占用的物理内存会很大,那么在复制物理内存这一过程,就会比较耗时,所以有可能会阻塞父进程。
热点key
如果一个key的访问频率占比过大,或带宽占比过大,都属于热点key。
如何发现热点key?
- 根据业务经验进行分析
- redis集群监控
- 使用
hotkey
监控:redis-cli --hotkeys
- 客户端收集:操作redis时加上统计查询频次的逻辑
- 代理层收集
如何解决热点key问题?
- 热点key拆分:将热点数据拆分到多个key中,例如通过引入随机前缀,使不同用户请求分散到多个key,多个key分布在多实例中,避免集中访问单一key。
- 多级缓存:增加本地缓存分担redis压力
- 读写分离:通过主从复制,将读请求分散到多个从节点
- 限流和降级:热点key访问过高时,应用限流策略,减少redis请求,或者必要时返回降级的数据或空值。
缓存双写一致性(缓存更新策略)
对于读数据,选择旁路缓存策略,如果 cache 不命中,会从 db 加载数据到 cache。对于写数据,选择更新 db 后,再删除缓存。
常见的缓存更新策略
- Cache Aside(旁路缓存)策略;
- Read/Write Through(读穿 / 写穿)策略;
- Write Back(写回)策略;
Cache Aside(旁路缓存)
Cache Aside(旁路缓存)策略是最常用的,应用程序直接与「数据库、缓存」交互,并负责对缓存的维护,该策略又可以细分为「读策略」和「写策略」。
写策略的步骤:
- 先更新数据库中的数据,再删除缓存中的数据。
读策略的步骤:
- 如果读取的数据命中了缓存,则直接返回数据;
- 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。
Read/Write Through(读穿 / 写穿)策略
Read/Write Through(读穿 / 写穿)策略原则是应用程序只和缓存交互,不再和数据库交互,而是由缓存和数据库交互,相当于更新数据库的操作由缓存自己代理了。
Read Through 策略:
先查询缓存中数据是否存在,如果存在则直接返回,如果不存在,则由缓存组件负责从数据库查询数据,并将结果写入到缓存组件,最后缓存组件将数据返回给应用。
Write Through 策略:
当有数据更新的时候,先查询要写入的数据在缓存中是否已经存在:
- 如果缓存中数据已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中,然后缓存组件告知应用程序更新完成。
- 如果缓存中数据不存在,直接更新数据库,然后返回;
Write Back(写回)策略
Write Back(写回)策略在更新数据的时候,只更新缓存,同时将缓存数据设置为脏的,然后立马返回,并不会更新数据库。对于数据库的更新,会通过批量异步更新的方式进行。
Write Back 是计算机体系结构中的设计,比如 CPU 的缓存、操作系统中文件系统的缓存都采用了 Write Back(写回)策略。
Write Back 策略特别适合写多的场景,因为发生写操作的时候, 只需要更新缓存,就立马返回了。比如,写文件的时候,实际上是写入到文件系统的缓存就返回了,并不会写磁盘。但是带来的问题是,数据不是强一致性的,而且会有数据丢失的风险。
数据库和缓存一致性的几种更新策略
1. 先更新数据库,再更新缓存
问题:数据不一致
多线程下
2. 先更新缓存,再更新数据库
问题:数据不一致
多线程下
3. 先删除缓存,再更新数据库
问题:数据不一致
多线程下
解决方案:
- 延时双删策略:
4. 先更新数据库,再删除缓存
问题:数据不一致
多线程下
但是在实际中,这个问题出现的概率并不高。因为缓存的写入通常要远远快于数据库的写入
针对删除缓存异常的情况,解决方案:
- 重试机制:引入消息队列把删除缓存要操作的数据加入消息队列,删除缓存失败则从队列中重新读取数据再次删除,删除成功就从队列中移除
- 订阅MySql binlog,再操作缓存:更新数据库成功,就会产生一条变更日志,记录在 binlog 里。订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除。Canal。
缓存雪崩/缓存击穿/缓存穿透
缓存雪崩
redis故障或者redis中大量的缓存数据同时失效,导致大量的请求直接打到数据库或其他后端系统,造成系统性能急剧下降甚至宕机的现象。
解决:
-
大量数据同时过期:
-
均匀设置过期时间或不过期:设置过期时间时可以加上一个随机数
-
互斥锁:保证同一时间只有一个请求访问数据库来构建缓存
-
后台更新缓存:业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新。
-
redis故障
-
缓存集群高可用:哨兵、集群
-
服务降级、熔断
缓存穿透
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中。缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义,可能会使后端存储负载加大,甚至可能造成后端存储宕掉。
造成缓存穿透的基本原因有两个。第一,自身业务代码或者数据出现问题,第二,一些恶意攻击、爬虫等造成大量空命中。
解决:
- 缓存空对象或缺省值:存储层不命中后,仍然将空对象或缺省值保留到缓存层中,之后再访问这个数据将会从缓存中获取。
缓存空对象会有两个问题:第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。
-
布隆过滤器:Google布隆过滤器Guava解决缓存穿透 guava
-
参数校验:在接口层对请求参数进行严格校验,过滤掉明显不合法的请求
-
增强id复杂度,避免被猜测id规律(可以采用雪花算法)
-
做好数据的基础格式校验
-
加强用户权限校验
-
做好热点参数的限流
缓存击穿
缓存击穿是指某些热点数据由于访问量大,且该数据的缓存刚好在某一时刻失效,导致大量并发请求同时击中数据库,从而造成数据库瞬时压力过大甚至宕机的现象。
与缓存雪崩不同,缓存击穿主要集中在某一条或少量几条缓存失效的热点数据上。
解决:
- 差异失效时间:开辟两块缓存,设置不同的缓存过期时间,主A从B,先更新B再更新A,先查询A没有再查询B
- 加锁策略,保证同一时间只有一个业务线程更新缓存
- 不给热点数据设置过期时间,后台更新缓存
- 接口限流、熔断与降级
缓存过期删除/内存淘汰策略
redis默认内存多少可用?
如果不设置最大内存或者设置最大内存大小为0,在64位操作系统下不限制内存大小,在32位操作系统下最多使用3GB内存
注意:在64bit系统下,maxmemory设置为0表示不限制redis内存使用
一般推荐Redis设置内存为最大物理内存的¾
如何修改redis内存设置?
-
通过修改文件配置
maxmemory
-
通过命令修改,但是redis重启后会失效
config set maxmemory SIZE
什么命令查看redis内存使用情况:
info memory
config get maxmemory
过期删除策略
- 立即/定时删除:在设置 key 的过期时间时,同时创建一个定时事件,当时间到达时,由事件处理器自动执行 key 的删除操作。
- 惰性删除:不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。节省CPU成本,但存在内存泄露。
- 定期删除:每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。超过一定比例则重复此操作。Redis 为了保证定期删除不会出现循环过度,导致线程卡死现象,为此增加了定期删除循环流程的时间上限,默认不会超过 25ms。缺点是难以确定删除操作执行的时长和频率。
- 定时任务在每个数据库空间随机检查20个键,当发现过期时删除对应的键。
- 如果超过检查数25%的键过期,循环执行回收逻辑直到不足25%或运行超时为止,慢模式下超时时间为25毫秒。
- 如果之前回收键逻辑超时,则在Redis触发内部事件之前再次以快模式运行回收过期键任务,快模式下超时时间为1毫秒且2秒内只能运行1次。
- 快慢两种模式内部删除逻辑相同,只是执行的超时时间不同。
Redis 选择「惰性删除+定期删除」这两种策略配和使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡。
内存淘汰策略
超过redis设置的最大内存,就会使用内存淘汰策略删除符合条件的key
LRU:最近最少使用页面置换算法,淘汰最长时间未被使用的页面,看页面最后一次被使用到发生调度的时间长短,首先淘汰最长时间未被使用的页面。
LFU:最近最不常用页面置换算法,淘汰一定时期内被访问次数最少的页面,看一定时间段内页面被使用的频率,淘汰一定时期内被访问次数最少的页
淘汰策略有哪些(Redis7版本):
- noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息
- 对设置了过期时间的数据中进行淘汰
- LRU
- LFU
- random
- TTL:优先淘汰更早过期的key
- 全部数据进行淘汰
- LRU
- LFU
- random
如何修改 Redis 内存淘汰策略?
-
config set maxmemory-policy <策略>
设置之后立即生效,不需要重启 Redis 服务,重启 Redis 之后,设置就会失效。 -
通过修改 Redis 配置文件修改,设置“
maxmemory-policy <策略>
”,它的优点是重启 Redis 服务后配置不会丢失,缺点是必须重启 Redis 服务,设置才能生效。
Redis 是如何实现 LRU 算法的?
传统 LRU 算法的实现是基于「链表」结构,链表中的元素按照操作顺序从前往后排列,最新操作的键会被移动到表头,当需要内存淘汰时,只需要删除链表尾部的元素即可,因为链表尾部的元素就代表最久未被使用的元素。
Redis 并没有使用这样的方式实现 LRU 算法,因为传统的 LRU 算法存在两个问题:
- 需要用链表管理所有的缓存数据,这会带来额外的空间开销;
- 当有数据被访问时,需要在链表上把该数据移动到头端,如果有大量数据被访问,就会带来很多链表移动操作,会很耗时,进而会降低 Redis 缓存性能。
Redis 实现的是一种近似 LRU 算法,目的是为了更好的节约内存,它的实现方式是在 Redis 的对象结构体中添加一个额外的字段,用于记录此数据的最后一次访问时间。
当 Redis 进行内存淘汰时,会使用随机采样的方式来淘汰数据,它是随机取 N 个值,然后淘汰最久没有使用的那个。
Redis 实现的 LRU 算法的优点:
- 不用为所有的数据维护一个大链表,节省了空间占用;
- 不用在每次数据访问时都移动链表项,提升了缓存的性能;
Redis 是如何实现 LFU 算法的?
LFU 算法相比于 LRU 算法的实现,多记录了「数据的访问频次」的信息。
Redis 对象头中的 lru 字段,在 LRU 算法下和 LFU 算法下使用方式并不相同。
在 LRU 算法中,Redis 对象头的 24 bits 的 lru 字段是用来记录 key 的访问时间戳,因此在 LRU 模式下,Redis可以根据对象头中的 lru 字段记录的值,来比较最后一次 key 的访问时间长,从而淘汰最久未被使用的 key。
在 LFU 算法中,Redis对象头的 24 bits 的 lru 字段被分成两段来存储,高 16bit 存储 ldt(Last Decrement Time),用来记录 key 的访问时间戳;低 8bit 存储 logc(Logistic Counter),用来记录 key 的访问频次。
布隆过滤器
布隆过滤器由「初始值都为 0 的 bit 数组」和「 N 个哈希函数」两部分组成,用来快速判断集合是否存在某个元素。
当我们在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。
布隆过滤器会通过 3 个操作完成标记:
- 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值;
- 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置。
- 第三步,将每个哈希值在位图数组的对应位置的值设置为 1;
一个元素如果判断结果:存在→元素可能存在;不存在→元素一定不存在
布隆过滤器只能添加元素,不能删除元素,因为布隆过滤器的bit位可能是共享的,删掉元素会影响其他元素导致误判率增加
应用场景:
- 解决缓存穿透问题
- 黑白名单校验
为了解决布隆过滤器不能删除元素的问题,布谷鸟过滤器横空出世。https://www.cs.cmu.edu/~binfan/papers/conext14_cuckoofilter.pdf#:~:text=Cuckoo%20%EF%AC%81lters%20support%20adding%20and%20removing%20items%20dynamically,have%20lower%20space%20overhead%20than%20space-optimized%20Bloom%20%EF%AC%81lters.
分布式锁
https://redis.io/docs/latest/develop/use/patterns/distributed-locks/#implementations
基于 Redis 节点实现分布式锁时,对于加锁操作,我们需要满足三个条件。
- 加锁包括了读取锁变量、检查锁变量值和设置锁变量值三个操作,但需要以原子操作的方式完成,所以,我们使用 SET 命令带上 NX 选项来实现加锁;
- 锁变量需要设置过期时间,以免客户端拿到锁后发生异常,导致锁一直无法释放,所以,我们在 SET 命令执行时加上 EX/PX 选项,设置其过期时间;
- 锁变量的值需要能区分来自不同客户端的加锁操作,以免在释放锁时,出现误释放操作,所以,我们使用 SET 命令设置锁变量值时,每个客户端设置的值是一个唯一值,用于标识客户端;
满足这三个条件的分布式命令如下:
而解锁的过程就是将 lock_key 键删除(del lock_key),但不能乱删,要保证执行操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的 unique_value 是否为加锁客户端,是的话,才将 lock_key 键删除。
可以看到,解锁是有两个操作,这时就需要 Lua 脚本来保证解锁的原子性,因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。
基于 Redis 实现分布式锁的优点:
- 性能高效(这是选择缓存实现分布式锁最核心的出发点)。
- 实现方便。因为 Redis 提供了 setnx 方法,实现分布式锁很方便。
基于 Redis 实现分布式锁的缺点:
- 锁的可重入问题。
-
没有重试机制。
-
超时时间不好设置。可以基于续约的方式设置超时时间:先给锁设置一个超时时间,然后启动一个守护线程,让守护线程在一段时间后,重新设置这个锁的超时时间
-
Redis 主从复制模式中的数据是异步复制的,这样导致分布式锁的不可靠性。如果在 Redis 主节点获取到锁后,在没有同步到其他节点时,Redis 主节点宕机了,此时新的 Redis 主节点依然可以获取锁,所以多个应用服务就可以同时获取到锁。
Redisson
Redisson分布式锁原理
可重入:利用hash结构记录线程id和重入次数
可重试:利用信号量和Pub/Sub功能实现等待、唤醒,获取锁失败的重试机制
超时续约:利用watchDog,每个一段时间(releaseTime),重置超时时间。
Redisson可重入锁原理
在分布式锁中,采用hash结构来存储锁,其中外层key表示这把锁是否存在,内层key则记录当前这把锁被哪个线程持有。
获取锁:
使用lua脚本,通过 exists + hexists + hincrby
保证只有一个线程成功设置键。
释放锁:
watchdog
主要用来避免锁在超时后业务逻辑还未执行完,锁被自动释放的情况,通过定期刷新锁的过期时间来实现自动续期。
- 如果当前分布式锁未设置过期时间,Redisson基于Netty时间轮启动一个定时任务,定期向redis发送命令更新锁的过期时间,默认每10s发送一次请求,每次续期30s
- 当客户端主动释放锁时,Redisson会取消看门狗刷新操作。如果客户端宕机了,定时任务无法执行,等超时时间到锁自动释放。
RedLock
基本思想:部署多个redis实例(>=5),客户端向多个实例上申请加锁,如果客户端能够和半数以上的节点成功地完成加锁操作,就认为客户端成功地获得分布式锁,否则加锁失败。
这样一来,即使有某个 Redis 节点发生故障,因为锁的数据在其他节点上也有保存,所以客户端仍然可以正常地进行锁操作,锁的数据也不会丢失。
客户端只有在满足下面的这两个条件时,才能认为是加锁成功:
-
客户端从超过半数(大于等于N/2+1)的Redis节点上成功获取到了锁;
-
客户端获取锁的总耗时没有超过锁的过期时间。
缺点:
- 复杂性:需要多个redis实例,增加了系统的复杂性和维护成本
- 不适用于高并发:需要访问多个实例同时尝试获取锁,可能导致锁性能下降
- 锁的续期问题
过期键
Redis 持久化时,对过期键会如何处理的?
Redis 持久化文件有两种格式:RDB(Redis Database)和 AOF(Append Only File)。
RDB:
-
RDB 文件生成阶段:从内存状态持久化成 RDB文件的时候,会对 key 进行过期检查,过期的键不会被保存到新的 RDB 文件中,因此 Redis 中的过期键不会对生成新 RDB 文件产生任何影响。
-
RDB 文件加载阶段:
- 主服务器模式运行:在载入 RDB 文件时,程序会对文件中保存的键进行检查,过期键不会被载入到数据库中。所以过期键不会对载入 RDB 文件的主服务器造成影响;
- 从服务器模式运行:在载入 RDB 文件时,不论键是否过期都会被载入到数据库中。但由于主从服务器在进行数据同步时,从服务器的数据会被清空。所以一般来说,过期键对载入 RDB 文件的从服务器也不会造成影响。
AOF:
- AOF 文件写入阶段:当 Redis 以 AOF 模式持久化时,如果数据库某个过期键还没被删除,那么 AOF 文件会保留此过期键,当此过期键被删除后,Redis 会向 AOF 文件追加一条 DEL 命令来显式地删除该键值。
- AOF 重写阶段:执行 AOF 重写时,会对 Redis 中的键值对进行检查,已过期的键不会被保存到重写后的 AOF 文件中,因此不会对 AOF 重写造成任何影响。
Redis 主从模式中,对过期键会如何处理?
服务器运行在复制模式下时,从服务器的过期键删除动作由主服务器控制:
- 主服务器删除一个过期键后,会向从服务器发送DEL命令告知删除过期键。
- 从服务器在执行客户端发送的读命令时,即使碰到过期键也不会删除,而是像处理未过期键一样处理过期键。
- 从服务器只有接收到主服务器的DEL命令后,才会删除过期键。