redis网络和事件循环

aeFileEvent和connectionType:

  • 前者直接面对fd,较简。 后者抽象度高,上升到conection.

redis服务器是事件驱动程序,需要处理两类事件:

  • 文件事件:redis服务器与客户端或者其他服务器进行连接,文件事件是对套接字操作的抽象。通信会产生很多套接字文件事件,服务器监听处理这些来完成网络通信。
  • 时间事件:定时操作。
 // 事件循环
 
 typedef struct aeEventLoop {
 
   int maxfd; // 目前最大注册fd
 
   int maxsize;  // 支持的最大fd-1。即events数组大小。
 
   aeFileEvent* events;  // 已注册事件数组。events[0]对应fd为0.
 
   aeFileEvent* fireEvents;  // 触发的事件队列。
 
   aeApiState* apiState;  // IO多路复用状态
 
   int stop;  // 事件循环停止标志
 
 } aeEventLoop;

注册和触发事件:

  • 为fd注册事件读/写,events[fd].mask包含 AE_READABLE/AE_WRITABLE, 并且对于fileproc指向回调
  • IO复用等待返回填充到fireevtns,如果fd读/写事件触发,fireevents就会有一个{fd, AE_READABLE/AE_WRITABLE}
  • 注册事件是静态的,存储所有注册事件
  • 触发事件队列是动态,存储当前已触发事件,

文件事件

基于Reactor模式开发了自己的网络事件处理器:file event handler.

  • 多路复用,监听多套接字,根据套接字执行任务关联不同事件处理器。
  • IO复用ready后,通过关联的处理器进行执行。

image-20250413183015070

事件循环:IO多路复用会把ready的套接字放到队列,逐一送到分派处理,只有处理完成后,才会继续从队列取出处理。

IO多路复用机制

整体分为三阶段:

  1. 事件注册:将描述符或者事件注册到事件循环。
  2. 事件等待:使用select、epoll等待事件触发。
  3. 事件处理:触发事件后,调用注册的回调器处理。
// 事件循环
typedef struct aeEventLoop {
  int maxfd; // 目前最大注册fd
  int maxsize;  // 支持的最大fd。即events数组大小。
  aeFileEvent* events;  // 已注册事件数组。events[0]对应fd为0.
  aeFileEvent* fireEvents;  // 触发的事件队列。
} aeEventLoop;
 
// 注册事件
typedef struct aeFileEvent {
  int type;  // AE_READABLE,AE_WRITABLE
  aeFileProc* rfileProc;  // 事件读处理程序
  aeFileProc* wfileProc; // 事件写处理程序
  void* procArg; // 事件处理程序参数
} aeFileEvent;
// 触发事件
typedef struct aeFireEvent {
  int fd;
  int type;  // 标记events[fd]触发上触发的事件类型。
} aeFireEvent;

时间事件:

  • 定时:
  • 周期
typedef struct aeTimeEvent {

  long long id;  // 时间事件id

  long when; // 毫秒时间戳

  aeTimeProc* timeProc;  // 时间事件处理函数

  void* procArg; // 时间事件处理函数参数

  struct aeTimeEvent* next;  // 下一个时间事件

} aeTimeEvent;

id从小到大递增,新事件ID>旧事件ID

定时心跳、info事件 穿插在正常的其他都事件流中, 需要很明确的将其分开。

RESP协议

数据类型 前缀 示例
简单字符串 + `+OK
错误信息 - -ERR unknown command
整数 : :100
批量字符串 $ $5\r\nhello\r\n
多条批量字符串(数组 * *2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n

RESP 设计简单高效,便于解析,同时支持二进制安全的数据传输。

【问题】

  1. 为什么RESP没有全局起止符号?这在RESP+RDB这样的内容下还要解析,而非直接获取。

优点:如果是这样,RESP格式内容边界很清晰。能够完整获取一个RESP内容。

<START>+OK\r\n<END>
<START>*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n<END>
<START>+FULLRESYNC repl-id offset\r\n$1024\r\n<RDB data><END>

缺点:

  • 冗余开销:每个消息多12个字节,对于高频SET GET 命令性能影响。
  • 不符合TCP流式传输:需要等待完整一个RESP消息传来。

{ping} -> *1\r\n$4\r\nping\r\n


服务器

多类型监听是什么,为什么?一种类型需要多端口监听吗?

  1. TCP 6379; TLS 6380; Unix socket /tmp/redis.sock
  2. 同一类型如tcp通过多路复用即可搞定大并发,所以不需要。

## 套接字

EAGAIN 描述符是非阻塞模式,当前操作请等候。 表示 写缓冲满或读缓冲空

EINTR 阻塞操作被信号打断。

results matching ""

    No results matching ""