当前位置:   article > 正文

redis键的生命周期和持久化操作_redis 生命周期

redis 生命周期

1. Redis数据库

1.1 Redis数据库原理

Redis服务器将所有数据库都保存在服务器状态redis.h/redisServer结构的db数组中,db数组的每个项都是一个redis.h/redisDb结构,每个redisDb代表着一个数据库。

struct redisServer {
	// ...
    
    // 一个数组,保存着服务器中的所有数据库
    redisDb *db;
    
    // ...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

而数据库的个数则有dbnum来决定,在创建的时候服务器状态会根据次属性来创建相应数量的数据库。

struct redisServer {
    // ...
    
    // 服务器的数据库数量
    int dbnum;
    
    // ...
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如果我们对此值进行设置的话,默认此值为16。

在redisDb结构中有一个dict *dict属性保存了数据库中所有的键值对,我们将这个字典称为键空间(key space)。键空间的键也就是数据库的键,每个键都是一个字符串对象,键空间的值是数据库的值,每个键可以是字符串对象、列表对象、哈希对象、集合对象和有序集合对象中的任意一种Redis对象。如下就是一个键空间例子:

image-20210201221444682

1.2 键的生存时间和过期时间

使用过redis的同学都知道通过EXPIRE命令或者PEXPIRE命令,客户端就可以以秒或者毫秒的精度为数据库中的某个键设置生存时间,在经过设置的时间后,服务器就会自动删除生存时间为0的键。

同时可以使用EXPIREAT命令或者PEXPIREAT命令,以秒为单位或者以毫秒精度给数据库中的某个键设置过期时间。

事实上述这四个命令最终都可以转化成PEXPIREAT命令来实现的。在redisDb结构中存在dict *expires过期字典属性来保存数据库键的过期时间。如下图就是保存了过期时间字典的数据库例子:

image-20210201223251805

通过这个过期字典,程序可以用以下步骤检查一个给定键值是否过期:

  1. 检查给定键是否存在过期字典,如果存在,那么取得键的过期时间。
  2. 检查当前UNIX时间戳是否大于键的过期时间,如果是的话,那么键已经过期,否则的话,键未过期。

1.3 过期键删除策略

一般在键过期后,会有三种删除策略可供选择:定时删除、定期删除、惰性删除,前两种属于主动删除策略,最后一种则为被动删除策略。

策略名称详情介绍
定时删除在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。
定期删除每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于删除多少过期键,以及要删除多少个数据库,则由算法决定。
惰性删除放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期的话就返回该键。

redis实际只使用了惰性删除定期删除两种策略,两种删除策略相互配合。

2. 持久化

2.1 RDB持久化

2.1.1 保存与还原

是Redis提供的持久化功能之一,这个功能可以将Redis在内存中的数据库状态保存到一个RDB文件中,而通过该功能所生成的文件就是RDB文件,是一个经过压缩的二进制文件。通过这个二进制文件就可以还原生成RDB文件时的数据库状态。其逻辑如下图所示:

image-20210204171445738

在Redis中生成RDB文件中可以使用两个指令,一个是SAVE,另外一个是BGSAVE。两个命令之间存在显著的差别。

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

BGSAVE指令通过派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程继续处理命令请求。但是在BGSAVE命令执行过程中,服务器SVAEBGSAVEBGREWRITEAOF三个命令的方式将会和平时有所不同。这里具体有什么不同我们先讲前两个,最后一个属于AOF持久化过程的命令,因此我们在下面讲到AOF的时候再讲。在BGSAVE命令执行的时候,客户端的SAVE会被直接拒绝,因为如果SAVE和BGSAVE两个命令同时执行,很有可能导致父进程(服务器进程)和子进程同时执行两个rdbSave()函数的调用而导致出现竞争条件。同理,在BGSAVE命令在执行的时候,客户端在发送一个BGSAVE请求也会被服务器拒绝。理由和刚才所说类似,也是为了防止同时存在两个子进程都调用rdbSave()函数而导致的竞争条件。

文字描述可能不是很清晰,可以通过下面两段伪代码很清晰的看出两个指令之间的区别:

def SAVE():
    # 创建REB文件的实际工作实际上都是通过这个方法来实现的
    rdbSave()
# ------------------------------------------------------- #
def BGSAVE():
    
