redis--单机实现

服务器中的数据库

struct redisServer
{
    
    
	...
	// 一个数组,保存着服务器中的所有数据库
	redisDb *db;
	// 需创建数据库个数
	int dbnum;
};

切换数据库

SELECT xx;// 默认为0号数据库
typedef struct redisClient
{
    
    
	...
	// 记录客户端当前正使用的数据库
	redisDb *db;
	...
}redisClient;

数据库键空间

Redis是一个键值对数据库服务器,
服务器中的每个数据库都由一个redisDb结构表示,
该结构的dict字典保存了数据库中的所有键值对,称该字典为键空间
typedef struct redisDb
{
    
    
	...
	dict *dict;
	...
}redisDb;
键空间的键也是数据库的键,每个键都是一个字符串对象
键空间的值是数据库的值,每个值对象类型可变.
- 对象过期自动删除
typedef struct redisDb
{
    
    
	...
	dict *expires;
	...
} redisDb;
- 主从一致
删除一致
只有访问主服务器时,
可能触发键的删除和新键的插入,
且主服务器执行键插入,删除后,会向从服务器发送该命令,
来保证主从一致.

对从服务器访问,只能用来读取数据.
从服务器不会主动触发键的插入,删除.
必须通过收到来自主服务器的插入,删除命令才能作键的插入,删除.
- 过期删除
1.惰性删除
要访问键时,先检查键是否过期.
过期则删除.
2.定期删除
定时,执行过期键扫描及过期键删除.
可控制定时回调执行时间.

RDB持久化

Redis是内存数据库,
RDB持久化,可以将Redis在内存中的数据库状态保存到磁盘.
RDB持久化生成的RDB文件是一个经过压缩的二进制文件.
利用RDB文件可进行数据库恢复.

- RDB文件的创建与载入
SAVE以阻塞方式创建RDB文件
BGSAVE以派生子进程,子进程阻塞方式创建RDB文件.
BGSAVE执行中,
后续新的SAVE或BGSAVE被拒绝执行.
后续BGREWRITEAOF被拒绝执行.

RDB文件的载入在服务器启动时自动执行.
	
如开启了AOF持久化,优先用AOF来还原数据库状态.
// 条件触发
struct redisServer
{
    
    
	...
	// 自动保存触发条件
	struct saveparam *saveparams;
	// 上次成功执行SAVE或BGSAVE后,
	// 服务器对数据库状态进行了多少次修改
	long long dirty;
	// 记录了服务器上次成功执行SAVE或BGSAVE的时间
	time_t lastsave;
	...
};

struct saveparam
{
    
    
	time_t seconds;
	int changes;
};

RDB文件结构

1.五个字符常量:REDIS
2.4字节版本号
3.databases部分,记录每个数据库的键-值对信息.
4.1字节EOF常量
5.8字节无符号整数check_sum
用于数据一致性检验.
RDB生成时,依据其余4部分内容按众所周知方法计算得到.
使用RDB时,再次依据其余4部分内容按众所周知方法计算并与记录的比较.

- databases部分
每个非空数据库在RDB文件中可保存为
1.1字节常量SELECTDB
2.1字节/2字节/5字节的数据库号码,db_number.
3.key_value_pairs保存了数据库中的所有键值对数据.
如键值对带有过期时间,则过期时间会和键值对保存在一起.
- key_value_pairs
1.不带过期时间的键值对
a.1字节TYPE
REDIS_RDB_TYPE_STRING
REDIS_RDB_TYPE_LIST
REDIS_RDB_TYPE_SET
REDIS_RDB_TYPE_ZSET
REDIS_RDB_TYPE_HASH
REDIS_RDB_TYPE_LIST_ZIPLIST
REDIS_RDB_TYPE_SET_INTSET
REDIS_RDB_TYPE_ZSET_ZIPLIST
REDIS_RDB_TYPE_HASH_ZIPLIST
key总是一个字符串对象,
value类型参考type
b.key
c.value
2.带过期时间的键值对
a.1字节的EXPIRETIME_MS,
告知读入程序,
后续读入的将是一个以毫秒为单位的过期时间
b.ms是一个8字节长的带符号整数.
记录着一个以毫秒为单位的UNIX时间戳.
- value编码
1.字符串对象
TYPE为REDIS_RDB_TYPE_STRING
可为REDIS_RDB_ENC_INT8/REDIS_RDB_ENC_INT16/REDIS_RDB_ENC_INT32
分别表示整数长度为8/16/32

