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>

消息内容:键名。

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

相关推荐
夏季疯6 分钟前
学习笔记:黑马程序员JavaWeb开发教程(2025.3.31)
java·笔记·学习
chunfeng—9 分钟前
Redis相关命令详解与原理(一)
数据库·redis·缓存
老友@9 分钟前
MySQL + Elasticsearch:为什么要使用ES,使用场景与架构设计详解
数据库·mysql·elasticsearch·搜索引擎·性能优化·系统架构
檀越剑指大厂24 分钟前
【SQL系列】多表关联更新
数据库·sql
李二。29 分钟前
wordpress自学笔记 第二节: 3种独立站商城横幅的制作
服务器·笔记·wordpress
烦躁的大鼻嘎39 分钟前
【Linux】深入拆解Ext文件系统:从磁盘物理结构到Linux文件管理
linux·运维·服务器·ubuntu·centos
·云扬·1 小时前
【PmHub后端篇】PmHub整合TransmittableThreadLocal (TTL)缓存用户数据
java·开发语言·缓存
chenzhuyu1 小时前
海康摄像机在Edge浏览器的网页无法直接预览,按照要求安装WebComponents后仍然提示请安装插件或预览失败的解决办法
运维
极小狐1 小时前
如何减少极狐GitLab 容器镜像库存储?
运维·git·rpc·kubernetes·ssh·gitlab·terraform
cooldream20091 小时前
深入理解负载均衡:传输层与应用层的原理与实战
运维·负载均衡·系统架构师