Redis设计与实现笔记_9.数据库

服务器中的数据库

struct redisServer
{
	int dbnum;//服务器的数据库数量
	redisDb *db; //一个数组,保存着服务器中的所有数据库 默认16
}

切换数据库

每个Redis客户端都有个目标数据库,每当客户端执行数据库读写命令时,目标数据库就成为这些命令的操作对象.默认目标数据库为0号数据库

typedef struct redisClient
{
	redisDb * db;//记录客户端当前使用的数据库
}redisClient;

数据库键空间

typedef struct redisDb
{
	dict *dict;//数据库键空间,保存着数据库中的所有键值对
} redisDb;

键空间的键是一个字符串对象,值可以是字符串对象,列表对象,哈希表对象,集合对象,有序集合对象

所有针对数据库的操作,都是通过对键空间字典进行操作实现的

添加新键

就是将新键值对添加到键空间字典里面

读写键空间时的维护操作

  • 在读取一个键后(读写操作都要对键进行读取),服务器会根据键是否存在来更新服务器的键空间命中次数
  • 在读取一个键后,会更新LRU时间,可以计算键的闲置时间
  • 在读取一个键若发现该键已过期,则会先删除过期键
  • 在对被监视的键进行修改后,会标记为脏,让事务程序注意该键已经被修改

设置键的生存时间或过期时间

设置过期时间

  1. EXPIRE 设置过期时间,单位为秒
  2. PEXPIRE 设置过期时间,单位为毫秒
  3. EXPIREAT 设置过期时间为秒数时间戳
  4. 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 

猜你喜欢

转载自blog.csdn.net/weixin_42249196/article/details/108547097