可为REDIS_ENCODING_RAW:
打开压缩下,
如字符串长度小于等于20字节,则此字符串会被原样保存
如字符串长度大于20字节,则此字符串会被压缩后再保存
未打开压缩下,
原样保存
无压缩下:
a.len
b.string
压缩下:
a.REDIS_RDB_ENC_LZF
b.compressed_len
c.origin_len
d.compressed_string

2.列表对象
TYPE值为REDIS_RDB_TYPE_LIST
value部分格式为list_length + 字符串长度+字符串内容[重复list_length]
3.集合对象
TYPE值为REDIS_RDB_TYPE_SET
value部分格式为set_size+字符串长度+字符串内容[重复set_size]
4.哈希表对象
TYPE值为REDIS_RDB_TYPE_HASH,
value部分格式为hash_size+键字符串长度+键字符串内容+值字符串长度+值字符串内容[重复hash_size]
5.有序集合对象
TYPE值为REDIS_RDB_TYPE_ZSET
value部分格式为sorted_set_size+成员字符串长度+成员字符串内容+分值字符串长度+分值字符串内容
6.INTSET
TYPE值为REDIS_RDB_TYPE_SET_INTSET
value部分为字符串化表示的整数集合对象
7.ZIPLIST编码的列表,哈希表或有序集合
如TYPE值为	
REDIS_RDB_TYPE_LIST_ZIPLIST
REDIS_RDB_TYPE_HASH_ZIPLIST
REDIS_RDB_TYPE_ZSET_ZIPLIST
value保存的是一个压缩列表对象
保存方法为
将压缩列表转换成一个字符串对象
将转换得到的字符串对象保存到RDB文件

AOF持久化

AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的
服务器在启动时,
可通过载入和执行AOF文件中命令来还原到关闭前状态.
- AOF持久化的实现
1.命令追加
服务器在执行完一个写命令后,
以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区末尾.
struct redisServer
{
    
    
	...
	sds aof_buf;
	...
};
2.AOF文件的写入与同步
服务器在处理文件事件时可能执行写命令.
导致一些内容被追加到aof_buf缓冲区.
故,在每次结束一个事件循环之前,
会调用flushAppendOnlyFile,
决定是否需将aof_buf缓冲区内容写入到AOF文件.
并按一定策略决定是否额外执行文件同步.

- AOF文件的载入与数据还原
服务器读入并重新执行以便AOF文件里写命令,即可还原.
- AOF重写
为了避免AOF文件持续变大,引入AOF重写.
Redis服务器可创建一个新的AOF文件来代替现有的AOF文件.
新AOF文件不含浪费空间的冗余命令,
较原文件效果一致但体积小得多.
1.AOF文件重写的实现
从数据库中读取键现在的值,
用一条命令去记录键值对
2.AOF后台重写
产生子进程负责AOF重写.
在子进程执行AOF重写期间,
服务器进程需执行以下三个工作:
a.执行客户端发来的命令
b.将执行后的写命令追加到AOF缓冲区
c.将执行后的写命令追加到AOF重写缓冲区

子进程完成AOF重写工作后,
向父进程发一个信号,
父进程的信号处理执行以下工作:
a.将AOF重写缓冲区中的所有内容写入到新AOF文件中.
b.对新的AOF文件改名,原子地覆盖现有的AOF文件.
采用子进程,子进程产生时候具有父进程一样的数据状态.
相应得到一个所有数据库内存中键值对快照.
父进程信号处理,是补偿了快照时刻到信号处理时刻,
父进程对数据库的更改.

事件

- 文件事件
参考muduo对套接字描述符的epoll+nio+事件回调处理模式
- 时间事件
参考muduo对定时器事件的处理

持续运行的Redis服务器需定期对自身的资源和状态进行检查和调整.
定期操作包括
1.更新统计信息
2.清理过期键-值对
3.关闭和清理失效套接字
4.AOF/RDB
5.主服务器需执行针对从服务器的定期同步
6.集群下,需定期对集群做同步和连接测试.

客户端

通过使用I/O多路复用技术实现的文件事件处理器,
Redis服务器用单线程处理多个描述符的监听/事件处理.

对每个与服务器进行连接的客户端,
服务器都为这些客户端建立了相应的redis.h/redisClient结构,其中包括
1.套接字描述符
2.客户端名
3.标志
4.正使用的数据库指针/号码
5.当前要执行的命令/命令参数/参数个数/命令实现函数指针
6.输入缓冲区,输出缓冲区
7.一些数据结构
8.其他信息

