这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战
一、数据库键空间操作及特性
Redis是一个键值对(key-value pair)数据库服务器,服务器中的每个数据库都由一个redis.h/redisDb结构表示,其中,redisDb结构的dict字典保存了数据库中的所有键值对,我们将这个字典称为键空间(key space):
typedef struct redisDb {
//....
// 数据库键空间,保存着数据库中的所有键值对
dict *dict;
// ....
} redisDb;
键空间和用户所见的数据库是直接对应的:
1、键空间的键也就是数据库的键,每个键都是一个字符串对象。
2、键空间的值也就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的任意一种Redis对象。
1、添加新键
- 添加一个新键值对到数据库,实际上就是将一个新键值对添加到键空间字典里面,其中键为字符串对象,而值则为任意一种类型的Redis对象。 2、删除键
- 删除数据库中的一个键,实际上就是在键空间里面删除键所对应的键值对对象。 3、更新键
- 对一个数据库进行更新,实际上就是对键空间里面键所对应的值对象进行更新,根据值对象的类型不同,更新的具体方法也会有所不同。 3、对键取值
- 对一个数据库进行取值,实际上就是在键空间中取出键所对应的值对象,根据值对象的类型不同,具体的取值方法也会有所不同。 除了上述操作之外,还有很多针对数据库本身的Redis命令,也是通过对键空间进行处理来完成的。比如清空整个数据库的FLUSHDB命令,就是通过删除键空间中所有键值对数量来实现的;用于随机返回数据库中某个键的RANDOMKEY命令,就是通过在键空间中随机返回一个键来实现的。类似的命令还有EXISTS、RENAME、KEYS等。
1.1 读写键空间时的维护操作
当使用Redis命令对数据库进行读写时,服务器不仅会对键空间执行指定的读写操作,还会执行一些额外的维护操作,其中包含以下:
1、在读取一个键之后(读操作和写操作都要对键进行读取),服务器会根据键是否存在来更新服务器的键空间命中(hit)次数或键空间不命中(miss)次数,这两个值可以在INFO stats命令的keyspace_hits属性和keyspace_misses属性中查看。
2、在读取一个键之后,服务器会更新键的LRU(最后一次使用)时间,这个值可以用于计算键的闲置时间,使用OBJECT idletime <key> 命令可以查看键key的闲置时间。
3、如果服务器在读取一个键时发现该键已经过期,那么服务器会先删除这个过期键,然后才执行余下的其他操作。
二、设置键的生存时间或过期时间
通过EXPIRE命令或者PEXPIRE命令,客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间(Time To Live,TTL),在经过指定的秒数或者毫秒数之后,服务器就会自动删除生存时间为0的键。
客户端也可以通过EXPIREAT命令或PEXPIREAT命令,以秒或者毫秒精度给数据库中的某个键设置过期时间(expire time),过期时间是一个UNIX时间戳,当键的过期时间来临时,服务器就自动删除。
TTL命令和PTTL命令接受一个带有生存时间或者过期时间的键,返回这个键的剩余生存时间。
2.1 设置过期时间
Redis有四个不同的命令可以用于设置键的生存时间(键可以存多久)或过期时间:
- EXPIRE <key> <ttl> 命令用于将键key的生存时间设置ttl秒。
- PEXPIRE <key> <ttl>命令用于将键key的生存时间设置ttl毫秒。
- EXPIREAT <key> <timestamp> 命令用于将键key的过期时间设置为timesamp所指定的秒数时间戳。
- PEXPIREAT <key> <timestamp> 命令用于将键key的过期时间设置为timesamp所指定的毫秒数时间戳。 以上所有命令最后都会被转换PEXPIREAT命令来实现。(有点多态的意思)
2.2 保存过期时间
redisDb结构的expires字典保存了数据库中所有键的过期时间,我们称这个字典为过期字典:
- 过期字典的键是一个指针,指针指向键空间中的某个键对象。
- 过期字典的值是一个long long类型的整数,这个整数保存了键所指向的数据库键的过期时间————一个毫秒精度的UNIX时间戳。
typedef struct redisDb {
// ...
// 过期字典,保存着键的过期时间
dict *expires;
// ...
} redisDb;
以下是PEXPIREAT命令的伪代码定义:
def PERPIREAT(key, expire_time_in_ms):
# 如果给定的键不存在于键空间,那么不能设置过期时间
if key not in redisDb.dict
return 0
# 在过期字典中关联键和过期时间
redisDb.expires[key] = expire_time_in_ms
# 过期时间设置成功
return 1
2.3 移除过期时间
PERSIST命令可以移除一个键的过期时间。
以下是PERSIST命令的伪代码定义
def PERSIST(key):
# 如果给定的键不存在,或者键没有设置过期时间,那么直接返回
if key not in redisDb.expires:
return 0
# 移除过期字典中给定键的键值对关联
redisDb.expires.remove(key)
# 过期时间移除成功
return 1
2.4 计算并返回剩余生存时间
TTL命令以秒为单位返回键的剩余生存时间,而PTTL命令则以毫秒为单位返回键的剩余生存时间。而这两者都是通过计算键的过期时间和当前时间之间的差来实现的,以下是两个命令的伪代码实现:
def PTTL(key):
# 键不存在于数据库
if key not in redisDb.dict:
return -2
# 尝试取得键的过期时间
# 如果键没有设置过期时间,那么expire_time_in_ms 将为None
expire_time_in_ms = redisDb.expires.get(key)
# 键没有设置过期时间
if expire_time_in_ms is None:
return -1
# 获得当前时间
now_ms = get_current_unix_timestamp_in_ms()
# 过期时间减去当前时间,得出的差就是键的剩余生存时间
return (expire_time_in_ms - now_ms)
def TTL(key):
# 获取以毫秒为单位的剩余生存时间
ttl_in_ms = PTTL(key)
if ttl_in_ms < 0
# 处理返回值为-2 和 -1的情况
return ttl_in_ms
else:
# 将毫秒转换为秒
return ms_to_sec(ttl_in_ms)
2.5 过期键的判定
通过过期字典,程序可以用以下步骤检查一个给定键是否过期:
- 检查给定键是否存在于过期字典,如果存在,那么取得键的过期时间。
- 检查当前UNIX时间戳是否大于键的过期时间:如果是的话,那么键已经过期;否则的话,键未过期。
三、过期键删除策略
一个键过期了,它什么时候会被删除呢?
这个问题有三种可能的答案,它们分别代表了三种不同的删除策略:
- 定时删除:在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作。
- 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
- 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于删除多少过期键,以及要检查多少个数据库,则由算法决定。