1. 事件
1.1 main函数流程图
1.2 文件事件
- 构成
- 套接字
- I/O多路复用程序
- 文件事件分派器
- 事件处理器
- 套接字产生AE_READABLE事件(服务器读套接字,优先)
- 套接字可应答(客户端对套接字执行connect)
- 套接字可读(客户端对套接字执行write/close)
- 套接字产生AE_WRITABLE事件(服务器写套接字)
- 套接字可写(客户端对套接字执行read)
1.3 时间事件
时间事件放入无序链表
- server.c/initServer()里的server.c/serverCron()函数
更新服务器的内存占用、数据库占用情况。- zmalloc_used_memory(), zmalloc_get_allocator_info()
清理过期键值对。 - databasesCron()
关闭和清理连接失效的客户端。 - clientsArePaused()
尝试进行 AOF 或 RDB 持久化操作。- rewriteAppendOnlyFileBackground(), rdbSaveBackground()
如果服务器是主节点的话,对附属节点进行定期同步。
如果处于集群模式的话,对集群进行定期同步和连接测试。- clusterCron()
如果处于哨兵模式的话,运行哨兵检查机制。- sentinelTimer()
2. 客户端
2.1 数据结构
struct redisServer{
list *clients;
...
}
struct client{
int fd; //客户端正在使用的套接字描述符
redisDb *db; //当前select的DB
int flags; //角色和状态
sds querybuf; //输入命令缓冲区
int argc; //参数个数【命令名字本身也是参数!】
robj **argv; //参数
struct redisCommand *cmd; //命令集合
int bufpos; //固定大小的输出缓冲区已使用
char buf[PROTO_REPLY_CHUNK_BYTES]; //固定大小的输出缓冲区,16k
list *reply; //可变大小输出缓冲区,字符串链表
int authenticated; //0未认证通过,1认证通过
...
}
2.2 客户端类型
- 伪客户端(fd = -1,不需要套接字描述符,伴随redisServer一直存在)
- 载入AOF文件还原数据库
- 执行Lua脚本里的redis命令
- 普通客户端(fd > 0)
redis > CLIENT list
reids > CLIENT setname
3. 服务器
3.1 命令集合
struct redisCommand redisCommandTable[] = {
{"module", //name, 命令名称
moduleCommand, //proc,执行函数指针
-2, //arity,参数个数,包括命令本身。-N个表示大于或等于N个。
"admin no-script", //sflags,命令属性
0, //flags,分析命令属性得到的二进制标识
NULL,0,0,0,0,0,0},
{"get",getCommand,2,
"read-only fast @string",
0,NULL,1,1,1,0,0,0}
...
}
命令属性 | 含义 | 示例 |
---|---|---|
read-only | 只读 | get |
write | 要写 | set,rpush,del |
use-memory | 占用内存(需检查内存) | set |
admin | 管理命令 | bgsave,shutdown |
fast | 内核调度给时间就不会延迟执行的命令,隐式调用del的均不是fast | get,setnx,exists,rpush |
no-script | 不能在Lua脚本里使用 | blpop,auth,bgsave,multi相关 |
random | 随机命令 | randomkey,srandmember |
ok-loading | 允许命令加载数据库 | select,auth,multi相关,pub-sub相关 |
ok-stale | 允许正在数据载入时执行 | select,auth,multi相关,pub-sub相关 |
no-monitor | 不传给monitor | auth,hello |
pub-sub | pub-sub相关命令 | subscribe等 |
@xxx | 按类别给命令排序用的属性 | @write,@hash等 |
3.2 【单机时】命令预备工作
- 检查cmd是否为null,null表示没有此命令。
- 检查参数。
- 检查auth通过没有。
- 写命令时,检查内存占用,有需要时回收内存。
- 写命令时,根据stop-writes-on-bgsave-error(默认yes),来检查上次BGSAVE是否error。
- 若客户端订阅了频道,只会接受客户端pub-sub相关命令。
- 若正在载入数据的话,to-stale命令才允许执行。
- 若正在阻塞执行Lua,则只允许shutdown nosave和script kill命令。
- 若客户端正在执行事务,则只执行此客户端的事务相关命令。
- 若打开了monitor功能,则把命令传给monitor。
3.3 命令后续工作
- 若开启了慢查询(默认slowlog-log-slower-than 10000,表示0.1秒。slowlog-max-len 128),检查并添加慢查询日志。
- 更新redisCommandTable[]里面的calls计数和millisenconds执行总时长。
- 若开启了AOF,若有需要则记录到AOF。
- 若正在主从复制,则传播命令。
4. client/server交互流程
4.1 client发起socket连接
src/redis-cli -h {ip} -p {port}
//unix socket用于同一主机进程间的通信
src/redis-cli -s /tmp/redis.sock
- cliConnect() -> redisConnect() -> 创建context,对socket执行connect。
- socket产生AE_READABLE事件。
- server接受成功后,cliAuth() 权限验证。
- server接受成功后,cliSelect() DB选择。
- server接受成功后,cliSwitchProto() 协议选择。
4.2 server接受socket连接
- 初始化服务器时,打开TCP监听端口。
- 连接应答处理器,通过epoll_ctl注册事件。ae.c/aeCreateFileEvent(),ae_epoll.c/acceptTcpHandler()
- client发起socket连接后,server通过epoll_wait取出事件。
- acceptTcpHandler()接受socket连接。
- server后续1:createClient()
- server后续2:ae.c/aeCreateFileEvent()->ae_epoll.c/aeApiAddEvent()->epoll_ctl注册事件,绑定readQueryFromClient方法。
4.3 客户端开始写入
- 将客户端内容格式化成redis协议。cliSendCommand()
- 写入context的outbuf。
- outbuf内容写入套接字描述符,产生AE_READABLE事件。
- 读取挂起。
4.4 server接收写入
- readQueryFromClient()读取内容到client对象命令缓冲区。
- redis协议解析并传给processCommand()。
- 命令预备工作。
- call(),从redisCommand命令表里找执行方法。(判断key过期,键空间失效通知等执行相关操作)。
4.5 server返回结果
- 写client reply缓冲区。
- aeCreateFileEvent()注册写事件,绑定sendReplyToClient()方法。
- 结果内容写入到socket,触发AE_WRITABLE事件。
4.6 Client收到结果
- 若客户端1秒内收到读事件,则从socket读数据 - redisBufferRead()。
- cliFormatReplyRaw()解析。
- printf()打印展示。