数据库
文章目录
概述
Redis 服务器实例可以包含多个数据库,默认情况下有 16 个数据库(编号从 0 到 15),用户可以通过配置文件中的 databases
参数修改数据库数量。
实现
Redis 的多个数据库实际上是以数组形式存在的,每个数据库在服务器启动时创建,并且在 Redis 服务器中以 redisDb
结构表示:
typedef struct redisDb {
dict *dict; // 数据库键空间,保存所有键值对
dict *expires; // 过期时间字典,保存所有键的过期时间
dict *blocking_keys; // 阻塞操作相关
dict *ready_keys; // 阻塞操作相关
dict *watched_keys; // 事务相关
int id; // 数据库编号
long long avg_ttl; // 平均TTL(过期时间)
} redisDb;
每个 redisDb
实例表示一个数据库,其中 dict
是一个字典,存储了所有的键值对。
数据结构图
以下是 Redis 数据库的结构图:
其他说明
- 每个数据库的键空间和过期时间字典都是独立的,操作不会相互影响。
- Redis 命令默认作用于当前选中的数据库,切换数据库使用
SELECT
命令。
概述
Redis 默认从 0 号数据库开始工作,可以通过 SELECT
命令切换到其他数据库。数据库切换只会影响当前连接,其他连接不会受影响。
实现
SELECT
命令用于切换数据库,接受一个参数,即数据库编号。命令的处理函数如下:
void selectCommand(client *c) {
long id;
if (getLongFromObjectOrReply(c,c->argv[1],&id,NULL) != C_OK)
return;
if (selectDb(c,id) == C_ERR) {
addReplyError(c,"DB index is out of range");
} else {
addReply(c,shared.ok);
}
}
selectDb
函数用于实际切换数据库:
int selectDb(client *c, int id) {
if (id < 0 || id >= server.dbnum)
return C_ERR;
c->db = &server.db[id];
return C_OK;
}
client
结构体中包含db
指针,指向当前数据库。- 切换数据库时,
db
指针会更新为目标数据库的地址。
数据结构图
以下是切换数据库时的结构图:
其他说明
- 数据库切换不影响其他客户端的数据库选择,每个客户端都有自己的当前数据库。
- 使用
INFO keyspace
命令可以查看各个数据库的键数量和过期信息。
概述
Redis 的键空间(key space)是数据库的核心部分,存储了所有的键值对。每个数据库的键空间由一个字典(dict
)实现,提供高效的增删改查操作。
具体操作
添加新键
在 Redis 中添加新键通常通过 SET
、HSET
等命令完成。如果键不存在,则会创建新键。
int dbAdd(redisDb *db, robj *key, robj *val) {
// 尝试插入新键值对到字典中
if (dictAdd(db->dict, key, val) == DICT_OK) {
return 1; // 成功
} else {
return 0; // 失败
}
}
删除键
删除键可以通过 DEL
命令实现。如果键存在,则从键空间字典中移除该键。
int dbDelete(redisDb *db, robj *key) {
if (dictDelete(db->dict, key) == DICT_OK) {
return 1; // 成功
} else {
return 0; // 失败
}
}
更新键
更新键值通常也是通过 SET
、HSET
等命令完成。如果键已经存在,则更新其值。
void setKey(redisDb *db, robj *key, robj *val) {
if (lookupKeyWrite(db, key) == NULL) {
dbAdd(db, key, val);
} else {
dbOverwrite(db, key, val);
}
}
对键取值
通过 GET
命令等可以获取键的值。如果键存在,则返回其值。
robj *lookupKeyRead(redisDb *db, robj *key) {
return dictFetchValue(db->dict, key);
}
其他键空间操作
包括键重命名、键移动等操作。
读写键空间时的维护操作
如键空间压缩、键空间清理等操作确保键空间高效运行。
数据结构图
以下是 Redis 键空间的结构图:
其他说明
- 键空间操作是 Redis 性能的关键,字典的高效实现保证了操作的快速响应。
- 对键空间的每次写操作都会触发相应的维护操作,如过期键检查等。
概述
Redis 支持为键设置生存时间(TTL)或过期时间。当键的生存时间到期后,键会被自动删除。相关操作包括设置过期时间、保存过期时间、移除过期时间、计算并返回剩余生存时间、以及过期键的判定。
具体操作
设置过期时间
可以通过 EXPIRE
、PEXPIRE
、EXPIREAT
、PEXPIREAT
等命令为键设置过期时间。
int setExpire(redisDb *db, robj *key, long long when) {
if (dictAdd(db->expires, key, when) == DICT_OK) {
return 1; // 成功
} else {
return 0; // 失败
}
}
保存过期时间
过期时间以毫秒为单位存储在 expires
字典中,键为实际的键,值为过期时间戳。
移除过期时间
可以通过 PERSIST
命令移除键的过期时间。
int removeExpire(redisDb *db, robj *key) {
if (dictDelete(db->expires, key) == DICT_OK) {
return 1; // 成功
} else {
return 0; // 失败
}
}
计算并返回剩余生存时间
通过 TTL
或 PTTL
命令可以计算并返回键的剩余生存时间。
long long getExpire(redisDb *db, robj *key) {
long long when = dictFetchValue(db->expires, key);
return when - mstime();
}
过期键的判定
通过检查当前时间与键的过期时间来判定键是否过期。
int keyIsExpired(redisDb *db, robj *key) {
long long when = dictFetchValue(db->expires, key);
return mstime() > when;
}
数据结构图
以下是设置和管理键的过期时间的结构图:
其他说明
- 过期时间以毫秒为单位存储,确保精确的过期管理。
- Redis 使用惰性删除和定期删除策略处理过期键,确保系统性能。
概述
Redis 使用三种主要策略来删除过期键:定时删除、惰性删除和定期删除。这些策略共同确保过期键能够及时删除,同时尽量减少对性能的影响。
策略详解
定时删除
定时删除(timed deletion)指的是在设置键的过期时间时,创建一个定时任务,在过期时间到达时立即删除该键。这种策略能够保证过期键能够及时删除,但会占用系统资源,尤其是当有大量键设置了过期时间时,可能导致系统性能下降。
Redis 并未使用这种策略,因为其开销较大,不适合高性能的要求。
惰性删除
惰性删除(lazy deletion)指的是在访问键时,检查键是否过期,如果过期则删除。这样做能够减少系统开销,但不能保证过期键能够及时删除。
实现惰性删除的关键代码:
if (keyIsExpired(db, key)) {
dbDelete(db, key);
}
定期删除
定期删除(periodic deletion)指的是以固定时间间隔随机检查一部分键,并删除其中已经过期的键。这种策略能够在减少系统开销的同时,尽量保证过期键能够及时删除。
实现定期删除的关键代码:
void activeExpireCycle(int type) {
// 定期检查键空间中的键是否过期,并进行删除
// 略去具体实现代码
}
数据结构图
以下是三种过期键删除策略的结构图:
其他说明
- 定时删除不适合高性能场景,因此 Redis 未采用该策略。
- 惰性删除能够减少系统开销,但不能保证及时删除。
- 定期删除在保证性能