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 载入与还原

Created with Raphaël 2.2.0 启动载入程序 创建不联网伪客户端 分析读取单条指令 执行指令 执行完毕? 结束 yes no

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重写缓冲区的末尾。

命令
客户端
服务端
AOF缓冲区
AOF重写缓冲区

3. 事件

Redis主要需要处理两类事件

  • 时间时间,比如key的有效时间,这种根据时间发生的时间
  • 文件时间,包括使用IO多路复用进行的套接字读区、应答、写入等相关问题

3.1 文件事件

3.1.1 流程

主要监听应答、写入、读取和关闭四大事件。

套接字
套接字
套接字
IO多路复用
事件分派器
事件处理
事件处理
事件处理

每个套接字都会产生写入、读取等请求,io复用接受之后将其加入队列,事件分派器一次只读入一个队列请求,处理完毕之后再读入第二个请求。监听类型分为

  • 读监听 AE_READABLE
  • 写监听 AE_WRITABLE
  • 未监听 AE_NONE
  • 读写同时 AE_WRITABLE |AE_READABLE

3.1.2 事件处理器

  • 应答处理器:对客户端进行应答
  • 命令请求处理器:接受请求的任务
  • 命令回复处理器:返回命令执行结果
  • 复制处理器:处理主从复制操作
发送命令或链接请求
命令回复
客户端
服务器坚挺套接字并设置为AE_READABLE
客户端
服务器坚挺套接字并设置为AE_READABLE

总流程

  • 应答处理器:设置为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
  • 复制状态,复制所需数据接口
  • 查看所需数据结构
  • 客户端身份验证、创建时间、最后通讯时间
  • 命令、命令参数
redisClient
= = = = =
clients
client1
client2
...

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进行记录
redisClient
arglist
argv
set
argc
key
value
  • 命令实现函数
dict
push
set
get
push_command
set_command
get_command
  • 输出缓冲区
  • 验证身份
    如果为零,除Auth指令外全部被拒绝
  • 时间
    • 开始时间ctime
    • 最后互动时间 lastinteraction
    • 软限时间 obuf_soft_limit_reach_time

4.2 连接与断开

连接十分简答,在clients链表结尾加入新client即可

  • 断开
    • 客户端进程被杀死
    • 客户端发送不符合协议格式的命令
    • 成为CLIENT KILL目标
    • 发送大小超过缓冲区
    • 收到的数据超过换冲区
  • 两种类限制
    • 硬限制,超过缓冲区大小,直接关闭
    • 软限制,未超过硬限制,但是超过软限制,开始计时,若一段时间之后恢复正常则不断开,如果超过限制时间,则断开

5. 单机服务器模式

本节重点介绍服务器与客户端的互动,并不涉及多机、集群操作。

5.1 命令的请求与执行

整个客户端请求过程可以高度概括为如下过程:

发送
回复
客户端
server
  1. 用户发送数据
  2. 服务器处理数据
  3. 回复ok

5.1.1 发送命令

客户端将文字转化为协议模式,之后将这段协议发给服务器(socket)

5.1.2 读取命令

  1. 读取协议,并放入输入缓冲区中query_buf
  2. 解析,分析参数,argv、argc

5.1.3 命令实现

  1. 查找命令表。同客户端,server有一套命令表,命令表中存储文字,并指向执行命令的rediscommad,redisCommand指向具体执行的函数,比如:
redisCommand
name:set
proc 指向执行函数
arity -3 :接受三个参数
sflag:wm // w写入命令 m占用内存命令
  1. 执行预备操作,进行如下检查
  • cmd是否指向null ,命令是否存在;
  • 根据arity,参数数量是否正确;
  • 验证身份,AUTH
  • 检查内存占用,是否设置maxmemory
  • 是否因为BGSAVE执行失败不可写
  • 是否进行数据载入
  • 是否进行LUA脚本阻塞
  • 是否正在执行事务(EXEC、DISCARD、MULTI、WATCH)
  1. 执行函数
    通过客户端的指针(指向具体的执行函数),并根据几个参数,进行赋值,执行。
  2. 后续工作
  • 加日志
  • 如果正在AOF持久化,写入缓存区
  • 如果正在复制,添加命令

5.1.4 返回命令

写入输出缓冲区,并打印

5.2 时间serverCron

Redis服务器中的serverCron函数默认每100毫秒执行一次,这个函数负责管理服务器资源并保证服务器自身的良好运转。

  1. 记录时间缓存:
    time_t unix_time;//秒级
    long long mstime;//毫秒级
  2. 更新LRU时钟,记录最后一次命令执行时间,可以记录数据库空转时间
    unsigned lruclock;
  3. 记录每一秒执行指令次数
    long long ops_sec_last_sample_time
  4. 记录内存峰值
    size_t stat_peak_memory;
  5. 处理SIGTERM信号该信号用来关闭数据库
    int shutdown_assap//为1关闭,为0不动作
  6. 管理客户资源,判断链接是否超时,输出区满
  7. 管理数据库,删除过期键
  8. 延迟BGREWRITEAOF命令,如果执行BGSAVE期间收到BGREWRITEAOF则延迟
    int aof_rewrite_scheduled
  9. 记录持久化的PID
    pid_t rdb_child_pid;//rdb saving pid
    pid_t aof_child_pid; // rewriting pid
    若两个pid都为-1 则做持久化检查
yes
no
yes
no
yes
no
服务器没有持久化操作
BGREWRITEAOF延迟
BGREWRITEAOF
保存条件满足
BGSAVE
可以重写AOF
do_nothing
  1. AOF缓冲区写入
  2. 关闭异步客户端
  3. 增加cronloops计数器

5.3 服务器初始化

服务器初始化主要由redis.c/initServerConfig负责。首先要进行一下配置

  1. 设置运行ID
  2. 设置默认频率
  3. 设置默认配置文件
  4. 设置运行架构(64 or 32)
  5. 设置默认端口号,
  6. 设置RDB和AOF持久化条件
  7. 设置LRU时钟,初始化命令列表
  8. 载入配置选项。根据redis.conf设置基本的redisNum,port等信息
  9. 初始化数据结构,包括server.client链表,server.db数组,保存订阅信息的server.pubsubpatterns,server.lua环境,server.slowlog查询日志
  10. 还原数据库
  11. 循环执行业务
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值