Redis底层原理--05. Redis 数据库
1数据库
1.1 数据结构
Redis 中的每个数据库,都由一个 redis.h/redisDb 结构表示:
1 | typedef struct redisDb { |
具体事例:
1.2 设置生存时间
Redis 有四个命令可以设置键的生存时间(可以存活多久)和过期时间(什么时候到期):
- EXPIRE 以秒为单位设置键的生存时间;
- PEXPIRE 以毫秒为单位设置键的生存时间;
- EXPIREAT 以秒为单位,设置键的过期 UNIX 时间戳;
- PEXPIREAT 以毫秒为单位,设置键的过期 UNIX 时间戳。
1.3 过期键的清除
Redis 使用的过期键删除策略是惰性删除加上定期删除.
定期删除是这两种策略的一种折中:
- 它每隔一段时间执行一次删除操作,并通过限制删除操作执行的时长和频率,籍此来减少删除操作对 CPU 时间的影响。(相当于定时执行一次删除,但是这个删除是有限制时间和频率的)
- 另一方面,通过定期删除过期键,它有效地减少了因惰性删除而带来的内存浪费。
1.4 过期删除流程
实现过期键惰性删除策略的核心是 db.c/expireIfNeeded 函数——所有命令在读取或写入数据库之前,程序都会调用 expireIfNeeded 对输入键进行检查,并将过期键删除:
Get 获得数据:
1.4 过期键对 AOF 、 RDB 和复制的影响
在创建新的 RDB 文件时,程序会对键进行检查,过期的键不会被写入到更新后的 RDB 文件
中。因此,过期键对更新后的 RDB 文件没有影响。
在键已经过期,但是还没有被惰性删除或者定期删除之前,这个键不会产生任何影响, AOF 文
件也不会因为这个键而被修改。当过期键被惰性删除、或者定期删除之后,程序会向 AOF 文件追加一条 DEL 命令,来显式地
记录该键已被删除。
举个例子,如果客户端使用 GET message 试图访问 message 键的值,但 message 已经过期了,
那么服务器执行以下三个动作:
- 从数据库中删除 message ;
- 追加一条 DEL message 命令到 AOF 文件;
- 向客户端返回 NIL 。
2. RDB
SAVE 和 BGSAVE 命令实现 RDB 的功能实现。
2.1 RDB 文件结构
3. AOF
3.1 缓存追加
整个缓存追加过程可以分为以下三步:
- 接受命令、命令的参数、以及参数的个数、所使用的数据库等信息。
- 将命令还原成 Redis 网络通讯协议。
- 将协议文本追加到 aof_buf 末尾。
3.2 AOF 写入和保存
WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。
SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
3.3 AOF 同步过程
同步命令到 AOF 文件的整个过程可以分为三个阶段:
- 命令传播: Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程序中。
- 缓存追加: AOF 程序根据接收到的命令数据,将命令转换为网络通讯协议的格式,然后将协议内容追加到服务器的 AOF 缓存中。
- 文件写入和保存: AOF 缓存中的内容被写入到 AOF 文件末尾,如果设定的 AOF 保存条件被满足的话, fsync 函数或者 fdatasync 函数会被调用,将写入的内容真正地保存到磁盘中。
3.4 AOF 保存模式
Redis 目前支持三种 AOF 保存模式,它们分别是:
- AOF_FSYNC_NO :不保存。
- AOF_FSYNC_EVERYSEC :每一秒钟保存一次。
- AOF_FSYNC_ALWAYS :每执行一个命令保存一次。
每一秒钟保存一次
每当 flushAppendOnlyFile 函数被调用时,可能会出现以下四种情况:
子线程正在执行 SAVE ,并且:
- 这个 SAVE 的执行时间未超过 2 秒,那么程序直接返回,并不执行 WRITE 或新的SAVE 。
- 这个 SAVE 已经执行超过 2 秒,那么程序执行 WRITE ,但不执行新的 SAVE 。注意,因为这时 WRITE 的写入必须等待子线程先完(旧的) SAVE ,因此这里 WRITE 会比平时阻塞更长时间。
子线程没有在执行 SAVE ,并且:
- 上次成功执行 SAVE 距今不超过 1 秒,那么程序执行 WRITE ,但不执行 SAVE 。
- 上次成功执行 SAVE 距今已经超过 1 秒,那么程序执行 WRITE 和 SAVE 。可以用流程图表示这四种情况:
根据以上说明可以知道,在“每一秒钟保存一次”模式下,如果在情况 1 中发生故障停机,那么用户最多损失小于 2 秒内所产生的所有数据。
Redis 官网上所说的, AOF 在“每一秒钟保存一次”时发生故障,只丢失 1 秒钟数据的说法,实际上并不准确
3.6 AOF 保存模式对性能和安全性的影响
- 不保存(AOF_FSYNC_NO):写入和保存都由主进程执行,两个操作都会阻塞主进程。
- 每一秒钟保存一次(AOF_FSYNC_EVERYSEC):写入操作由主进程执行,阻塞主进程。保存操作由子线程执行,不直接阻塞主进程,但保存操作完成的快慢会影响写入操作的阻塞时长。
- 每执行一个命令保存一次(AOF_FSYNC_ALWAYS):和模式 1 一样。
3.7 AOF 重写
是为了防止 AOF 越来越大,对多条命令合并成一个命令,例如:
1 | RPUSH list 1 2 3 4 // [1, 2, 3, 4] |
而是直接读取 list 键在数据库的当前值,然后用一条 RPUSH 1 2 3
命令来代替前面的四条命令
4. 事件
4.1 文件事件
读和写 事件
4.2 时间事件
定期需要执行的任务
例如:
更新服务器的各类统计信息,比如时间、内存占用、数据库占用情况等。
清理数据库中的过期键值对。
对不合理的数据库进行大小调整。
关闭和清理连接失效的客户端。
尝试进行 AOF 或 RDB 持久化操作。
如果服务器是主节点的话,对附属节点进行定期同步。
- 如果处于集群模式的话,对集群进行定期同步和连接测试
4. 服务端和客户端
启动过程
- 初始化服务器全局状态。
- 载入配置文件。
- 创建 daemon 进程。
- 初始化服务器功能模块。
- 载入数据。
- 开始事件循环。
执行完成后,各个组件的关系:
客户端连接到服务器
当一个客户端通过套接字函数 connect 到服务器时,服务器执行以下步骤:
- 服务器通过文件事件无阻塞地 accept 客户端连接,并返回一个套接字描述符 fd
- 服务器为 fd 创建一个对应的 redis.h/redisClient 结构实例,并将该实例加入到服务器的已连接客户端的链表中。
- 服务器在事件处理器为该 fd 关联读文件事件。