文章目录
服务器中的数据库
struct redisServer
{
int dbnum;//服务器的数据库数量
redisDb *db; //一个数组,保存着服务器中的所有数据库 默认16
}
切换数据库
每个Redis客户端都有个目标数据库,每当客户端执行数据库读写命令时,目标数据库就成为这些命令的操作对象.默认目标数据库为0号数据库
typedef struct redisClient
{
redisDb * db;//记录客户端当前使用的数据库
}redisClient;
数据库键空间
typedef struct redisDb
{
dict *dict;//数据库键空间,保存着数据库中的所有键值对
} redisDb;
键空间的键是一个字符串对象,值可以是字符串对象,列表对象,哈希表对象,集合对象,有序集合对象
所有针对数据库的操作,都是通过对键空间字典进行操作实现的
添加新键
就是将新键值对添加到键空间字典里面
读写键空间时的维护操作
- 在读取一个键后(读写操作都要对键进行读取),服务器会根据键是否存在来更新服务器的键空间命中次数
- 在读取一个键后,会更新LRU时间,可以计算键的闲置时间
- 在读取一个键若发现该键已过期,则会先删除过期键
- 在对被监视的键进行修改后,会标记为脏,让事务程序注意该键已经被修改
设置键的生存时间或过期时间
设置过期时间
- EXPIRE 设置过期时间,单位为秒
- PEXPIRE 设置过期时间,单位为毫秒
- EXPIREAT 设置过期时间为秒数时间戳
- PEXPIREAT 设置过期时间为毫秒数时间戳
最后都是转换为PEXPIREAT来执行的
保存过期时间
typedef struct redisDb
{
dict *dict;//数据库键空间,保存着数据库中的所有键值对
dict *expires;//保存了数据库中所有键的过期时间,过期字典
//指向的键就是键空间某个键
//指向的值为long long类型的过期时间,一个毫秒精度的UNIX时间戳
} redisDb;
键空间的键和过期字典的键都指向同一个键对象,不会出现任何重复对象,也不会浪费任何空间
def PEXPIREAT(key,expire_time_in_ms)
{
#如果给定的键不存在键空间,那么不能设置过期时间
if key not in redisDb.dict:
return 0
#在过期字典中关联键和过期时间
redisDb.expires[key]=expire_time_in_ms
# 过期时间设置成功
return 1
}
移除过期时间
def PERSIST(key):
#如果给定的键不存在或没设置过期时间,那么直接返回
if key not in redisDb.expires:
return 0
#移除过期字典中给定键的键值对关联
redisDb.expires.remove(key)
#移除成功
return 1
计算并返回剩余生存时间
def PTTL(key):
#如果给定的键不存在数据库
if key not in redisDb.dict:
return -2
#尝试获取过期时间
#没有则为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)
过期键的判断
def is_expired(key):
#尝试获取过期时间
#没有则为None
expire_time_in_ms=redisDb.expires.get(key)
if expire_time_in_ms is None:
return False
# 获取当前时间时间戳
now_ms=get_current_unix_timestamp_in_ms()
if now_ms>expire_time_in_ms:
return True # 已过期
else:
return False
过期键删除策略
定时删除
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作
可以保证过期键会尽可能快地被删除,并释放过期键所占用的内存
对CPU时间最不友好
不太现实
惰性删除
每次取出键都会检查是否过期,过期则删除
只有在取出键时才会对键进行过期检查,对CPU时间最友好
对内存最不友好,会导致内存泄漏:无用的垃圾数据占用了大量内存
如日志,当不再使用时,会大量积压在数据库,用户以为自动删除了,其实还存在
定期删除
每隔一段时间进行检查,删除里面的过期键
是前两种的整合与折中
必须合理地设置删除操作的执行时间和执行效率
Redis过期键删除策略
配合使用惰性删除和定期删除两种
惰性删除策略的实现
采用expireIfNeeded函数,如果过期则将键删除。就像过滤器,在命令真正执行前,过滤掉过期的输入键,避免命令接触到过期键
所以每个命令的实现函数必须能同时处理键存在和不存在两种情况
定期删除策略实现
# coding=utf-8
# 默认每次检查的数据库数量
DEFAULT_DB_NUMBERS = 16
# 默认每个数据库检查的键数量
DEFAULT_KEY_NUMBERS = 20
# 全局变量,记录检查进度
current_db = 0
def activeExpireCycle():
# 初始化要检查的数据库数量
# 以服务器的数据库数量为主
if serer.dbnum < DEFAULT_DB_NUMBERS:
db_numbers = serer.dbnum
else:
db_numbers = DEFAULT_DB_NUMBERS
#遍历每个数据库
for i in range(db_numbers):
#如果current_db=服务器的数据库数量
#则表示已经遍历了服务器的所有数据库一次
#将current_db=重置为0,开始新一轮
if current_db==server.dbnum:
current_db=0
#获取当前要处理的数据库
redisDb=server.db[current_db]
#指向下一个要处理的数据库
current_db+=1
#检查数据库的键
for j in range(DEFAULT_KEY_NUMBERS):
#如果数据库中没有一个键带有过期时间,则跳过该数据库
if redisDb.expires.size()==0:
break
#随机获取一个带过期时间的键
key_with_ttl=redisDb.expires.get_random_key()
if is_expired(key_with_ttl):
delete_key(key_with_ttl)
# 已到达时间上限,停止处理
if reach_time_limit():
return
b.expires.get_random_key()
if is_expired(key_with_ttl):
delete_key(key_with_ttl)
# 已到达时间上限,停止处理
if reach_time_limit():
return