赞
踩
Redis服务器将所有数据库都保存在服务器状态redis.h/redisServer结构的db数组中,db数组的每个项都是一个redis.h/redisDb结构,每个redisDb代表着一个数据库。
struct redisServer {
// ...
// 一个数组,保存着服务器中的所有数据库
redisDb *db;
// ...
};
而数据库的个数则有dbnum来决定,在创建的时候服务器状态会根据次属性来创建相应数量的数据库。
struct redisServer {
// ...
// 服务器的数据库数量
int dbnum;
// ...
};
如果我们对此值进行设置的话,默认此值为16。
在redisDb结构中有一个dict *dict
属性保存了数据库中所有的键值对,我们将这个字典称为键空间(key space)。键空间的键也就是数据库的键,每个键都是一个字符串对象,键空间的值是数据库的值,每个键可以是字符串对象、列表对象、哈希对象、集合对象和有序集合对象中的任意一种Redis对象。如下就是一个键空间例子:
使用过redis的同学都知道通过EXPIRE
命令或者PEXPIRE
命令,客户端就可以以秒或者毫秒的精度为数据库中的某个键设置生存时间,在经过设置的时间后,服务器就会自动删除生存时间为0的键。
同时可以使用EXPIREAT
命令或者PEXPIREAT
命令,以秒为单位或者以毫秒精度给数据库中的某个键设置过期时间。
事实上述这四个命令最终都可以转化成PEXPIREAT
命令来实现的。在redisDb结构中存在dict *expires
过期字典属性来保存数据库键的过期时间。如下图就是保存了过期时间字典的数据库例子:
通过这个过期字典,程序可以用以下步骤检查一个给定键值是否过期:
一般在键过期后,会有三种删除策略可供选择:定时删除、定期删除、惰性删除,前两种属于主动删除策略,最后一种则为被动删除策略。
策略名称 | 详情介绍 |
---|---|
定时删除 | 在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。 |
定期删除 | 每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于删除多少过期键,以及要删除多少个数据库,则由算法决定。 |
惰性删除 | 放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期的话就返回该键。 |
redis实际只使用了惰性删除和定期删除两种策略,两种删除策略相互配合。
是Redis提供的持久化功能之一,这个功能可以将Redis在内存中的数据库状态保存到一个RDB文件中,而通过该功能所生成的文件就是RDB文件,是一个经过压缩的二进制文件。通过这个二进制文件就可以还原生成RDB文件时的数据库状态。其逻辑如下图所示:
在Redis中生成RDB文件中可以使用两个指令,一个是SAVE
,另外一个是BGSAVE
。两个命令之间存在显著的差别。
SAVE
命令会阻塞Redis服务进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理**任何**命令请求。
而BGSAVE
指令通过派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程继续处理命令请求。但是在BGSAVE
命令执行过程中,服务器SVAE
、BGSAVE
、BGREWRITEAOF
三个命令的方式将会和平时有所不同。这里具体有什么不同我们先讲前两个,最后一个属于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()
**而对于将RDB文件载入到服务器中,则是由Redis自动执行的,因此也就没有专门用于加载RDB文件的指令了。**和rdbSave()
函数类似Redis定义了一个rdbLoad()
函数来对专门对持久化文件进行载入,也就是说这个方法也可以用来载入接下来要说的AOF持久化文件。而且由于AOF的更新频率往往要高于RDB文件,因此如果服务器开启了AOF持久化功能,服务器默认优先载入AOF来还原数据,如果没有开启AOF持久化功能才会使用RDB文件来还原数据库状态。
和创建RDB文件不同可以选择阻塞服务器或者不阻塞服务器的方法,在载入RDB文件的时候,服务器会处于阻塞状态,直到载入工作完成。
正如我们上文所说,BGSAVE
命令在执行的时候不会阻塞服务器,那么Redis允许我们通过设置服务器的save选项,让服务器每个一段时间自动执行一个BGSAVE
并且不会影响到服务器线程的其他操作。
我们可以对save选项设置多个保存条件,只要满足其中任意一条,服务器就会执行BGSAVE命令。如下面是redis的默认save条件:
save 900 1
save 300 10
save 60 10000
上述三个条件分别表示为900秒内对数据库进行至少1次修改、300秒内对数据库至少进行10次修改、60秒内至少对数据进行10000次修改。只要其中任意一个条件被满足,服务器就就会自动执行BGSAVE
命令。
上图就是reids关于自动间隙保存相关的属性,saveparams
数组属性保存了所有的保存条件,dirty
则保存自上一次保存后服务秦对数据库状态修改的次树。lastsave
属性则保存了上一次保存时的时间戳。redis中的serverCron
函数通过一定的间隔时间(默认为100毫秒)循环对服务器进行维护,其中有一件工作就是检查save选项的条件情况,如果满足就执行BGSAVE命令,同时dirty
置为0。
AOF持久化是redis提供的另外一种和RDB持久化方式不同的持久化方式。RDB通过将当前服务器中的键值对记录以快照的方式保存到磁盘中,而AOF是通过保存Redis服务器所执行的写命令来记录数据库的状态。
在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文件进行同步,何时同步由操作系统来决定。 |
基本过程就是:
当上述过程执行完之后,AOF文件所保存的数据状态就会被完整的还原出来。
由于AOF持久化过程是通过保存被执行的写命令来记录数据库状态的,所以随着写命令越来越多,AOF文件中的内容也会越来越多,文件的体积也就会越来越大。如果AOF体积过大的则有可能导致redis服务器受到影响,且解析AOF文件所需要的时间将会非常久。因此需要尽量的减少AOF文件的体积大小,因此redis提供了一种AOF重写功能。
AOF重写功能就是创建一个新的AOF文件来替换原有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但是新AOF文件削减了久AOF文件中的冗余命令,体积将会小很多。文件重写通过BGREWRITEAOF
命令来实现。上面我们说过在执行BGSAVE
和执行BGREWRITEAOF
命令将会出现一些不同这补充一下:
虽然两个命令都是通过子进程来执行,但是服务器并不允许两者同时执行,因为这两个操作都是在执行大量的磁盘写入操作,并发的执行这两个子进程会给redis带来很大的性能影响。
下面详细的介绍一下重写的实现过程。
虽然redis将生成新的AOF文件替换旧的AOF文件称为“AOF文件重写”,但是实际上,AOF文件重写并没有对现有的AOF文件进行了任何读取、分析或者写入操作,而是完全通过当前数据库的状态来实现的。
详细过程如下图所示
在客户端发送了重写请求后,redis会生成一个子进程,通过子进程生成当前数据库状态的快照。使用快照中的内容通过aof_rewrite
函数将所有所有状态信息写入到临时文件中。由于是采用子进程来执行此过程,服务器并没有被阻塞,还能进行别的请求操作。如果又有写的操作,将新的写操作继续追加写到旧AOF文件后面,通过通过缓存将这些命令缓存起来。在快照内容全部都写入到临时文件后,通知主进程,将缓存中的命令刷入到临时文件中。最后一步使用临时文件替换现存AOF旧文件。
RDB对过期键的处理策略:在我们执行了SAVE或者BGSAVE命令创建出RDB文件之后,程序会对数据库中的过期键检查,已过期的键不会被保存在RDB文件中。在载入RDB文件时,程序同样会对RDB文件中的过期键进行检查,过期键会被忽略。
AOF对归期间的处理策略:由于redis采用的是定时删除和惰性删除策略,如果某个键已经过期,但是还没有被删除,AOF不会对这个过期键进行任何操作。一旦这个过期键被删除,则AOF将对这条件追加一条DEL指令表示该键已经被删除。而如果在重写的时候,程序会对子进程生成的RDB文件进行过期键搜索,如果出现了过期键则被忽略不会被写入到AOF文件中。
RDB持久化体积更小,且载入后恢复数据十分的快,但是如果在定时执行BGSAVE
命令之前服务器宕机,则将损失当前没来得及持久化的所有数据。
期键被删除,则AOF将对这条件追加一条DEL指令表示该键已经被删除。而如果在重写的时候,程序会对子进程生成的RDB文件进行过期键搜索,如果出现了过期键则被忽略不会被写入到AOF文件中。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。