    # 创建子进程
    pid = fork()
    
    if pid == 0:
        
        # 子进程负责创建RDB文件
        rdbSave()
        
        # 完成之后向父进程发送信号
        signal_parent()
    elif pid > 0:
        
        # 父进程继续处理命令请求,并通过轮询等待子进程信号
        handle_request_and_wait_signal()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

**而对于将RDB文件载入到服务器中,则是由Redis自动执行的,因此也就没有专门用于加载RDB文件的指令了。**和rdbSave()函数类似Redis定义了一个rdbLoad()函数来对专门对持久化文件进行载入,也就是说这个方法也可以用来载入接下来要说的AOF持久化文件。而且由于AOF的更新频率往往要高于RDB文件,因此如果服务器开启了AOF持久化功能,服务器默认优先载入AOF来还原数据,如果没有开启AOF持久化功能才会使用RDB文件来还原数据库状态。

和创建RDB文件不同可以选择阻塞服务器或者不阻塞服务器的方法,在载入RDB文件的时候,服务器会处于阻塞状态,直到载入工作完成。

2.1.2 自动间隙保存

正如我们上文所说,BGSAVE命令在执行的时候不会阻塞服务器,那么Redis允许我们通过设置服务器的save选项,让服务器每个一段时间自动执行一个BGSAVE并且不会影响到服务器线程的其他操作。

我们可以对save选项设置多个保存条件,只要满足其中任意一条,服务器就会执行BGSAVE命令。如下面是redis的默认save条件:

save 900 1
save 300 10
save 60 10000
  • 1
  • 2
  • 3

上述三个条件分别表示为900秒内对数据库进行至少1次修改、300秒内对数据库至少进行10次修改、60秒内至少对数据进行10000次修改。只要其中任意一个条件被满足,服务器就就会自动执行BGSAVE命令。

image-20210204200335196

上图就是reids关于自动间隙保存相关的属性,saveparams数组属性保存了所有的保存条件,dirty则保存自上一次保存后服务秦对数据库状态修改的次树。lastsave属性则保存了上一次保存时的时间戳。redis中的serverCron函数通过一定的间隔时间(默认为100毫秒)循环对服务器进行维护,其中有一件工作就是检查save选项的条件情况,如果满足就执行BGSAVE命令,同时dirty置为0。

2.2 AOF持久化

AOF持久化是redis提供的另外一种和RDB持久化方式不同的持久化方式。RDB通过将当前服务器中的键值对记录以快照的方式保存到磁盘中,而AOF是通过保存Redis服务器所执行的写命令来记录数据库的状态

2.2.1 持久化实现

在AOF持久化的实现功能可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。

命令追加过程其实就是服务器在执行完一个写命令之后,会以协议格式(这个又是另外一个概念了,可以网络查询redis请求协议格式)将被执行的写命令追加到服务器状态(也就是redisServer)的aof_buf缓冲区的末尾。

文件写入则是条用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区写入AOF文件中。

而文件同步就是考虑是否需要在写入AOF文件的时候,同时对AOF文件的内容进行同步处理。此同步属性的设置通过appednfync值来进行配置。不同的appendfsync值产生不同的持久化行为,其关系如下图所示:

appendfsync选项值flushAppendOnlyFile函数行为
always将aof_buf缓存区中的所有内存够写入并同步到AOF文件
everysec将aof_buf缓冲区中的所有内容写入到AOF文件中,如果上次同步AOF文件的时间距离现在超过一秒钟,那么再次对AOF文件进行同步,并且这个同步操作是由一个线程专门负责执行的。
no将aof_buf缓冲区中的所有内容写入到AOF文件,但并不对AOF文件进行同步,何时同步由操作系统来决定。
2.2.2 文件载入和数据还原

基本过程就是:

