读书笔记-Redis设计与实现-数据库,持久化和通信
1.数据库
- 服务器默认创建16个数据库,该数量由服务器配置的database选项决定。
- 通过select 0/1/2…命令进行切换数据库,redis没有返回当前目标数据库的命令,因此在进行整库操作时,最好先通过select命令切换到目标数据库
- 通过expire或者pexpire来对某个键设置过期时间,ttl查询剩余过期时间
- EXPIRE<key><ttl>命令用于将键key的生存时间设置为ttl秒。
- PEXPIRE<key><ttl>命令用于将键key的生存时间设置为ttl毫秒。
- EXPIREAT<key><timestamp>命令用于将键key的过期时间设置为timestamp所指定的秒数时间戳。
- PEXPIREAT<key><timestamp>命令用于将键key的过期时间设置为timestamp所指定的毫秒数时间戳。
- PERSIST命令可以移除一个键的过期时间
- 过期键删除策略
- 定时删除:在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作。
- 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
- 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
- 数据库通知
- 数据库通知是Redis 2.8版本新增加的功能,这个功能可以让客户端通过订阅给定的频道或者模式,来获知数据库中键的变化,以及数据库中命令的执行情况。
- 关注“某个键执行了什么命令”的通知称为键空间通知(key-space notification)
- 键事件通知(key-event notification)的通知,它们关注的是“某个命令被什么键执行了”
- 当Redis命令对数据库进行修改之后,服务器会根据配置向客户端发送数据库通知。
- 执行SAVE命令或者BGSAVE命令所产生的新RDB文件不会包含已经过期的键。
- 执行BGREWRITEAOF命令所产生的重写AOF文件不会包含已经过期的键。
- 当一个过期键被删除之后,服务器会追加一条DEL命令到现有AOF文件的末尾,显式地删除过期键。
- 当主服务器删除一个过期键之后,它会向所有从服务器发送一条DEL命令,显式地删除过期键。
- 从服务器即使发现过期键也不会自作主张地删除它,而是等待主节点发来DEL命令,这种统一、中心化的过期键删除策略可以保证主从服务器数据的一致性。
2.RDB持久化
- RDB持久化功能所生成的RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态
- 有两个Redis命令可以用于生成RDB文件,一个是SAVE,另一个是BGSAVE
- SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求
- BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求
- AOF文件的更新频率通常比RDB文件的更新频率高
- 如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。
- 只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态
- 服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止
- 服务器每隔一段时间自动执行一次BGSAVE命令,只要满足以下三个条件中的任意一个,BGSAVE命令就会被执行:
- 服务器在900秒之内,对数据库进行了至少1次修改。
- 服务器在300秒之内,对数据库进行了至少10次修改。
- 服务器在60秒之内,对数据库进行了至少10000次修改
- dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)
- lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间
- Redis的服务器周期性操作函数serverCron默认每隔100毫秒就会执行一次
- RDB文件结构
- RDB文件的最开头是REDIS部分,这个部分的长度为5字节,保存着“REDIS”五个字符。通过这五个字符,程序可以在载入文件时,快速检查所载入的文件是否RDB文件
- db_version长度为4字节,它的值是一个字符串表示的整数,这个整数记录了RDB文件的版本号,比如”0006”就代表RDB文件的版本为第六版
- databases部分包含着零个或任意多个数据库,以及各个数据库中的键值对数据
- 如果服务器的数据库状态为空(所有数据库都是空的),那么这个部分也为空,长度为0字节。
- 如果服务器的数据库状态为非空(有至少一个数据库非空),那么这个部分也为非空,根据数据库所保存键值对的数量、类型和内容不同,这个部分的长度也会有所不同
- EOF常量的长度为1字节,这个常量标志着RDB文件正文内容的结束,当读入程序遇到这个值的时候,它知道所有数据库的所有键值对都已经载入完毕了
- check_sum是一个8字节长的无符号整数,保存着一个校验和,这个校验和是程序通过对REDIS、db_version、databases、EOF四个部分的内容进行计算得出的。服务器在载入RDB文件时,会将载入数据所计算出的校验和与check_sum所记录的校验和进行对比,以此来检查RDB文件是否有出错或者损坏的情况出现
- 使用od -cx dump.rdb 命令对RDB文件进行分析。
3.AOF(Append Only File)持久化
- 与RDB持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的
- AOF文件中的所有命令都以Redis命令请求协议的格式保存。
- 命令请求会先保存到AOF缓冲区里面,之后再定期写入并同步到AOF文件。
- 实现步骤
- 命令追加:服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾
- AOF文件的写入与同步:服务器每次结束一个事件循环之前,它都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面
- 三种持久化策略:
- 当appendfsync的值为always时,服务器在每个事件循环都要将aof_buf缓冲区中的所有内容写入到AOF文件,并且同步AOF文件
- 当appendfsync的值为everysec时,服务器在每个事件循环都要将aof_buf缓冲区中的所有内容写入到AOF文件,并且每隔一秒就要在子线程中对AOF文件进行一次同步
- 当appendfsync的值为no时,服务器在每个事件循环都要将aof_buf缓冲区中的所有内容写入到AOF文件,至于何时对AOF文件进行同步,则由操作系统控制
- AOF重写,用以解决AOF文件快速膨胀的问题,通过将多个命令合并为一条来实现
4.事件
- 文件事件
- 服务器与客户端(或者其他服务器)的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作。
- 文件事件处理器以单线程方式运行,但通过使用I/O多路复用程序来监听多个套接字
- 如果一个套接字又可读又可写的话,那么服务器将先读套接字,后写套接字
- 文件事件处理器是基于Reactor模式实现的网络通信程序
- 文件事件分为AE_READABLE事件(读事件)和AE_WRITABLE事件(写事件)两类。
- 时间事件
- Redis服务器中的一些操作(比如serverCron函数)需要在给定的时间点执行,而时间事件就是服务器对这类定时操作的抽象。
- 定时事件:让一段程序在指定的时间之后执行一次。比如说,让程序X在当前时间的30毫秒之后执行一次。
- 周期性事件:让一段程序每隔指定时间就执行一次。比如说,让程序Y每隔30毫秒就执行一次。
- 服务器将所有时间事件都放在一个无序链表中,每当时间事件执行器运行时,它就遍历整个链表,查找所有已到达的时间事件,并调用相应的事件处理器
- 文件事件和时间事件之间是合作关系,服务器会轮流处理这两种事件,并且处理事件的过程中也不会进行抢占
- 时间事件的实际处理时间通常会比设定的到达时间晚一些。(原因是redis单线程处理,时间事件未到达时会先处理文件事件)
5.客户端
- 服务器状态结构使用clients链表连接起多个客户端状态,新添加的客户端状态会被放到链表的末尾。
- 客户端状态的flags属性使用不同标志来表示客户端的角色,以及客户端当前所处的状态。
- 输入缓冲区记录了客户端发送的命令请求,这个缓冲区的大小不能超过1GB。
- 命令的参数和参数个数会被记录在客户端状态的argv和argc属性里面,而cmd属性则记录了客户端要执行命令的实现函数。
- 客户端有固定大小缓冲区和可变大小缓冲区两种缓冲区可用,其中固定大小缓冲区的最大大小为16KB,而可变大小缓冲区的最大大小不能超过服务器设置的硬性限制值。
- 输出缓冲区限制值有两种,如果输出缓冲区的大小超过了服务器设置的硬性限制,那么客户端会被立即关闭;除此之外,如果客户端在一定时间内,一直超过服务器设置的软性限制,那么客户端也会被关闭。
- 当一个客户端通过网络连接连上服务器时,服务器会为这个客户端创建相应的客户端状态。网络连接关闭、发送了不合协议格式的命令请求、成为CLIENT KILL命令的目标、空转时间超时、输出缓冲区的大小超出限制,以上这些原因都会造成客户端被关闭。
- 处理Lua脚本的伪客户端在服务器初始化时创建,这个客户端会一直存在,直到服务器关闭。
- 载入AOF文件时使用的伪客户端在载入工作开始时动态创建,载入工作完毕之后关闭。
6.服务器
- 一个命令请求从发送到完成主要包括以下步骤:
- 客户端将命令请求发送给服务器;
- 服务器读取命令请求,并分析出命令参数;
- 命令执行器根据参数查找命令的实现函数,然后执行实现函数并得出命令回复;
- 服务器将命令回复返回给客户端。
- serverCron函数默认每隔100毫秒执行一次,它的工作主要包括更新服务器状态信息,处理服务器接收的SIGTERM信号,管理客户端资源和数据库状态,检查并执行持久化操作等等。
- 服务器从启动到能够处理客户端的命令请求需要执行以下步骤:
- 初始化服务器状态;
- 载入服务器配置;
- 初始化服务器数据结构;
- 还原数据库状态;
- 执行事件循环。