Redis设计原理之数据库与持久化(三)
数据库
struct redisServer{
//一个数组,保存着服务器中的所有数据库
redisDb *db;
// 服务器的数据库数量,默认值为16
int dbnum;
//记录保存条件的数组
struct saveparam *saveparams;
//修改计数器
long long dirty;
//上一次执行保存的时间
time_t lastsave;
//AOF缓冲区
sds aof_buf;
}
切换数据库
每个Redis客户端都有自己的目标数据库,每当客户端执行数据库写命令或者数据库读命令的时候,目标数据库就会成为这些命令的操作对象.
如果客户端没有设置的情况下,默认数据库为0号数据库.
redis> select 1 //选择1号数据库
默认情况下,不要随意切换数据库,因为redis并没有返回当前客户端连接的是那个数据库,如果用户切换数据库多次后,会忘记自己当前操作的是哪个数据库.数据库之间是互相不通的.所以在没有特殊需求的情况下不建议切换数据库.
数据库键空间
Redis是一个键值对(key-value)的数据库服务器,服务器中的每个数据库都由一个redisDb结构表示.
typedef struct redisDb{
//数据库键空间,保存着数据库中的所有键值对
dict *dict;
//过期字典,保存键的过期时间
dict *expires;
}
- 键空间的键就是数据库的键,每个键都是一个字符串对象.
- 键空间的值也就是数据库的值,每个值可以是字符串对象,列表,哈希,集合,有序集合对象中的任意一种.
//存入一个字符串key=message
redis> set message "hello world"
//存入一个列表,key=aplhabet
redis> rpush aplhabet "a" "b" "c"
//存入一个字典key=book
redis> hset book name "redis inaction"
redis> hset book pushlist "manning"
dbsize命令:用于返回数据键的数量 exists 键是否存在,rename 键重命名,keys显示所有key(生产上少用)
读写键空间的维护操作
当我们操作一个键时,redis内部不仅仅把值返回给我们,而是有很多操作.
- 服务器会根据键是否存在来更新服务器的键空间命中(hit)次数或者键空间不命中(miss)次数.
info stats
的keyspace_hits,keyspace_misses可以看出来. - 读取一个键之后,服务器会更新lru(最后一次使用时间),这个值可以用来计算键的闲置时间.
- 服务器读取一个键发现该键已经过期了,那么服务器会先删除这个过期键,然后再执行其他操作.
- 服务器每次修改一个键之后,都会对dirty键计数器的值增1.这个计数器会触发服务器的持久化及复制操作.
- 服务器开启了数据库通知功能后,修改一个键后会发送相应的通知.
设置键的生成时间或者过期时间
rdis> set key value
redis> expire key 5 //5秒后过期
redis> pexpire key 1377253002 //豪秒
redis> expireat key <timestamp> 设置过期日期(时间格式为long)
redis> pexpireat key <timestamp> 设置过期日期,到毫秒级别
//上面的过期日期,底层都将转换为pexpireat.
redis> ttl key //查看键还剩多少生存时间
redis> pttl key //查看键过期时间的键
保存过期时间
redisDb中的expires保存了键的过期日时间.
图中键“alphabet”键对象的值为1385877600000,这表示该键的过期时间为(2013年12月1日零时).
redis> pexpireat massage 1391234400000 //设置了过期日期.
移除过期时间
redis> pexpireat message 1391234400000 //设置过期时间
redis> ttl message //查看过期时间
redis> persist msssage //移除过期时间,移除了过期时间后,会从expires表中删除该数据
过期键的判定
- 检查给定键是否存在过期字典,如果存在,那么获取过期时间
- 检查当前unix时间戳是否大于键的过期时间,如果是的话,那么键过期了,否则未过期
删除过期键策略
上面说了过期键,那么过期键什么时候删除呢?
- 定时删除: 设置过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作.
- 惰性删除: 放任键不管,但是每次从键空间中获取键时,都检查键是否过期,如果过期则删除,没有过期则返回该键.
- 定时删除: 每隔一个段时间,程序就对数据库进行一次检查,删除里面的过期键.(一次删除多少,有redis算法决定)
定时删除(占cpu)
优点:定时删除,可以保证过期键会经快的被删除,释放过期键所占用的内存.
缺点:对cpu不友好,在过期键比较多的情况下,会占用大量cpu.影响服务器的响应时间和吞吐量.
惰性删除(占内存)
优点:cpu友好, 程序只会在获取键的时候才对键进行检查,过期了,则删除.
缺点:内存不友好, 当一个键过期了,但是没人访问,这个过期键就会一直存在内存里面.比如记录日志时用redis做缓冲,但是过期的日志后期基本都不会被访问到,就会存在大量的内存浪费了.
定期删除
定期删除策略每隔一段时间执行一次删除过期操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响.
缺点:这个定期的时长和频率的策略上很难确定.如果频率太快就会变的和定时一样,频率太少就会和惰性一样.所以很难确定这个频率和时长.
Redis的过期键删除策略
Redis实际是结合惰性删除和定期删除两种策略组合.
- 开启惰性删除,在访问键时,判断键是否过期,如果过期了则删除.
- 开启定期删除,某些键在过期后,一直没有被访问到,这个时候定期策略就发挥作用,删除那些残留的过期键.
AOF,RDB和复制功能对过期键的处理
RDB对过期键的处理
在启动redis服务器时,如果服务器开启了RDB功能,那么服务器将对RDB文件进行载入如下:
- 如果服务器是主服务器模式,载入RDB文件时,会检查键,如果键过期了,则忽略不载入.
- 如果是从服务器模式,直接载入所有键,不论是否过期.(载入过期键不会有影响,因为主从同步时会把从库清空,然后重新载入主库)
AOF文件写入
当服务器以AOF模式运行时,如果数据库中的某个键已经过期了,但是还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生任何影响.
当过期键被惰性删除或者定期删除之后,程序会向AOF文件追加一天DEL命令,用于显示地记录该键已被删除.
AOF重写
在执行AOF重写的过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写的AOF文件中.
主从复制
当服务器运行在复制模式下,从服务器的过期键删除动作主要由主服务控制:
- 主服务器在删除一个过期键之后,会显示地向所有从服务器发送一个del命令.告知从服务器删除这个键.
- 从服务器在接受到客户端发送的读命令是,即使遇到过期键,也不会删除,而是像返回过期键一样返回给客户端.
- 主服务只有在接受到主服务器发送来的del命令之后才会删除过期键.
RDB持久化
RDB备份有两种模式:手动或者配置时间自动备份.
生成RDB文件有个两个命令:save(阻塞备份,客户端所有请求都会被拒绝),bgsave(后台子进程备份,不阻塞)
Redis服务会在启动时检查是否有RDB文件,如果有则载入RDB文件. 就是说恢复操作不需要我们手动执行,而是当redis服务挂掉重启时,他会自动恢复数据.
- 如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库的状态.
- 只有在AOF持久化功呢功能处于关闭状态时,服务器才会使用RDB文件还原数据库状态.
服务器备份状态
执行save命令时:redis服务器会被阻塞,如果save命令正在执行,那么客户端发送过来的命令会被拒绝.
执行bgsave命令时:
- 在bgsave执行期间,客户端发送过来的save命令会被拒绝,服务器禁止save与bgsave同时执行.
- 在bgsave执行期间,客户端发送的bgsave命令也会被服务器拒绝,因为同时执行两个bgsave也会存在自竞争.
- 如果bgsave正在执行,那么bgrewriteaof命令会被延迟到bgsave命令执行完毕后执行.但是如果bgrewriteaof命令正在执行,那么客户端发送的bgsave命令会被拒绝.
服务器在载入RDB文件期间,会一直处于阻塞状态,知道载入完成为止.
自动间隔性保存
举个例子:
save 900 1 //900秒之内,对数据库进行至少1次修改.
save 300 10 //300秒之内,对数据库进行至少10次修改
save 60 10000 //60s之内,对数据库至少10000次修改
用户可以通过save选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行bgsave命令.
自动保存的实现
redisServer结构里面的saveparam会保存我们设置好的条件.
struct saveparam{
//秒数
time_t seconds;
//修改数
int changes;
}
redisServer结构里面的dirty计数器和lastsave属性:
- dirty计数器记录了上一次成功执行save或者bgsave命令之后,服务器对数据库(所有数据库)进行了多少次修改(包括写入,删除,更行等操作).
- lastsave属性一个unix时间戳,记录了服务器上一次成功执行save命令或者bgsave命令的时间.
自动保存条件是否满足
redis会每100毫秒就执行一次,检查是否满足save的条件.
AOF持久化
redis> set msg "hello"
redis> sadd fuits "apple" "banna"
redis> rpush number 1 3 35
RDB持久化是把msg,fruits,number 三个键的键值对保存到RDB文件中,而AOF持久化是将set,add,rpush三个命令保存到AOF文件中.
被写入AOF文件的所有命令都是以redis的命令请求协议格式保存的,因为Redis的命令请求协议是纯文本格式.
AOF持久化的实现
struct redisServer{
sds aof_buf;
}
当AOF持久化功能处于打开状态是,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态aof_buf缓冲区的末尾;
AOF文件的写入与同步
如果用户没有主动设置appendfsync选项值,那么默认值为everysec.
注意这里说的aof_buf缓冲区写入到AOF文件并同步AOF文件,为啥写入AOF文件了,还需要同步呢.因为你程序里面是写入了AOF文件,但是操作系统可并不会理解写入到AOF文件里面哦,操作系统会先把我们的写入文件操作写入到操作系统的缓冲区,然后在真正写入到AOF文件中.
AOF文件的载入与数据还原
因为AOF里面包含了重建数据库状态所需的所有写命令,所以服务器启动时,只需要重新执行一遍AOF文件里面的写命令就OK了.载入AOF文件时,Redis会启动一个伪客户端来执行这些命令.
AOF重写
redis>rpush list "a" "b"
redis> rpush list "c"
redis> rpush list "d" "e"
redis> lpop list
redis> lpop list
上面产生了3个入库命令,2个出库命令,按正常的AOF模式来,他会将这5条命令全部写入到AOF文件里面.但是现在数据库里面真实存入在数据只有
[“c”,“d”,“e”],“a”,"b"已经被弹出去了.
上面5条命令完全可以合成一条存入到AOF文件里面.redis> rpush list "c" "d" "e"
,重写后的AOF文件里面只有这一条命令,这样就从5条变成1条,减少了很多存储空间.
AOF重写不会很笨的去分析原有的AOF文件,而是直接去遍历现在现有的库,遍历库里面的所有key,然后根据key找到value值,反写出存储命令.
重写优化在优化:
- 重写AOF是在后台进行的,redis会fork一个子进程来做这些事.
- 当重写过程中遇到比较大的list或set之类的,会拆成多条执行.
- 重写创建一个子进程时还会开启一块AOF缓冲区,用来缓存新加入的命令,待重写完毕后,AOF缓冲区再追加到AOF文件中.删除AOF缓冲区
- 重新完毕后会用重写后的AOF文件,替换掉原有的AOF文件.
触发AOF后台重写的条件
AOF重写可以由用户通过调用BGREWRITEAOF手动触发。
服务器在AOF功能开启的情况下,会维持以下三个变量:
no-appendfsync-on-rewrite yes //开启AOF重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb //当aof文件达到64mb后开始重写
- 记录当前AOF文件大小的变量aof_current_size。
- 记录最后一次AOF重写之后,AOF文件大小的变量aof_rewrite_base_size。
- 增长百分比变量aof_rewrite_perc。
每次当serverCron(服务器周期性操作函数)函数执行时,它会检查以下条件是否全部满足,如果全部满足的话,就触发自动的AOF重写操作:
- 没有BGSAVE命令(RDB持久化)/AOF持久化在执行;
- 没有BGREWRITEAOF在进行;
- 当前AOF文件大小要大于auto-aof-rewrite-min-size(默认为1MB),或者在redis.conf配置了auto-aof-rewrite-min-size大小;
- 当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比(在配置文件设置了auto-aof-rewrite-percentage参数,不设置默认为100%)