  1. 客户端读取AOF文件
  2. 从AOF文件中分析并读取出一条写命令
  3. 使用客户端执行被读出的写命令
  4. 一直执行步骤2和步骤3直到所有的写命令都被处理完毕为止。

当上述过程执行完之后,AOF文件所保存的数据状态就会被完整的还原出来。

image-20210204204110779

2.2.3 AOF重写

由于AOF持久化过程是通过保存被执行的写命令来记录数据库状态的,所以随着写命令越来越多,AOF文件中的内容也会越来越多,文件的体积也就会越来越大。如果AOF体积过大的则有可能导致redis服务器受到影响,且解析AOF文件所需要的时间将会非常久。因此需要尽量的减少AOF文件的体积大小,因此redis提供了一种AOF重写功能。

AOF重写功能就是创建一个新的AOF文件来替换原有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但是新AOF文件削减了久AOF文件中的冗余命令,体积将会小很多。文件重写通过BGREWRITEAOF命令来实现。上面我们说过在执行BGSAVE和执行BGREWRITEAOF命令将会出现一些不同这补充一下:

  • 在执行BGSAVE指令的时候客户端请求执行BGREWRITEAOF指令,BGREWRITEAOF将会被延迟到BGSAVE执行完之后在执行。
  • 如果BGREWRITEAOF指令的时候客户端请求执行BGSAVE指令,BGSAVE请求将会被服务器拒绝。

虽然两个命令都是通过子进程来执行,但是服务器并不允许两者同时执行,因为这两个操作都是在执行大量的磁盘写入操作,并发的执行这两个子进程会给redis带来很大的性能影响。

下面详细的介绍一下重写的实现过程。

2.2.3.1 AOF重写的实现

虽然redis将生成新的AOF文件替换旧的AOF文件称为“AOF文件重写”,但是实际上,AOF文件重写并没有对现有的AOF文件进行了任何读取、分析或者写入操作,而是完全通过当前数据库的状态来实现的。

详细过程如下图所示

image-20210204211052086

在客户端发送了重写请求后,redis会生成一个子进程,通过子进程生成当前数据库状态的快照。使用快照中的内容通过aof_rewrite函数将所有所有状态信息写入到临时文件中。由于是采用子进程来执行此过程,服务器并没有被阻塞,还能进行别的请求操作。如果又有写的操作,将新的写操作继续追加写到旧AOF文件后面,通过通过缓存将这些命令缓存起来。在快照内容全部都写入到临时文件后,通知主进程,将缓存中的命令刷入到临时文件中。最后一步使用临时文件替换现存AOF旧文件。

2.3 持久化过期键的处理

RDB对过期键的处理策略:在我们执行了SAVE或者BGSAVE命令创建出RDB文件之后,程序会对数据库中的过期键检查,已过期的键不会被保存在RDB文件中。在载入RDB文件时,程序同样会对RDB文件中的过期键进行检查,过期键会被忽略。
AOF对归期间的处理策略:由于redis采用的是定时删除和惰性删除策略,如果某个键已经过期,但是还没有被删除,AOF不会对这个过期键进行任何操作。一旦这个过期键被删除,则AOF将对这条件追加一条DEL指令表示该键已经被删除。而如果在重写的时候,程序会对子进程生成的RDB文件进行过期键搜索,如果出现了过期键则被忽略不会被写入到AOF文件中。

2.4 两种持久化之间的总结

RDB持久化体积更小,且载入后恢复数据十分的快,但是如果在定时执行BGSAVE命令之前服务器宕机,则将损失当前没来得及持久化的所有数据。

期键被删除,则AOF将对这条件追加一条DEL指令表示该键已经被删除。而如果在重写的时候,程序会对子进程生成的RDB文件进行过期键搜索,如果出现了过期键则被忽略不会被写入到AOF文件中。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/695177
推荐阅读
相关标签
  

闽ICP备14008679号