参考muduo的TcpConnection
- 客户端属性
1.套接字描述符
typedef struct redisClient
{
    
    
	...
	int fd;
	...
}redisClient;
a.对伪客户端,fd为-1
载入AOF并还原/执行Lua脚本中Redis命令 时需用伪客户端
b.普通客户端
2.名字
typedef struct redisClient
{
    
    
	...
	robj *name;
	...
};
3.标志
typedef struct redisClient
{
    
    
	...
	int flags;
	...
};
a.REDIS_MASTER/REDIS_SLAVE
b.REDIS_PRE_PSYNC,REDIS_SLAVE下进一步表明版本
c.REDIS_LUA_CLIENT
d.REDIS_MONITOR/REDIS_UNIX_SOCKET/REDIS_BLOCKED/
REDIS_UNBLOCKED/REDIS_MULTI
e.REDIS_DIRTY_CAS/REDIS_DIRTY_EXEC
在REDIS_MULTI打开下进一步表示执行情况
f.REDIS_CLOSE_ASAP/REDIS_CLOSE_AFTER_REPLY/
REDIS_ASKING/REDIS_FORCE_AOF/REDIS_FORCE_REPL
g.REDIS_MASTER_FORCE_REPLY
4.输入缓冲区
typedef struct redisClient
{
    
    
	...
	sds querybuf;
	...
}redisClient;
5.命令与命令参数
在服务器将客户端发送的命令请求存到客户端状态的querybuf后,
服务器将对命令请求的内容进行分析,
得出的命令参数,参数个数将存到argv,argc
typedef struct redisClient
{
    
    
	...
	robj **argv;
	int argc;
	...
}redisClient;
argv[0]是命令,其后其他项是传给命令的参数
6.命令的实现函数
当程序在命令表中找到argv[0]所对应的redisCommand结构时,
它会将客户端状态的cmd指针指向这个结构
typedef struct redisClient
{
    
    
	...
	struct redisCommand *cmd;
	...
}redisClient;
之后,服务器可用cmd指向对象,及argv,argc中信息,
指向命令调用
7.输出缓冲区
命令回复保存在客户端输出缓冲区.
客户端输出缓冲区两个,固定大小,可变大小.
typedef struct redisClient
{
    
    
	...
	// 固定大小缓冲区
	char buf[REDIS_REPLY_CHUNK_BYTES];
	int bufpos;// buf中已经使用的字节数量
	...
}redisClient;
可变大小缓冲区由reply链表和一个或多个字符串对象组成
typedef struct redisClient
{
    
    
	...
	list *reply;
	...
}redisClient;
8.身份验证
客户端状态的authenticated用于记录客户端是否通过了身份验证
typedef struct redisClient
{
    
    
	...
	int authenticated;
	...
}redisClient;
9.时间
typedef struct redisClient
{
    
    
	...
	// 创建客户端的时间
	time_t ctime;
	// 客户端与服务器最后一次进行互动的时间
	time_t lastinteraction;
	// 可用来计算客户端的空转时间
	time_t obuf_soft_limit_reached_time;
	...
}redisClient;

客户端的创建与关闭

- 创建普通客户端
- 关闭普通客户端
- Lua脚本的伪客户端
服务器会在初始化时创建负责执行Lua脚本中包含的redis命令的伪客户端
并将这个伪客户端关联在服务器状态结构的lua_client属性中
struct redisServer
{
    
    
	...
	redisClient *lua_client;
	...
};
- AOF文件的伪客户端
载入AOF文件时,创建,载入完成时,关闭.

服务器

- 命令请求的执行过程
1.发送命令请求
2.读取命令请求
3.命令执行器
typedef struct redisCommand
{
    
    
	char *name;
	redisCommandProc *proc;
	int arity;// 命令参数个数
	char *sflags;
	int flags;
	long long calls;
	long long milliseconds;
}redisCommand;
sflags属性的标识
a.w/r/m/a/p/s/R/S/l/t/M
4.执行预备
a.检查客户端状态的cmd是否有效
b.参数合法性检查
c.权限验证/内存/执行权限/...
5.命令执行:函数调用
6.后续
a.慢查询日志/统计/AOF/主从
7.回复
8.客户端接收回复,打印结果

serverCron

- 更新服务器时间缓存
- 更新LRU时钟
- 更新服务器每秒执行命令次数
- 更新服务器内存峰值记录
- 处理SIGTERM信号
信号处理中设置关闭标志,再后续定时处理中,再执行关闭操作.
- 客户端资源管理
- 管理数据库资源
- 持久化逻辑
- 将AOF缓冲区中的内容写入AOF文件

初始化服务器

- 初始化服务器状态结构
- 载入配置选项
- 初始化服务器数据结构
- 还原数据库状态
- 执行事件循环

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/114847657