共计 2441 个字符,预计需要花费 7 分钟才能阅读完成。
Redis 数据库(Redis 如何表示一个数据库,数据库操作是如何实现的)
当 Redis 服务器初始化的时候会创建 redis.h/REDIS_DEFAULT_DBNUM(后面简写 N)个数据库,且数据库的 id 是从 0 到 N-1 , 所有的数据库保存到 redis.h/redisServer.db 数组中。
在客户端可以通过“SELECT”命令进行切换,其中程序是直接用 redis.h/redisServer.db[number] 进行切换。但是, 一些内部程序, 比如 AOF 程序、复制程序和 RDB 程序, 需要知道当前数据库的号码, 如果没有 id 域的话, 程序就只能在当前使用的数据库的指针, 和 redisServer.db 数组中所 有数据库的指针进行对比, 以此来弄清楚自己正在使用的是那个数据库。
typeof struct redisDb{
int id ; // 数据库的 id
dict *dict ; // 保存着该数据库的所有键值对 也被称为键空间
dict *expires ; // 保存着键的过期时间
…..
} redisDb ;
Redis 是一个键值对 字典表,同样 Redis 数据库存储形式也是键值对 字典表
键是字符串
值可以是字符型、list 列表、hash、集合以及 有序集合其中之一
新增:
Redis 会在键空间字典中增加一个键 - 值对,其中键为一个字符串,值为任意一个值类型。
删除:
Redis 会在键空间字典中删去对应键的键 - 值对
更新:
Redis 会在键空间字典中释放之前对应键的值对象,并让键指向新的值对象
查询:
Redis 会在键空间字典中查询对应键的值对象:
键不存在,返回 NULL
键存在,且类型正确,返回正确的值
键存在,但类型不正确,返回类型错误
其他操作:
除了上面展示的键值操作之外, 还有很多针对数据库本身的命令, 也是通过对键空间进行处理 来完成的:
FLUSHDB 删除键空间中的所有键值对
RANDOMKEY 从键空间中随机返回一个键
DBSIZE 返回键空间中键值对的数量
EXISTS 检查给定键是否存在于键空间中
RENAME 在键空间中, 对给定键进行改名
在 Redis 数据库中,所有键的过期时间都保存在 RedisDb 结构体的 expires 字典中,其中键为一个指向 dict 字典(键空间)里某个键的指针,值为改建的过期时间,用 long long 型表示。
Redis 有四个命令可以设置键的生存时间 (可以存活多久) 和过期时间(什么时候到期):
EXPIRE 以秒为单位设置键的生存时间;
PEXPIRE 以毫秒为单位设置键的生存时间;
EXPIREAT 以秒为单位, 设置键的过期 UNIX 时间戳;
PEXPIREAT 以毫秒为单位, 设置键的过期 UNIX 时间戳。
虽然有那么多种不同单位和不同形式的设置方式, 但是 expires 字典的值只保存“以毫秒为单 位的过期 UNIX 时间戳”, 这就是说, 通过进行转换, 所有命令的效果最后都和 PEXPIREAT 命令的效果一样。
定时清除:
在创建 KEY 的时候创建一个定时任务,在 KEY 到期时定时任务会被触发,第一时间清除过期 KEY。
此种操作对内存最友好,不会有垃圾数据占用内存情况存在
缺点是会造成很大的服务器负载,特别是 CPU 负载高的时候,CPU 很大一部分负载用在了删除不必要的 KEY 上了
惰性清除:
放任键空间的键不管,每次查询 KEY 的时候先去校验 KEY 是否过期,过期则删除,不过期则正常返回相应的 VALUE。
此种操作对 CPU 最友好,这种策略仅限于当前 KEY,相关不必要的 KEY 不会造成 CPU 负载
缺点是:容易造成内存空间浪费,特别是当系统中存在大量过期 KEY 且很少被用到,这十分影响非常依赖于内存大小 Redis 的性能
定期删除:
由定时脚本 cron 定时对 expires 的键扫描判断是否有过期的 KEY 存在,如存在,将其删除掉。
这是一种折中方案,既不会过多消耗 CPU,又可以定时清楚惰性删除忽略到的不必要的内存消耗
Redis 采用的“惰性清除”和“定期清楚”相结合的方式,其中定期删除模式是在规定的时间限制内, 尽 可能地遍历各个数据库的 expires 字典, 随机地检查一部分键的过期时间, 并删除其中的过期键。
伪代码如下:
def activeExpireCycle():
# 遍历数据库(不一定能全部都遍历完, 看时间是否足够)
for db in server.db:
# MAX_KEY_PER_DB 是一个 DB 最大能处理的 key 个数 # 它保证时间不会全部用在个别的 DB 上(避免饥饿) i=0 while (i < MAX_KEY_PER_DB):
# 数据库为空, 跳出 while , 处理下个 DB if db.is_empty(): break
# 随机取出一个带 TTL 的键
key_with_ttl = db.expires.get_random_key()
# 检查键是否过期, 如果是的话, 将它删除 if is_expired(key_with_ttl):
db.deleteExpiredKey(key_with_ttl)
# 当执行时间到达上限, 函数就返回, 不再继续 # 这确保删除操作不会占用太多的 CPU 时间
if reach_time_limit(): return
i += 1
Redis 过期键删除的主从同步问题(Redis 的机制是由主节点统一控制)
如果服务器是主节点,当它删除一个过期键之后,会显式的向所有附属节点发送一条 DEL 命令
如果服务器是附属节点,当它判断到当前 KEY 已经过期,会将该键过期的消息发送给主服务器,主服务器删除后向所有的从服务器节点发送 DEL 命令。
从服务器节点不自主的对键进行删除是为了保持和主服务器数据的绝对一致性,即当一个过期键还存在主服务器上,这个键在所有的从服务器上也不会被删除。