bigywater.tech
Redis · 37

事务和 Pub/Sub 的边界

测试PUBLISH和SUBSCRIBE能不能被排进MULTI事务,再验证一个连接真正进入订阅模式之后,普通读写命令会不会被拒绝,划清事务排队和真正订阅态这两条完全不同的边界。

均可排队
PUBLISH和SUBSCRIBE都能被排进MULTI事务
GET被拒绝
真正进入订阅态后普通命令无法执行
PING例外
订阅态下仍放行PING/QUIT/RESET等少数命令

做的事:把PUBLISH和SUBSCRIBE分别塞进MULTI事务里

Pub/Sub和事务是两套听起来不太搭的机制,测测能不能混用。

代码cmd(s, "MULTI") cmd(s, "PUBLISH", "ch1", "hello") cmd(s, "SET", "pxkey", "1") cmd(s, "EXEC") -- 另一个连接 cmd(s2, "MULTI") cmd(s2, "SUBSCRIBE", "ch2") cmd(s2, "EXEC")

验证:PUBLISH和SUBSCRIBE都能被正常排进事务

这一步比预想的更宽松。

结果PUBLISH排队: +QUEUED EXEC: [:0, +OK] (:0表示没有订阅者收到这条消息,SET成功) SUBSCRIBE排队: +QUEUED EXEC: [ [subscribe, ch2, 1] ] (是订阅确认消息本身,作为EXEC数组的一个元素)

验证了两点:PUBLISH跟其它命令一样,只是排队等EXEC统一执行,返回值(:0,表示没有人订阅ch1)也正常出现在数组里;更意外的是SUBSCRIBE本身居然也能被排进事务——EXEC执行完,这个连接就进入了订阅ch2的状态,EXEC返回的数组里那个元素就是的订阅确认消息本身。

验证:真正进入订阅模式后,这个连接只能发极少数命令

跟上一步"SUBSCRIBE能被排进事务"不同,这里说的是真的执行了SUBSCRIBE之后的连接状态。

结果SUBSCRIBE ch3 → 订阅确认消息 GET pxkey (订阅状态下尝试发普通命令) → 报错: ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context PING (订阅状态下) → 正常返回pong
这里划出了两条完全不同的边界:第一条边界是"事务排队阶段",这一阶段非常宽松,几乎任何合法命令(包括PUBLISH、SUBSCRIBE)都能被排进队列,因为排队阶段本质只是语法检查+入队,不涉及的连接状态切换;第二条边界是"连接真的进入订阅模式之后",这时候RESP2协议下的连接被限制成了一种特殊模式——服务器认为你此刻只关心"收消息",普通的读写命令被完全拒绝,只放行SUBSCRIBE系列命令、UNSUBSCRIBE系列、PING、QUIT、RESET这几个跟"退出订阅或保活"相关的命令。这也是为什么生产环境里,Pub/Sub订阅者通常都用一个独立的连接,不会跟其它读写操作共用同一条连接——一旦共用,这条连接就没法再干别的事了,除非先UNSUBSCRIBE退出订阅状态。

分步演示:事务和Pub/Sub的两条边界

每一步都是本文上面用裸RESP协议跑出来的原始结果。

排队阶段宽松,进入订阅态后连接被严格限制
这一步新验证的场景

    本文用到的工具

    • 本机 Redis 8.10.1
    • 裸socket + RESP协议:测试PUBLISH/SUBSCRIBE排队进事务,以及真正订阅态下的命令限制
    京ICP备2026067502号-1京公网安备11010502063487号