Redis(八) 备份、删除和淘汰机制

一、备份机制

AOFRDB和复制功能对于过期键的处理:

①RDB对过期键的处理机制

​ 在执行SAVE命令或者BGSAVE命令创建一个新的RDB文件时,程序会对数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件中。
​ 举个例子,如果数据库中包含三个键k1、k2、k3,并且k2已经过期,那么当执行SAVE命令或者BGSAVE命令时,程序只会将k1k3的数据保存到RDB文件中,而k2则会被忽略。
因此,数据库中包含过期键不会对生成新的RDB文件造成影响。

载入RDB文件:

在启动Redis服务器时,如果服务器开启了RDB功能,那么服务器将对RDB文件进行载入:

  • 如果服务器以主服务器模式运行,那么在载入RDB文件时,程序会对文件中保存的键进行检查,未过期的键会被载入到数据库中,而过期键则会被忽略,所以过期键对载入RDB文件的主服务器不会造成影响;
  • 如果服务器以从服务器模式运行,那么在载入RDB文件时,文件中保存的所有键,不论是否过期,都会被载入到数据库中。不过,因为主从服务器在进行数据同步的时候,从服务器的数据库就会被清空,所以一般来讲,过期键对载入RDB文件的从服务器也不会造成影响;

综上所述:持久化时已过期的键不会保存到新生成的rdb文件中;

redis服务器(主服务器)启动时,载入持久化的数据到内存时,已过期的键不会载入到内存;

redis服务器(从服务器)启动时,载入持久化的数据到内存时,已过期的键也会载入到内存,但是当与主服务器进行数据同步时,从服务器的数据会被清空。

因此不管是写入rdb文件还是读取rdb文件的数据过期键都是无效的。

AOF对过期键的处理机制

当服务器以AOF持久化模式运行时,如果数据库中的某个键已经过期,但它还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生任何影响。

当过期键被惰性删除或者定期删除之后,程序会向AOF文件追加(append)一条DEL命令,来显式地记录该键已被删除。
举个例子,如果客户端使用GET message命令,试图访问过期的message键,那么服务器将执行以下三个动作:
1)从数据库中删除message键。
2)追加一条DEL message命令到AOF文件。(根据AOF文件增加的特点,AOF只有在客户端进行请求的时候才会有这个DEL操作)
3)向执行GET命令的客户端返回空回复。

这部分就是Redis中的惰性删除策略中expireIfNeeded函数的使用。关于惰性删除策略这一部分在Redis惰性删除策略一篇中有讲。所以这里就不赘述了。

需要提示一下的是:expireIfNeeded函数是在db.c/lookupKeyRead()函数中被调用,lookupKeyRead函数用于在执行读取操作时取出键key在数据库db中的值。

AOF重写:

和生成RDB文件时类似,在执行AOF重写的过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的AOF文件中。

举个例子,如果数据库中包含三个键k1、k2、k3,并且k2已经过期,那么在进行重写工作时,程序只会对k1k3进行重写,而k2则会被忽略。这一部分如果掌握了AOF重写的方法的话,那就自然理解了。

综上所述:因为aof保存的操作命令,所以键的读写操作命令都会被保存在aof文件中,当键过期时,通过惰性删除或者定期删除之后,aof会保存相应的删除命令,显式记录该键已经被删除。

redis服务器通过aof文件方式将持久化数据load到内存时,遇到过期键的处理机制和rdb方式将持久化数据load到内存时的处理方式一样。

因此不管时写入aof文件还是读取aof文件的数据过期键都是无效的。

③复制功能对过期键的处理机制

当服务器运行在复制模式下时,从服务器的过期键删除动作由主服务器控制:

  • 主服务器在删除一个过期键之后,会显式地向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键
  • 从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是继续像处理未过期的键一样来处理过期键;
  • 从服务器只有在接到主服务器发来的DEL命令之后,才会删除过期键。

举个例子,有一对主从服务器,它们的数据库中都保存着同样的三个键messagexxxyyy,其中message为过期键,如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nWjEJKFx-1614242420907)(F:/研一下/Redis/img1/主从服务器复制功能对于过期键的处理.png)]

如果这时有客户端向从服务器发送命令GET message,那么从服务器将发现message键已经过期,但从服务器并不会删除message键,而是继续将message键的值返回给客户端,就好像message键并没有过期一样。

假设在此之后,有客户端向主服务器发送命令GET message,那么主服务器将发现键message已经过期:主服务器会删除message键,向客户端返回空回复,并向从服务器发送DEL message命令,如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZSNzrJUA-1614242420909)(F:/研一下/Redis/img1/客户端向从服务器拿取过期键.png)]

