redis之数据库
文章目录
- 服务器中的数据库
- 切换数据库
- 数据库键空间
- 读写键空间时的维护操作
- 设置键的生存时间或过期时间
- 保存过期时间
- 过期键的判定
- 过期键删除策略
- 清性删除策略的实现
- 定期删除策略的实现
- 总结
服务器中的数据库
Redis服务器将所有数据库都保存在服务器状态redis.h/redisServer结构的db数组中,db数组的每个项都是一个redis.h/redisDb结构,每个redisDb结构代表一个数据库:
``
struct RedisServer{
// 一个数组,保存着服务器中的所有数据库
redisDb *db;
//服务器的数据库数量
int dbnum;
}
``
donum 属性的值由服务器配置的database选项决定,默认情况下,该选项的值为16,所以Redis服务器默认会创建16个数据库
RedisServer这个对象是非常重要的,因为在本篇的所有内容都是根据这个对象内的各个属性来展开讲解的。这和上篇redis对象有异曲同工之处。
切换数据库
每个Redis客户端都有自已的目标数据库,每当客户端执行数据库写命令或者数据库读命令的时候,日标数据库就会成为这些命令的操作对象。
默认情况下,Redis客户端的目标数据库为0号数据库,但客户端可以通过执行SELECT命令来切换日标数据库。
在服务器内部,客户端状态redisclient结构的db属性记录了客户端当前的目标数锯库,这个属性是一个指向redisDb结构的指针:
``
typedef struct redisClient {
//记录客户端当前正在使用的数据库
redisDb *db ;
}
``
通过修改redisclient.db指针,让它指向服务器中的不同数据库,从而实现切换目标数据库的功能这就是SELECT命令的实现原理。
数据库键空间
Redis是一个键值对(key-valuepair)数据库服务器,服务器中的每个数据库都由一个redis.h/redisDb结构表示,其中,redisDb结构的dict字典保存了数据库中的所有键值对,我们将这个字典称为键空间(keyspace)
``
typedef stxuct redisDb{
// 数据库键空间,保存着数据库中的所有键值对
dict *dict
}redisDb ;
``
建空间和用户所见的数据库是直接对应的:
- 键空间的键也就是数据库的键,每个键都是一个字符串对象。
- 键空间的值也就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的任意一种Redis对象。
让我们通过图来直观的了解键空间如何存储数据:
因为数据库的键空间是一个字典,所以所有针对数据库的操作,比如添加一个键值对到数据库,或者从数据库中删除一个键值对,又或者在数据库中获取某个键值对等,实际上都是通过对键空间字典进行操作来实现的。数据库的添加、删除、更新、取值等操作的实现原理其实就是来对这个键空间进行相应的操作,而上篇提到的命令多态你可以理解为是redis代码内对命令进行的校验,校验通过后就来到了键空间进行对应的操作。
读写键空间时的维护操作
当使用Redis命令对数据库进行读写时,服务器不仅会对键空间执行指定的读写操作,还会执行一些额外的维护操作,其中包括:
- 在读取一个键之后(读操作和写操作都要对键进行读取)服务器会根据键是否存在来更新服务器的键空间命中(hit)次数或键空间不命中(miss)次数,这两个值可以在INFOstats命令的keyspace_hits属性和keyspace_misses属性中香看。
- 在读取一个键之后,服务器会更新键的LRU(最后一次使用)时间,这个值可以用于计算键的闲置时间,使用OBJECTiletime命令可以查看键key的闲置时间。
- 如果服务器在读取一个键时发现该键已经过期,那么服务器会先删除这个过期键,然后才执行余下的其他操作,本章稍后对过期键的讨论会详细说明这一点。
- 如果有客户端使用WATCH命令监视了某个键,那么服务器在对被监视的键进行修改之后,会将这个键标记为脏(dirty),从而让事务程序注意到这个键已经被修改过。
- 服务器每次修改一个键之后,都会对脏(dirty)键计数器的值增1,这个计数器会触发服务器的持久化以及复制操作。
- 如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器将按配置发送相应的数据库通知。
设置键的生存时间或过期时间
通过EXPIRE命令或者PEXPPIRE命令,客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间(Time To Live,TTL)在经过指定的秒数或者毫秒数之后,服务器就会自动删除生存时间为0的键。
保存过期时间
redisDb结构的expires字典保存了数据库中所有键的过期时间,我们称这个字典为过期字典:
过期字典的键是一个指针,这个指针指向键空间中的某个键对象(也即是某个数据库键)。
过期字典的值是一个longlong类型的整数,这个整数保存了键所指向的数据库健的过期时间个毫秒精度的UNIX时间截。
``
typedef struct redisDb {
//过期字典,保存着键的过期时间
dict *expires ;
}
``
我们还是通过一张图来直观的了解我们所描述的内容:
过期时间的操作(添加、删除、修改),其实就是对expires字典进行操作。
过期键的判定
通过过期字典,程序可以用以下步骤检查一个给定键是否过期:
- 1)检查给定键是否存在于过期字典:如果存在,那么取得键的过期时间。
- 2)检查当前UNIX时间截是否大于键的过期时间:如果是的话,那么键已经过期;否则的话,键未过期。
过期键删除策略
redis服务器实际使用的是情性删除和定期删除两种策略:通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。
清性删除策略的实现
过期键的情性删除策略由db.c/expireIfNeeded函数实现,所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded函数对输人键进行检查:
- 如果输人键已经过期,那么expireIENeeded函数将输入键从数据库中删除。
- 如果输人键未过期,那么expireifNeeded函数不做动作。
如图所示展示了使用expireIfNeeded函数的过程
定期删除策略的实现
过期键的定期删除策略由redis.c/activeExpirecycle函数实现,每当Redis的服务器周期性操作redis.c/serverCron函数执行时,activeExpirecycle区函数就会被调用,它在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。
activeExpirecycle函数执行过程:
- 函数每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。
- 全局变量current_db会记录当前activeExpirecycle函数检查的进度,开在下一次activeExpirecycle函数调用时,接着上一次的进度进行处理。比如说,如果当前activeExpireCycle函数在遍历10号数据库时返回了,那么下次activeExpireCycle函数执行时,将从11号数据库开始查找并删除过期键。
- 随着activeExpireCycle函数的不断执行,服务器中的所有数据库都会被检查一遍,这时函数将currentdb变量重置为0,然后再次开始新一轮的检查工作。
总结
- Redis服务器的所有数据库都保存在redisServer.db数组中,而数据库的数量则由redisServer.dbnum属性保存。
- 客户端通过修改目标数据库指针,让它指向redisServer.db数组中的不同元素来切换不同的数据库。
- 数据库主要由dict和expires两个字典构成,其中dict字典负责保存键值对,而expires学典则负责保存键的过期时间。
- 因为数据库由字典构成,所以对数据库的操作都是建立在字典操作之上的。
- 数据库的键总是一个字符串对象,而值则可以是任意一种Redis对象类型,包括字符串对象、哈希表对象、集合对象、列表对象和有序集合对象,分别对应字符串键、哈希表键、集合键、列表键和有序集合键。
- expires字典的键指向数据库中的某个键,而值则记录了数据库键的过期时间,过期时间是一个以毫秒为单位的UNIX时间截。
- Redis使用情性删除和定期删除两种策略来删除过期的键:情性删除策略只在碰到过期键时才进行删除操作,定期删除策略则每隔一段时间主动查找并删除过期键。