Redis 是一个使用 C 语言编写的 NoSql 的数据库,本篇就讲解在 Redis 中数据库是如何存储的?以及和数据库有关的一些操作。
Redis 中的所有数据库都保存在 redis.h/redisServer 结构中的 db 数组中,如下:
struct redisServer { ...... // 数据库 redisDb *db; ...... }Redis 默认会创建 16 个数据库,每个数据库互不影响。
切换数据库每个 Redis 客户端也都有自己的目标数据库,默认情况下,Redis客户端的目标数据库是 0 号数据库。但客户端也可以通过 select 命令来切换目标数据库。
在服务器内部,客户端状态 redisClient 结构的 db 属性记录了客户端当前的目标数据库,如下:
typedef struct redisClient { // 套接字描述符 int fd; // 当前正在使用的数据库 redisDb *db; // 当前正在使用的数据库的 id (号码) int dictid; // 客户端的名字 robj *name; /* As set by CLIENT SETNAME */ } redisClient;假如某个客户端的目标数据库为 1 号数据库,那么这个客户端所对应的客户端状态和服务器状态之间的关系如下(出自《Redis设计与实现第二版》第九章:数据库):
注意: 到目前为止,Redis 仍然没有返回客户端目标数据库的命令,所以尽量不要在项目中使用多数据库,以免造成混乱。
数据库键空间Redis 是一个键值对数据库服务器,服务器中的每个数据库都由一个 redis.h/redisDb 结构表示,具体结构如下:
typedef struct redisDb { // 数据库键空间,保存着数据库中的所有键值对 dict *dict; /* The keyspace for this DB */ // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳 dict *expires; /* Timeout of keys with a timeout set */ // 正处于阻塞状态的键 dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */ // 可以解除阻塞的键 dict *ready_keys; /* Blocked keys that received a PUSH */ // 正在被 WATCH 命令监视的键 dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */ // 数据库号码 int id; /* Database ID */ // 数据库的键的平均 TTL ,统计信息 long long avg_ttl; /* Average TTL, just for stats */ } redisDb;键空间(db 属性)和用户所见的数据库是直接对应的:
键空间的键也就是数据库的键,每个键都是一个字符串对象。
键空间的值也就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的任意一种 Redis 对象。
而在数据库中添加、修改、删除键也都是操作的 db 字典。
键的过期策略Redis 中设置键的过期时间有四种写法:
expire key t1 :表示将键 key 的生存时间设置为 t1 秒。
pexpire key t1 :表示将键 key 的生存时间设置为 t1 毫秒。
expireat key t1 :表示将键 key 的过期时间设置为 t1 所指定的秒数时间戳。
pexpireat key t1 :表示将键 key 的过期时间设置为 t1 所指定的毫秒数时间戳。
虽然有 4 种不同的写法,但这些做的都是一件事,所以可以抽成一个统一的方法。而实际上 Redis 也正是这么做的,expire、pexpire、expireat 三个命令都是使用 pexpireat 命令来实现的。
Redis 如何存储键的过期时间呢?redisDb 结构的 expires 字典保存了数据库中所有键的过期时间,我们称这个字典为过期字典:
过期字典的键是一个指针,这个指针指向键空间中的某个键对象(也即是某个数据库键)。
过期字典的值是一个 long long 类型的整数,这个整数保存了键所指向的数据库键的过期时间(一个毫秒精度的 UNIX 时间戳)。
Redis 如何移除键的过期时间呢?命令是: persist key
Redis 数据库做的操作也仅仅是在 expires 字典中删除对应的键值对。
Redis 如何判断一个键是否过期呢?通过 expires 字典,程序可以用以下步骤检查一个给定键是否过期:
检查给定键是否存在于过期字典:如果存在,那么取得键的过期时间。
检查当前 UNIX 时间戮是否大于键的过期时间:如果是的话,那么键已经过期;否则的话,键未过期。
Redis 具体是如何删除一个过期的键呢?我们知道数据库键的过期时间都保存在过期字典中,又知道了如何根据过期时间去判断一个键是否过期,现在剩下的问题是:如果一个键过期了,那么它什么时候会被删除呢?
这个问题有三种可能的答案,它们分别代表了三种不同的删除策略:
定时删除:在设置键的过期时间的同时,创建一个定时器(timer) 。让定时器在键的过期时间来临时,立即执行对键的侧除操作。
惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就侧除该键;如果没有过期,就返回该键。