从服务器在接收到主服务器发来的DEL message命令之后,也会从数据库中删除message键,在这之后,主从服务器都不再保存过期键message了,如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTJjm5YH-1614242420910)(F:/研一下/Redis/img1/从服务器经过主服务器发来命令之后的变化.png)]

综上所述:当服务器运行在复制模式下,从服务器的过期键的删除动作有主服务器控制;

主服务器在删除一个过期键之后,会显式的向所有从服务器发送一条DEL命令,告知从服务器删除这个过期键

从服务器在处理客户端发送过来的查询命令时,即使碰到过期键也不会将过期键删除,而是像处理未过期键一样来处理这个过期键

从服务器只有在接收到主服务器发送过来的删除命令之后,才会删除过期键。

RDB持久化

RDB 持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个RDB 文件中。RDB 持久化功能所生成的 RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成 RDB文件时的数据库状态。

有两个 Redis 命令可以用于生成 RDB 文件,一个是 SAVE,另一个是 BGSAVE

  • SAVE 命令会阻塞Redis 服务器进程,直到RDB 文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求;

  • BGSAVE 命令会派生出一个子进程,然后由子进程负责创建 RDB 文件,父进程继续处理命令请求。

RDB 文件的载入工作是在服务启动时自动执行的,所以 Redis 并没有专门用于载入 RDB 文件的命令,只要 Redis 服务器在启动时检测到 RDB 文件存在,它就会自动载入 RDB 文件

因为 AOF 文件的更新频率通常比 RDB 文件的更新频率高,所以如果服务器开启了 AOF 持久化功能,那么服务器会优先使用 AOF 文件来还原数据库状态
只有在 AOF 持久化功能处于关闭状态时,服务器才会使用 RDB 文件来还原数据库状态

SAVE 命令和BGSAVE 命令执行时的服务器状态:

①SAVE 命令执行时的服务器状态:

  • 当 SAVE 命令执行时,Redis 服务器会被阻塞,所以当 SAVE 命令正在执行时,客户端发送的所有命令请求都会被拒绝;
  • 只有在服务器执行完 SAVE 命令、重新开始接受命令请求之后,客户端发送的命令才会被处理。

②BGSAVE 命令执行时的服务器状态:

  • 子进程创建 RDB 文件的过程中,Redis 服务器仍然可以继续处理客户端的命令请求,但是,在 BGSAVE 命令执行期间,服务器处理 SAVE、BGSAVE、BGREWRITEAOF 三个命令的方式会和平时有所不同;
  • 首先,在 BGSAVE 命令执行期间,客户端发送的 SAVE 命令会被服务器拒绝,服务器禁止 SAVE 命令和 BGSAVE 命令同时执行是为了避免父进程和子进程同时执行两个 rdbSave 调用,防止产生竞争条件;
  • 其次,在 BGSAVE 命令执行期间,客户端发送的 BGSAVE 命令会被服务器拒绝,因为同时执行两个 BGSAVE 命令也会产生竞争条件;
  • 最后,BGREWRITEAOF 和 BGSAVE 两个命令不能同时执行
    • 如果 BGSAVE 命令正在执行,那么客户端发送的 BGREWRITEAOF 命令会被延迟到 BGSAVE 命令执行完毕之后执行
    • 如果 BGREWRITEAOF 命令正在执行,那么客户端发送的 BGSAVE 命令会被服务器拒绝
    • 因为 BGREWRITEAOF 和 BGSAVE 两个命令的实际工作都由子进程执行,所以这两个命令在操作方面并没有什么冲突的地方,不能同时执行它们只是一个性能方面的考虑一一并发出两个子进程,并且这两个子进程都同时执行大量的磁盘写入操作

间隔性自动保存:

因为 BGSAVE 命令可以在不阻塞服务器进程的情况下执行,所以 Redis 允许用户通过设置服务器配置的 save 选项,让服务器每隔一段时间自动执行一次 BGSAVE 命令

用户可以通过 save 选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行 BGSAVE 命令
举个例子,如果我们向服务器提供以下配置:

save 900 1
save 300 10
save 60 10000

那么只要满足以下三个条件中的任意一个,BGSAVE 命令就会被执行:

  • 服务器在 900 秒之内,对数据库进行了至少 1 次修改
  • 服务器在 300 秒之内,对数据库进行了至少 10 次修改
  • 服务器在 60 秒之内,对数据库进行了至少 10000 次修改

设置保存条件:

通过设置 Redis 服务器配置的 save 选项,可以让服务器每隔一段时间自动执行一次 BGSAVE 命令;可以配置多个规则,只要满足其中一个规则就执行

服务器程序会根据 save 选项设置服务器状态 redisServer 结构的 saveparams 属性

