Redis设计与实现-服务器中的数据库

服务器中的数据库

  • [1、 redis数据库](#1、 redis数据库)
  • [2、 数据库键空间](#2、 数据库键空间)
  • [3、 过期时间和删除策略](#3、 过期时间和删除策略)
  • [4、 AOF、RDB和复制功能对过期键的处理](#4、 AOF、RDB和复制功能对过期键的处理)
  • [5、 通知](#5、 通知)

如有侵权,请联系~

如有错误,也欢迎批评指正~

本篇文章大部分是来自学习《Redis设计与实现》的笔记

1、 redis数据库

Redis默认有16个数据库,可以根据配置设置数据库的数量。数据库存储在数据库数组中,如下redisServer定义中的*db字段。

redisServer 是 Redis 服务器的核心数据结构,它包含了 Redis 运行时的所有状态和配置信息。redisServer 是一个全局变量(通常命名为 server),在 Redis 源码中定义为 struct redisServer 类型。它是 Redis 内部管理数据库、客户端连接、命令执行等核心功能的基础。

c 复制代码
struct redisServer {
    // 配置相关
    char *configfile;          // 配置文件路径
    int port;                  // 监听端口
    char **bindaddr;           // 绑定地址
    int databases;             // 数据库数量

    // 数据库相关
    redisDb *db;               // 数据库数组
    int dbnum;                 // 当前数据库索引

    // 客户端连接相关
    list *clients;             // 客户端列表
    list *clients_pending_write; // 等待写入的客户端

    // 命令相关
    dict *commands;            // 命令字典

    // 持久化相关
    char *rdb_filename;        // RDB 文件路径
    int aof_state;             // AOF 状态
    sds aof_buf;               // AOF 缓冲区

    // 事件循环
    aeEventLoop *el;           // 事件循环
    int hz;                    // 定时任务频率

    // 复制与集群
    char *masterhost;          // 主服务器地址
    int repl_state;            // 复制状态
    int cluster_enabled;       // 是否启用集群

    // 统计与监控
    long long stat_numcommands; // 命令总数
    long long stat_numconnections; // 连接总数
};

可以通过select命令进行切换数据库。例如select 2表示切换到2号数据库中。

Redis服务端利用client用来管理每个连接到服务器的客户端的状态和行为的核心数据结构。当一个客户端(如 Redis CLI、应用程序或其他 Redis 客户端)通过网络连接到 Redis 服务端时,Redis 服务端会为该连接创建一个 redisClient 实例。这个实例用于跟踪客户端的状态(如连接信息、执行的命令、数据库选择等),并负责处理客户端发送的请求以及向客户端返回响应。

c 复制代码
typedef struct client {
    // 客户端基本信息
    uint64_t id;               // 客户端 ID
    int fd;                    // 文件描述符
    sds name;                  // 客户端名称
    int flags;                 // 客户端状态标志

    // 输入缓冲区
    sds querybuf;              // 输入缓冲区
    int argc;                  // 参数个数
    robj **argv;               // 参数数组
    struct redisCommand *cmd;  // 当前命令

    // 输出缓冲区
    char buf[PROTO_REPLY_CHUNK_BYTES]; // 固定大小的输出缓冲区
    list *reply;               // 动态分配的输出缓冲区

    // 数据库相关
    redisDb *db;               // 当前数据库
    int dictid;                // 数据库索引

    // 复制相关
    int replstate;             // 复制状态
    long long reploff;         // 复制偏移量

    // 阻塞与超时
    mstime_t bpop_timeout;     // 阻塞超时时间
    long long lastinteraction; // 上次交互时间

    // 统计信息
    long long query_start_time; // 命令开始执行时间
    size_t obuf_mem;           // 输出缓冲区内存占用

    // 其他
    int authenticated;         // 是否已认证
    connection *conn;          // 连接对象
    dict *pubsub_channels;     // 订阅的频道
    list *pubsub_patterns;     // 订阅的模式
} client;

client中的db字段就表示当前使用的数据库。select命令其实底层实现就是修改client的db这个字段,这个字段就会指向redisServer db数组的具体某个数据库。

2、 数据库键空间

redis默认有16个数据库,每个数据库都是键值对数据库,每个数据库都是有redisServer中提到的redisDB数据结构存储。

c 复制代码
typedef struct redisDb {
    // 键空间,保存所有的键值对
    dict *dict;               // 主字典,存储键值对
    dict *expires;            // 过期字典,存储键的过期时间。过期key的删除策略:定期删除和惰性删除

    // 阻塞操作
    dict *blocking_keys;      // 阻塞键的集合
    dict *ready_keys;         // 已经准备好的阻塞键

    // 订阅发布
    dict *pubsub_channels;    // 频道订阅关系
    list *pubsub_patterns;    // 模式订阅关系

    // 持久化相关
    int id;                   // 数据库 ID
    long long avg_ttl;        // 平均 TTL

    // 事务相关
    dict *watched_keys;       // 被监视的键

    // Redis Cluster 相关
    unsigned char slots[CLUSTER_SLOTS / 8]; // 哈希槽位图
    dict *slots_to_keys;      // 哈希槽到键的映射
} redisDb;

这里的键空间就是和我们所见的数据库直接对应。存储的键就是dict中的键,存储的值【字符串、列表、哈希...】就是dict中的值。

3、 过期时间和删除策略

和键过期相关的命令:

命令 描述 示例
EXPIRE 设置键的过期时间(秒) EXPIRE key seconds
PEXPIRE 设置键的过期时间(毫秒) PEXPIRE key milliseconds
EXPIREAT 设置键在指定时间戳(秒)后过期 EXPIREAT key timestamp
PEXPIREAT 设置键在指定时间戳(毫秒)后过期 PEXPIREAT key milliseconds-timestamp
TTL 查看键剩余的过期时间(秒) TTL key
PTTL 查看键剩余的过期时间(毫秒) PTTL key
PERSIST 移除键的过期时间,使其永不过期 PERSIST key
SET ... EX 设置键值对并指定过期时间(秒) SET key value EX seconds
SET ... PX 设置键值对并指定过期时间(毫秒) SET key value PX milliseconds

无论是EXPIRE、PEXPIRE还是EXPIREAT底层都是使用PEXPIREAT命令进行实现的。

底层实现:

通过上面redisDB数据结构就可以看到有个字典属性expires过期字典,所有的键值对都存储到键空间dict上,而所有键的过期时间都存储到过期字典expires中。expires中的key就是指向键空间中的键对象key,而value就是一个long long类型的整数。

如何判断一个键是不是过期了呢?

通过过期字典中某个键的过期时间【键的value值】,如果当前时间大于过期时间证明这个键已经过期。

键过期了,怎么删除呢?

删除策略主要有三种:

  • 定时删除:每当给某个键设置过期时间的时候,都会为其创建一个定时器。当到过期时间之后,就会立刻删除。所以这种方式对于内存是友好的,一旦过期就会删除不会占用多余的内存;缺点:浪费CPU资源,尤其是存在大量的过期键,定时器,CPU的浪费更明显。
  • 惰性删除:只有用到这个键的时候才会校验这个键值对是不是已经过期了,如果过期则进行删除。这种方式不会浪费占用多余CPU资源,但是会占用大量内存。例如某些键已经过期,但是长期又不访问就导致一直占用内存,可以看成内存泄漏。Redis底层通过expireIfNeed方法进行实现。
  • 定期删除:这种策略是定期的去清理一定数量的过期键。综合衡量CPU和内存资源,但是这种方式的难点是执行的频率和时间。Redis底层通过activeExpireCycle方法进行实现。

Redis使用惰性删除和定期删除相结合的方式进行过期键的删除。

4、 AOF、RDB和复制功能对过期键的处理

类型 描述
生成RDB文件 无论是执行save命令、bgsave命令,还是配置项中的save周期,在进行生成RDB文件的时候,过期键都不会保存,会对数据库中的键进行检查
加载RDB文件 针对于主服务器:在加载RDB文件的时候,会主动过滤掉过期的键值对;从服务器则会将所有的键都加载到数据库内存中,不过等主从同步的时候,从服务器就会删除自身所有数据,加载主服务器的RDB数据
AOF文件写入 如果数据库中的键过期,不会对AOF文件有任何影响。除非触发定期删除或者惰性删除,在定期删除或者惰性删除的时候,会往AOF文件中写一条del命令
AOF重写 AOF重写和生成RDB文件一样,不会对过期键进行保存
复制 主服务器删除一个过期键之后,会显式地向所有的从服务器发送一个DEL命令;从服务器并不会主动的删除过期键,只有收到主服务器的DEL命令才会删除,这么做为了主从一致性。如果一个键过期了,从服务器没有收到主服务器的DEL命令,那么从服务器仍然存在这个键值对,这个时候当从服务收到客户端读get命令,从服务器会将这个键对应的值返回出去。【如果Redis集群配置读写分离,需要注意这点

5、 通知

这里的通知与发布订阅【客户端发布到频道一个值,服务端就会向订阅这个频道的客户端发送这个值,即订阅者就会收到这个值(客户端和服务端长连接)】不同。

通知分为键空间通知【某个键执行了哪些命令】和键事件通知【某个命令被哪些键执行了】。用于控制通知的参数:notify-keyspace-events

notify-keyspace-events的所有字符取值:

字符 含义
K 启用键空间通知(Keyspace notifications)。
E 启用键事件通知(Keyevent notifications)。
g 通用命令(如 DELEXPIRERENAME 等)。
s 字符串命令(如 SETGETINCR 等)。
l 列表命令(如 LPUSHLPOP 等)。
h 哈希命令(如 HSETHGET 等)。
z 有序集合命令(如 ZADDZREM 等)。
x 过期事件(当键因过期而被删除时触发)。
e 驱逐事件(当键因内存不足而被驱逐时触发)。
A 启用所有事件(相当于 g$lshzxe 的组合)。

常见组合:

组合 含义
KEA 启用所有键空间和键事件通知。
Ex 仅启用键事件通知中的过期事件。
Kg 仅启用键空间通知中的通用命令事件。
Ksx 启用键空间通知中的字符串命令和过期事件。

通知功能是通过 notifyKeySpaceEvent函数实现的,每个命令执行成功都会执行这个方法。这个方法的底层其实是使用的发布订阅功能。
键空间通知:

频道格式:*keyspace@<db>* :<key>

消息内容:操作类型(如 set、del、expire 等)。
键事件通知:

频道格式:*keyevent@<db>* :<event>

消息内容:键名。

键空间和键事件通知流程图:

相关推荐
TC139819 分钟前
服务器Ubuntu22.04 server 安装gnome GUI+VNC远程配置
linux·服务器·远程工作
今年不养猪只除草1 小时前
windows版本的时序数据库TDengine安装以及可视化工具
数据库·时序数据库·tdengine
沉默的八哥1 小时前
K8S中的etcd数据库备份与恢复
运维·kubernetes
沉默的八哥1 小时前
K8S中PV和PVC之间的关系
运维·kubernetes
Amd7941 小时前
FastAPI 核心机制:分页参数的实现与最佳实践
性能优化·fastapi·安全实践·web开发·分页·错误处理·数据库查询
安 当 加 密3 小时前
基于USB Key的Web系统双因素认证解决方案:构建安全与便捷的登录体系
运维·网络·安全
沉默的八哥3 小时前
K8S日常问题优化
运维·kubernetes
极限实验室3 小时前
Easysearch 磁盘水位线注意事项
数据库
666HZ6663 小时前
从0到1入门Docker
运维·docker·容器
月落星还在3 小时前
Redis 单线程架构:化繁为简的性能哲学
数据库·redis·架构