单机数据库的实现(上)

数据库

文章目录

  1. 服务器中的数据库

概述

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 命令。
  1. 切换数据库

概述

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 命令可以查看各个数据库的键数量和过期信息。
  1. 数据库键空间

概述

Redis 的键空间(key space)是数据库的核心部分,存储了所有的键值对。每个数据库的键空间由一个字典(dict)实现,提供高效的增删改查操作。

具体操作
添加新键

在 Redis 中添加新键通常通过 SETHSET 等命令完成。如果键不存在,则会创建新键。

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; // 失败
    }
}
更新键

更新键值通常也是通过 SETHSET 等命令完成。如果键已经存在,则更新其值。

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 性能的关键,字典的高效实现保证了操作的快速响应。
  • 对键空间的每次写操作都会触发相应的维护操作,如过期键检查等。
  1. 设置见得生存时间或过期时间

概述

Redis 支持为键设置生存时间(TTL)或过期时间。当键的生存时间到期后,键会被自动删除。相关操作包括设置过期时间、保存过期时间、移除过期时间、计算并返回剩余生存时间、以及过期键的判定。

具体操作
设置过期时间

可以通过 EXPIREPEXPIREEXPIREATPEXPIREAT 等命令为键设置过期时间。

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; // 失败
    }
}
计算并返回剩余生存时间

通过 TTLPTTL 命令可以计算并返回键的剩余生存时间。

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 使用惰性删除和定期删除策略处理过期键,确保系统性能。
  1. 过期键删除策略

概述

Redis 使用三种主要策略来删除过期键:定时删除、惰性删除和定期删除。这些策略共同确保过期键能够及时删除,同时尽量减少对性能的影响。

策略详解
定时删除

定时删除(timed deletion)指的是在设置键的过期时间时,创建一个定时任务,在过期时间到达时立即删除该键。这种策略能够保证过期键能够及时删除,但会占用系统资源,尤其是当有大量键设置了过期时间时,可能导致系统性能下降。

Redis 并未使用这种策略,因为其开销较大,不适合高性能的要求。

惰性删除

惰性删除(lazy deletion)指的是在访问键时,检查键是否过期,如果过期则删除。这样做能够减少系统开销,但不能保证过期键能够及时删除。

实现惰性删除的关键代码:

if (keyIsExpired(db, key)) {
    dbDelete(db, key);
}
定期删除

定期删除(periodic deletion)指的是以固定时间间隔随机检查一部分键,并删除其中已经过期的键。这种策略能够在减少系统开销的同时,尽量保证过期键能够及时删除。

实现定期删除的关键代码:

void activeExpireCycle(int type) {
    // 定期检查键空间中的键是否过期,并进行删除
    // 略去具体实现代码
}
数据结构图

以下是三种过期键删除策略的结构图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其他说明

  • 定时删除不适合高性能场景,因此 Redis 未采用该策略。
  • 惰性删除能够减少系统开销,但不能保证及时删除。
  • 定期删除在保证性能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值