跳到正文
← 返回博客
LEARNING / 学习

会话队列里的所有权、幂等与未知状态

现有链路已按业务身份串行;keyed mailbox、租约和 indeterminate 重试规则仍是待验证的设计扩展。

这篇文章包含两层内容,先把它们分开。

现有事实:

  • 消息会绑定客户端实例、店铺和买家会话;
  • 同一店铺与买家的任务按顺序处理;
  • 消息使用 mid 去重;
  • 多实例拥有各自的业务上下文;
  • 发送后会继续查询数据库确认目标记录;
  • 人工接管能够暂停对应业务身份的自动回复。

从这些事实推出的设计扩展:

  • 使用 instance_id + shop_id + conversation_id 作为完整 queue key;
  • 为每个 key 建立单消费者 mailbox;
  • 使用全局 semaphore 限制总并发;
  • 用租约或版本控制人工与旧 worker 的执行权;
  • indeterminate 与明确失败分开。

后半部分是可以实现和测试的调度模型,当前材料不足以证明它已经完整进入现有系统。

queue key 表达了什么

queue_key = instance_id + shop_id + conversation_id

这不是一种字符串拼接偏好,而是一项并发契约:同一个客户端实例、同一家店铺、同一个会话中的动作必须有序;不同会话可以在总并发上限内并行;身份没有确认时,worker 不得执行发送。

几种更短的键会表达不同错误:

队列范围能保证什么会丢掉什么
全局所有任务完全串行一个慢会话阻塞全部店铺
仅店铺店铺内顺序不同买家互相等待
仅买家买家维度顺序不同店铺或实例可能被错误合并
仅消息 ID单条任务可独立执行同一会话内的先后顺序
实例 + 店铺 + 会话业务身份内串行仍需全局并发与恢复控制

组合键的作用,是把真正需要串行的业务身份放进同一邮箱。它不自动解决吞吐、恢复或幂等。

一个建议的执行结构是:

dispatcher
→ keyed mailbox
  → single consumer per key
    → global semaphore
      → generator / sender / verifier

这里的 mailbox 与 semaphore 属于设计扩展,不是现有实现事实。

状态决定动作是否可以重做

发送函数没有抛异常,仍然可能停在尚未确认的中间状态。调度层真正需要知道的,是动作是否可能已经发生,以及当前是否允许再做一次。

当前状态发送动作是否可能已经发生默认处理
queued可以重新调度
claimed否或尚未进入发送先判断执行权是否过期
answer_ready可以重新取得发送权限
dispatched先查终态,禁止直接重发
confirmed是,且已有终态证据关闭任务,不再重试
manual_hold自动化不再拥有发送权阻止新的 dispatch
transient_failed已确认未发送在上限和退避约束下重试
permanent_failed已确认不可继续转人工或关闭

这张表是建议状态模型,不是当前数据库已经采用的 schema。

其中最重要的安全门是:

dispatched + 没有确认
≠ failed
≠ 可以自动重发

进程崩溃、确认查询超时或回调丢失,都可能让系统失去证据,却不代表发送没有发生。此时更安全的顺序是先按消息、会话和时间窗口查询最终记录,再决定是否恢复。

发送何时真正完成,由《自动回复的终点,不是点击“发送”》定义。本文不再重复数据库终态条件,只处理到达终态之前的三个问题:哪些任务必须等待、谁拥有执行权、证据不足时为什么不能自动再做一次。

人工接管是所有权切换

人工接管如果只是界面上的开关,旧 worker 仍可能在已经领取任务后继续发送。更完整的设计需要把 manual_hold 与同一个 queue key 绑定,并为所有权增加版本或租约。

切换后,不同阶段对应不同动作:

  1. 新消息可以继续被观察和记录;
  2. 尚未领取的自动发送任务停止推进;
  3. 已领取但未 dispatch 的任务失去发送权;
  4. 已 dispatch 的任务继续做终态确认;
  5. 恢复自动化前,重新确认最新消息位置与会话上下文。

这些规则用于约束未来实现。当前材料只能确认人工接管和业务身份隔离已经存在,不能确认版本租约与上述五步状态转换已经落地。

三组必须执行的故障注入

要把这个设计升级为实现结论,至少需要三组测试:

  1. **确定性调度:**同一 queue key 永不并行,不同 key 在全局额度内可以并发;
  2. **崩溃恢复:**分别在 answer_readydispatchedconfirmed 前后终止 worker,确认恢复流程不会越过终态查询直接重发;
  3. **人工接管竞态:**在排队、已领取和发送前切换 manual_hold,确认旧 worker 不能继续取得发送权。

之后才能观察每个 key 的等待时间、热点队列、indeterminate 积压和重试原因。没有这些数据,本篇可以交付的是调度安全模型,不是吞吐提升或故障恢复效果。