redis 事件 客户端 服务器

1. 事件

1.1 main函数流程图

image

1.2 文件事件

image
  • 构成
  1. 套接字
  2. I/O多路复用程序
  3. 文件事件分派器
  4. 事件处理器
  • 套接字产生AE_READABLE事件(服务器读套接字,优先)
  1. 套接字可应答(客户端对套接字执行connect)
  2. 套接字可读(客户端对套接字执行write/close)
  • 套接字产生AE_WRITABLE事件(服务器写套接字)
  1. 套接字可写(客户端对套接字执行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一直存在)
  1. 载入AOF文件还原数据库
  2. 执行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 【单机时】命令预备工作

  1. 检查cmd是否为null,null表示没有此命令。
  2. 检查参数。
  3. 检查auth通过没有。
  4. 写命令时,检查内存占用,有需要时回收内存。
  5. 写命令时,根据stop-writes-on-bgsave-error(默认yes),来检查上次BGSAVE是否error。
  6. 若客户端订阅了频道,只会接受客户端pub-sub相关命令。
  7. 若正在载入数据的话,to-stale命令才允许执行。
  8. 若正在阻塞执行Lua,则只允许shutdown nosave和script kill命令。
  9. 若客户端正在执行事务,则只执行此客户端的事务相关命令。
  10. 若打开了monitor功能,则把命令传给monitor。

3.3 命令后续工作

  1. 若开启了慢查询(默认slowlog-log-slower-than 10000,表示0.1秒。slowlog-max-len 128),检查并添加慢查询日志。
  2. 更新redisCommandTable[]里面的calls计数和millisenconds执行总时长。
  3. 若开启了AOF,若有需要则记录到AOF。
  4. 若正在主从复制,则传播命令。

4. client/server交互流程

4.1 client发起socket连接

src/redis-cli -h {ip} -p {port}
//unix socket用于同一主机进程间的通信
src/redis-cli -s /tmp/redis.sock
  1. cliConnect() -> redisConnect() -> 创建context,对socket执行connect。
  2. socket产生AE_READABLE事件。
  3. server接受成功后,cliAuth() 权限验证。
  4. server接受成功后,cliSelect() DB选择。
  5. server接受成功后,cliSwitchProto() 协议选择。

4.2 server接受socket连接

  1. 初始化服务器时,打开TCP监听端口。
  2. 连接应答处理器,通过epoll_ctl注册事件。ae.c/aeCreateFileEvent(),ae_epoll.c/acceptTcpHandler()
  3. client发起socket连接后,server通过epoll_wait取出事件。
  4. acceptTcpHandler()接受socket连接。
  5. server后续1:createClient()
  6. server后续2:ae.c/aeCreateFileEvent()->ae_epoll.c/aeApiAddEvent()->epoll_ctl注册事件,绑定readQueryFromClient方法。

4.3 客户端开始写入

  1. 将客户端内容格式化成redis协议。cliSendCommand()
  2. 写入context的outbuf。
  3. outbuf内容写入套接字描述符,产生AE_READABLE事件。
  4. 读取挂起。

4.4 server接收写入

  1. readQueryFromClient()读取内容到client对象命令缓冲区。
  2. redis协议解析并传给processCommand()。
  3. 命令预备工作。
  4. call(),从redisCommand命令表里找执行方法。(判断key过期,键空间失效通知等执行相关操作)。

4.5 server返回结果

  1. 写client reply缓冲区。
  2. aeCreateFileEvent()注册写事件,绑定sendReplyToClient()方法。
  3. 结果内容写入到socket,触发AE_WRITABLE事件。

4.6 Client收到结果

  1. 若客户端1秒内收到读事件,则从socket读数据 - redisBufferRead()。
  2. cliFormatReplyRaw()解析。
  3. printf()打印展示。

你可能感兴趣的:(redis 事件 客户端 服务器)