检查保存条件是否满足:

Redis 的服务器周期性操作函数 serverCron 默认每隔 100 毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,它的其中一项工作就是检查 save 选项所设置的保存条件是否已经满足,如果满足的话,就执行 BGSAVE 命令

算法实现:
遍历并检查 saveparams 数组中所有的保存条件,逐一与服务器状态的 dirty 属性和 lastsave 属性进行比较,只要有任意一个条件满足就执行 BGSAVE 命令

AOF持久化

RDB持久化是将进程数据写入文件,而AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中(有点像MySQL的binlog);当Redis重启时再次执行AOF文件中的命令来恢复数据。

与RDB相比,AOF的实时性更好,因此已成为主流的持久化方案。

Redis服务器默认开启RDB,关闭AOF;要开启AOF,需要在配置文件中配置:

appendonly yes//开启AOF

执行流程:

由于需要记录Redis的每条写命令,因此AOF不需要触发,下面介绍AOF的执行流程。AOF的执行流程包括:
命令追加(append):将Redis的写命令追加到缓冲区aof_buf;
文件写入(write)和文件同步(sync):根据不同的同步策略将aof_buf中的内容同步到硬盘;
文件重写(rewrite):定期重写AOF文件,达到压缩的目的。

(1)命令追加:

Redis先将写命令追加到缓冲区,而不是直接写入文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈。
命令追加的格式是Redis命令请求的协议格式,它是一种纯文本格式,具有兼容性好、可读性强、容易处理、操作简单避免二次开销等优点;具体格式略。在AOF文件中,除了用于指定数据库的select命令(如select 0 为选中0号数据库)是由Redis添加的,其他都是客户端发送来的写命令。

(2)文件写入和文件同步:

Redis提供了多种AOF缓存区的同步文件策略,策略涉及到操作系统的write函数和fsync函数,说明如下:
为了提高文件写入效率,在现代操作系统中,当用户调用write函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。这样的操作虽然提高了效率,但也带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失;因此系统同时提供了fsync、fdatasync等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。

AOF缓存区的同步文件策略由参数appendfsync控制,各个值的含义如下:
always:命令写入aof_buf后立即调用系统fsync操作同步到AOF文件,fsync完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。
no:命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。
everysec:命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置。

二、删除机制

1、键的生存时间和过期时间

设置生存时间或者过期时间:

​ ①expire expire key 5 该键值对秒后过期,pexpire以毫秒精度为数据库中的某个键设置生存时间;

​ ②expireat expireat key XXX 该键值对在某时某刻过期,时间戳,pexpireat将键key的过期时间设置为timestamp所指定的毫秒数时间戳;

​ ③通过TTL/PTTL查询该键值对剩余的生存时间(单位分别是秒和毫秒)。

设置时间戳的底层实现:expire pexpire expireat都是转换为pexpireat来实现的。

当键值对从dict中删除时候,也要删除该键值对的过期 expire

persist命令移除一个键值对的过期值,pexpierat的反操作。

2、过期键的删除策略

如何判定一个键已经过期:

​ ①检查给定的键值对(键)是否存在于过期字典中,如果不存在直接返回,如果存在,则获取过期时间

​ ②检查当前时间UNIX是否大于给定时间,如果大于则过期;

​ ③通过TTL/PTTL获取时间戳,查看该键值对所对应的值是否大于0,如果是则代表存活 。

​ 实际过程中,访问过期字典会比执行一个命令稍微快一些。

过期键的删除策略:
a.定时删除:
在设置键过期时间的同时,创建一个定时器(Timer),让定时器在键的过期时间来临时,立即执行对键的删除操作;

b.定期删除:
每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键值对;

c.懒惰删除:
放置不理,但每次查找时候,都会首先检查该键值对是否设定过期,如果设定则判断是否过期,如果过期删除键值对 以及时间过期。然后就返回空。

三种删除策略的特点:

定时删除是对内存非常友好,但是对CPU不友好,降低吞吐量;
懒惰删除是对内存最不友好,但对CPU最友好,如果某一个键值对一直没有访问会造成内存溢出问题;

定期删除是前两种策略的折中。因为定期删除每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对于CPU时间的影响;除此之外,通过定期删除过期键,定期删除策略有效地减少了因为过期键而带来的内存浪费。

分析完三种删除策略的特点后,redis实际中对于键的删除策略是惰性删除和定期删除两种。通过配合使用这两种删除策略,服务器可以很好的合理使用CPU时间和避免浪费内存空间之间取得平衡。

三、淘汰机制

