Skip to content

异步 API 与回调队列

This content is not available in your language yet.

hiredis 异步 API 的核心不是创建线程,而是把“发送命令”和“回调 reply”放入事件循环驱动的状态机。一个 async context 维护连接状态、待处理 callback 队列、读写兴趣和超时回调。

先给答案:异步客户端把“等待响应”改造成“保存回调并由事件驱动推进”

Section titled “先给答案:异步客户端把“等待响应”改造成“保存回调并由事件驱动推进””

异步 API 不会在发命令的线程里等待 socket,而是把命令、callback 和释放函数按顺序放入 pending 队列。可写事件负责刷新输出,可读事件驱动 reader 解析 reply,再从队列头取出对应 callback。

顺序匹配成立的前提是:命令发送顺序和服务端响应顺序一致,且连接在断开时能一次性处理所有未完成命令。超时并不是 reader 自己产生的结果,而是外部事件循环触发的定时器;因此 adapter、context 和 callback 的 cleanup 必须协同,否则会出现回调不触发或释放两次。

CONNECTING -> CONNECTED -> COMMANDS_PENDING
| | |
failure read/write callbacks
v v v
DISCONNECTED <- error <- redisProcessCallbacks

redisAsyncConnectWithOptions 位于 async.c:172,内部通过 redisAsyncInitialize(:106)从同步 context 扩展;连接/断开回调注册在 :252、:260。异步释放从 redisAsyncFree(:415)进入,真正释放由内部流程决定。

__redisAsyncCommand 在 async.c:831 把编码后的 command 写入输出缓冲,同时把 callback 和 privdata 放入待处理队列。读事件进入 redisAsyncHandleRead(:724),随后 redisProcessCallbacks(:570)从 reader 取 reply,并按顺序执行 callback。

redisAsyncCommand
-> output buffer + callback list
-> addWrite
-> socket writable
-> addRead
-> reader reply
-> redisProcessCallbacks

非阻塞 connect 的可写事件会触发 __redisAsyncHandleConnect(:672);失败路径进入 __redisAsyncHandleConnectFailure(:664)。断开时不能只关闭 fd,还必须处理剩余 callback:__redisAsyncFree(:362)负责清理上下文和待处理状态。

替代方案:给每个命令分配独立 future,并允许 reply 任意顺序匹配。 为什么不行:RESP 普通响应本身没有请求 ID,客户端只能按发送顺序关联 in-band reply;强行乱序需要改变协议或额外 multiplex 层。 证据:redisProcessCallbacks 按 callback 队列取出 reply;订阅场景由 __redisGetSubscribeCallback(:470)单独处理 push/订阅消息。

redisAsyncHandleTimeout 位于 async.c:776,超时通常通过 adapter 的 timer 回调进入。调用 redisAsyncFree 不等于立即在任意回调中释放;源码保留延迟清理路径,以避免正在派发 callback 时释放当前 context。

场景 现象 原因 规避
callback 中直接 free 当前 context 崩溃或 use-after-free 仍在 callback dispatch 栈上 使用库允许的 disconnect/free 路径
未注册 write 事件 命令 callback 永不触发 输出缓冲没有被冲刷 正确实现 ev.addWrite
timeout 未接入 timer 连接永久挂起 async core 只提供 timer hook adapter 实现 scheduleTimer
订阅连接按普通请求处理 reply 对应关系错误 push/订阅消息改变匹配规则 使用订阅 callback 分支

异步客户端的安全释放必须是状态机的一部分,而不是调用者随时 free()。回调队列、延迟销毁和断开收尾是所有 callback-based 网络库的共同问题。

面试锚点

  • async context 如何把 reply 匹配回 callback?
  • 为什么普通 RESP reply 不能乱序完成?
  • redisAsyncFree 为什么可能延迟释放?