Redis 单机实现
1. RDB 持久化
2. AOF持久化
RDB是通过直接保存数据,即数据库中的key-value对来进行数据的存储和恢复。AOF则是通过保存命令来对数据进行保存,每当客户端发出命令的时候,Redis会将命令转化为纯文本格式写入AOF文件当中。
2.1 AOF实现的三个步骤
- 命令追加
既然AOF是通过文字的方式来记录redis命令的,那么我们也可以通过字符串对文字进行保存
struct redisServer{
sds ato_buf;//用来记录redis命令
}
- 文件写入
我们需要将buf中的string写入文件中
def eventLoop():
while True:
#先处理文件事件、请求和命令回复
processFileEvent()
#处理时间事件
processTimeEvent()
#将数据保存、同步到AOF文件中
flushFAppendOnlyFile()
appendfsync选项 | 动作 |
---|---|
always | 写入同时同步 |
everysec | 写入之后美秒同步 |
no | 操作系统决定 |
2.2 载入与还原
2.3 AOF重写
随着AOF记录的指令越来越多,AOF文件的体积也迅速膨胀,会对整个系统带来不好的影响,这时候我们要重写AOF,减少数据量。面对多条指令,我们只需要读取最后的结果,便可以仅仅通过创建指令加以压缩。
def aof_rewrite():
for db in redisServer:
# 判空
if db.is_empty():
continue
for key in db:
# 判超时
if key.is_expired:
continue
switch(key.type):
case string : rewrite_sting();
.......
rewrite函数只需要执行最简单的PUSH、ADD、SET等命令即可。
2.4 AOF后台重写
通过同时向缓冲区和重写缓冲区写入数据可以保证数据同步,可以直接将重写期间的数据直接写入AOF重写缓冲区的末尾。
3. 事件
Redis主要需要处理两类事件
- 时间时间,比如key的有效时间,这种根据时间发生的时间
- 文件时间,包括使用IO多路复用进行的套接字读区、应答、写入等相关问题
3.1 文件事件
3.1.1 流程
主要监听应答、写入、读取和关闭四大事件。
每个套接字都会产生写入、读取等请求,io复用接受之后将其加入队列,事件分派器一次只读入一个队列请求,处理完毕之后再读入第二个请求。监听类型分为
- 读监听 AE_READABLE
- 写监听 AE_WRITABLE
- 未监听 AE_NONE
- 读写同时 AE_WRITABLE |AE_READABLE
3.1.2 事件处理器
- 应答处理器:对客户端进行应答
- 命令请求处理器:接受请求的任务
- 命令回复处理器:返回命令执行结果
- 复制处理器:处理主从复制操作
总流程
- 应答处理器:设置为AE_READABLE
- 命令请求处理器:设置为AE_READABLE
- 服务器处理
- 命令回复处理器:设置为AE_WRITEABLE
3.2 时间事件
事件分为两类
- 定时事件:在指定时间执行一次
- 周期性事件:每隔一段时间执行一次
时间结构体记录
- id 唯一标志
- when :毫秒级时间戳
- timeProc:处理函数
如果返回NO_MORE则为定时时间,不再发生,否则为周期时间,以上存储无序列表中
def processTimeEvent():
for time_event in time_events:
#判断是否到达
if time_event.when <= unix_time_now():
retval = time_event.process()
if retval == NO_MORE:
del_time_event(time_event)#单次时间删除
else:
updata(time_event,unix.now)#周期事件更新
4. 客户端
单台Redis服务器可以与多台客户端相连,通过IO复用技术实现一对多。对于每一个与Server相连的Client,服务器都为其建立了redisClient结构,其中包含
- 套接字、输入输出缓冲区
- 名字、flag
- client正在使用的数据库指针、数据库id
- 复制状态,复制所需数据接口
- 查看所需数据结构
- 客户端身份验证、创建时间、最后通讯时间
- 命令、命令参数
4.1 客户端属性
struct client{
int fd;//套接字
robj *name;//名字
int flag;//标红位
sds qurey_buf;//输入缓冲
// 命令与命令参数
robj **argv;
int argc;
// 输出缓冲区
int bufpos;
char buf[REDIS_REPLY_CHUNK_BYTES];
int authenticated;
}
- 套接字
- 用于执行AOF和LUA指令的客户端fd=-1
- 真正的客户端fd>0
- 名字
用户为客户端命名,默认没有名字 - 标志位
- 用来表示状态
- REDIS_MONITOR 执行MONITOR命令
- REDIS_BLOCK 阻塞命令
- REDIS_LUA_CLIENT lua客户端
- 。。。。。。
- 输入缓冲
新输入的文字以字符串的格式保存,之后再解析 - 命令与命令参数
将输入、拆分成命令和参数,同时记录数量
比如 set key value 会拆开为set、key、value,使用stringobj进行记录
- 命令实现函数
- 输出缓冲区
- 验证身份
如果为零,除Auth指令外全部被拒绝 - 时间
- 开始时间ctime
- 最后互动时间 lastinteraction
- 软限时间 obuf_soft_limit_reach_time
4.2 连接与断开
连接十分简答,在clients链表结尾加入新client即可
- 断开
- 客户端进程被杀死
- 客户端发送不符合协议格式的命令
- 成为CLIENT KILL目标
- 发送大小超过缓冲区
- 收到的数据超过换冲区
- 两种类限制
- 硬限制,超过缓冲区大小,直接关闭
- 软限制,未超过硬限制,但是超过软限制,开始计时,若一段时间之后恢复正常则不断开,如果超过限制时间,则断开
5. 单机服务器模式
本节重点介绍服务器与客户端的互动,并不涉及多机、集群操作。
5.1 命令的请求与执行
整个客户端请求过程可以高度概括为如下过程:
- 用户发送数据
- 服务器处理数据
- 回复ok
5.1.1 发送命令
客户端将文字转化为协议模式,之后将这段协议发给服务器(socket)
5.1.2 读取命令
- 读取协议,并放入输入缓冲区中query_buf
- 解析,分析参数,argv、argc
5.1.3 命令实现
- 查找命令表。同客户端,server有一套命令表,命令表中存储文字,并指向执行命令的rediscommad,redisCommand指向具体执行的函数,比如:
redisCommand |
---|
name:set |
proc 指向执行函数 |
arity -3 :接受三个参数 |
sflag:wm // w写入命令 m占用内存命令 |
- 执行预备操作,进行如下检查
- cmd是否指向null ,命令是否存在;
- 根据arity,参数数量是否正确;
- 验证身份,AUTH
- 检查内存占用,是否设置maxmemory
- 是否因为BGSAVE执行失败不可写
- 是否进行数据载入
- 是否进行LUA脚本阻塞
- 是否正在执行事务(EXEC、DISCARD、MULTI、WATCH)
- 执行函数
通过客户端的指针(指向具体的执行函数),并根据几个参数,进行赋值,执行。 - 后续工作
- 加日志
- 如果正在AOF持久化,写入缓存区
- 如果正在复制,添加命令
5.1.4 返回命令
写入输出缓冲区,并打印
5.2 时间serverCron
Redis服务器中的serverCron函数默认每100毫秒执行一次,这个函数负责管理服务器资源并保证服务器自身的良好运转。
- 记录时间缓存:
time_t unix_time;//秒级
long long mstime;//毫秒级 - 更新LRU时钟,记录最后一次命令执行时间,可以记录数据库空转时间
unsigned lruclock; - 记录每一秒执行指令次数
long long ops_sec_last_sample_time - 记录内存峰值
size_t stat_peak_memory; - 处理SIGTERM信号该信号用来关闭数据库
int shutdown_assap//为1关闭,为0不动作 - 管理客户资源,判断链接是否超时,输出区满
- 管理数据库,删除过期键
- 延迟BGREWRITEAOF命令,如果执行BGSAVE期间收到BGREWRITEAOF则延迟
int aof_rewrite_scheduled - 记录持久化的PID
pid_t rdb_child_pid;//rdb saving pid
pid_t aof_child_pid; // rewriting pid
若两个pid都为-1 则做持久化检查
- AOF缓冲区写入
- 关闭异步客户端
- 增加cronloops计数器
5.3 服务器初始化
服务器初始化主要由redis.c/initServerConfig负责。首先要进行一下配置
- 设置运行ID
- 设置默认频率
- 设置默认配置文件
- 设置运行架构(64 or 32)
- 设置默认端口号,
- 设置RDB和AOF持久化条件
- 设置LRU时钟,初始化命令列表
- 载入配置选项。根据redis.conf设置基本的redisNum,port等信息
- 初始化数据结构,包括server.client链表,server.db数组,保存订阅信息的server.pubsubpatterns,server.lua环境,server.slowlog查询日志
- 还原数据库
- 循环执行业务