为什么会有淘汰?
Redis可以看作是一个内存数据库,可以通过Maxmemory指令配置Redis的数据集使用指定量的内存。设置maxmemory0,则表示无限制(这是64位系统的默认行为,而32位系统使用3GB内隐记忆极限)。
当内存使用达到maxmemory极限时,需要使用某种淘汰算法来决定清理掉哪些数据,以保证新数据的存入。

如果需要设置100mb的内存,则需要这么设置maxmemory:

maxmemory 100mb

redis设置了合适的maxmemory时,就需要选择一个淘汰算法策略(置换策略)。

常用的淘汰算法:

FIFOFirst In First Out,先进先出。判断被存储的时间,离目前最远的数据优先被淘汰;
LRULeast Recently Used,最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰;
LFULeast Frequently Used,最不经常使用。在一段时间内,数据被使用次数最少的,优先被淘汰。

Redis3.0提供的淘汰策略:

  • noeviction: 不进行置换,表示即使内存达到上限也不进行置换,所有能引起内存增加的命令都会返回error
  • allkeys-lru: 优先删除掉最近最不经常使用的key,用以保存新数据;
  • volatile-lru: 只从设置失效(expire set)的key中选择最近最不经常使用的key进行删除,用以保存新数据;
  • allkeys-random: 随机从all-keys中选择一些key进行删除,用以保存新数据;
  • volatile-random: 只从设置失效(expire set)的key中,选择一些key进行删除,用以保存新数据;
  • volatile-ttl: 只从设置失效(expire set)的key中,选出存活时间(TTL)最短的key进行删除,用以保存新数据。

通常,根据经验会有这些置换方法:

  • 在所有的 key 都是最近最经常使用,那么就需要选择 allkeys-lru 进行置换最近最不经常使用的 key,如果你不确定使用哪种策略,那么推荐使用 allkeys-lru
  • 如果需要循环读写所有的key, 或者各个key的访问频率差不多,那么可以选用 allkeys-random 策略去置换数据;
  • 假如要让 Redis 根据 TTL 来筛选需要删除的key, 请使用 volatile-ttl 策略。

置换策略工作原理:

  • 客户端执行一条新命令,导致数据库需要增加数据(比如set key value)
  • Redis会检查内存使用,如果内存使用超过 maxmemory,就会按照置换策略删除一些 key
  • 新的命令执行成功

我们持续的写数据会导致内存达到或超出上限 maxmemory,但是置换策略会将内存使用降低到上限以下。

如果一次需要使用很多的内存(比如一次写入一个很大的set),那么,Redis 的内存使用可能超出最大内存限制一段时间。

近似LRU算法:

Redis 中的 LRU 不是严格意义上的LRU算法实现,是一种近似的 LRU 实现,主要是为了节约内存占用以及提升性能。Redis 有这样一个配置 —— maxmemory-samplesRedisLRU是取出配置的数目的key,然后从中选择一个最近最不经常使用的key 进行置换,默认的 5`,如下:

maxmemory-samples 5

Redis 不采用真正的 LRU 实现的原因是为了节约内存使用。虽然不是真正的 LRU实现,但是它们在应用上几乎是等价的。下图是Redis 的近似LRU 实现和理论LRU 实现的对比:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZIazJo1D-1614242420912)(F:/研一下/Redis/img1/redis的近似LRU.bmp)]

测试开始首先在 Redis 中导入一定数目的key,然后从第一个key依次访问到最后一个key,因此根据 LRU算法第一个被访问的 key 应该最新被置换,之后再增加50% 数目的 key,导致 50% 的老的key 被替换出去。

在上图中你可以看到三种类型的点,组成三种不同的区域:

  • 淡灰色的是被置换出去的key
  • 灰色的是没有被置换出去的key
  • 绿色的是新增加的key

理论LRU 实现就像我们期待的那样,最旧的 50% 数目的 key被置换出去,RedisLRU 将一定比例的旧key置换出去。

可以看到在样本数为5 的情况下,Redis3.0 要比Redis2.8 做的好很多,Redis2.8 中有很多应该被置换出去的数据没有置换出去。在样本数为10的情况下,Redis3.0 很接近真正的LRU实现。

LRU 是一个预测未来我们会访问哪些数据的模型,如果我们访问数据的形式接近我们预想——幂律,那么近似 LRU 算法实现将能处理的很好。

在模拟测试中我们可以发现,在幂律访问模式下,理论 LRURedis近似LRU的差距很小或者就不存在差距。

如果你将 maxmemory-samples设置为10,那么 Redis将会增加额外的CPU 开销以保证接近真正的 LRU性能,可以通过检查命中率来查看有什么不同。

通过 CONFIG SET maxmemory-samples动态调整样本数大小,做一些测试验证你的猜想。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值