持久化执行前置课(六):事务发件箱,让状态推进和命令发布不再双写
📅 2026/8/2 5:16:52
👁️ 次浏览
上一篇让重复活动收敛到同一业务回执。然而决定核一次会同时产生新事件和待执行命令若先更新状态再投队列崩溃会丢命令若先投队列再更新状态消费者可能看到尚不存在的前提。一、痛点两次成功之间总有崩溃窗口数据库事务无法覆盖普通消息代理所谓“按顺序调用两个可靠系统”仍然是不可靠的双写。重试可以修复部分失败却无法判断第一次发布是否已经被代理接受。更麻烦的是消息可能在数据库提交前被极速消费者处理产生一个找不到源事件的孤儿副作用。事务发件箱把命令先作为数据库行与事件在同一事务提交。独立发布器扫描未发布行发送后标记完成。发布器崩溃会导致重复发送而非丢失因此消费者仍需使用上一篇的活动幂等键。这个组合明确给出保证数据库事实与命令意图原子产生传输至少一次业务副作用按键去重。二、原理同库原子跨界重试下面程序在一个 SQLite 事务中追加事件和发件箱记录。故意制造重复逻辑命令时command_id唯一约束拒绝第二行。读取者永远不会看到只有事件而没有命令意图的中间状态。importjsonimportsqlite3 dbsqlite3.connect(:memory:)db.executescript( CREATE TABLE events( run_id TEXT NOT NULL, seq INTEGER NOT NULL, kind TEXT NOT NULL, PRIMARY KEY(run_id, seq) ); CREATE TABLE outbox( command_id TEXT PRIMARY KEY, run_id TEXT NOT NULL, kind TEXT NOT NULL, payload TEXT NOT NULL, published INTEGER NOT NULL DEFAULT 0 ); )defcommit_decision(run_id:str,seq:int,event:str,command_id:str,command:str,payload:dict)-None:withdb:db.execute(INSERT INTO events VALUES(?,?,?),(run_id,seq,event))db.execute(INSERT INTO outbox(command_id,run_id,kind,payload) VALUES(?,?,?,?),(command_id,run_id,command,json.dumps(payload,sort_keysTrue,separators(,,:))),)commit_decision(run-1,1,trip_requested,cmd-1,lock_budget,{amount:900})event_countdb.execute(SELECT COUNT(*) FROM events).fetchone()[0]command_countdb.execute(SELECT COUNT(*) FROM outbox).fetchone()[0]print(events,event_count,commands,command_count)print(db.execute(SELECT command_id,kind,published FROM outbox).fetchone())输出events 1 commands 1 (cmd-1, lock_budget, 0)三、实现发布租约允许崩溃后接管多个发布器需要避免同时长期处理同一行同时又要允许死节点的任务被接管。示例使用逻辑时钟与租约领取时把行改为sending并记录截止点超时行可重新领取确认发布只接受持有该租约的 worker。真实数据库应使用行锁或带版本号的条件更新。fromdataclassesimportdataclassdataclassclassItem:command_id:strstatus:strpendingowner:str|NoneNonelease_until:int0deliveries:int0defclaim(item:Item,worker:str,now:int,ttl:int)-bool:availableitem.statuspendingor(item.statussendinganditem.lease_untilnow)ifnotavailable:returnFalseitem.statussendingitem.ownerworker item.lease_untilnowttl item.deliveries1returnTruedefacknowledge(item:Item,worker:str)-None:ifitem.status!sendingoritem.owner!worker:raiseRuntimeError(lease_not_owned)item.statuspublisheditemItem(cmd-1)print(a_claimed,claim(item,worker-a,now0,ttl5))print(b_early,claim(item,worker-b,now3,ttl5))print(b_takeover,claim(item,worker-b,now6,ttl5))acknowledge(item,worker-b)print(status,item.status,deliveries,item.deliveries)输出a_claimed True b_early False b_takeover True status published deliveries 2四、踩坑已标记发布不代表已处理发件箱的published只证明消息代理接受了消息不能证明活动完成完成必须由独立回执事件表达。若在发送前标记发布会丢消息发送后再标记则必然存在重复窗口这是设计允许的行为。另一个坑是扫描没有索引的整张表积压后会拖垮主库应为状态和可用时间建立索引并限制批量。租约不是锁定业务所有权的永久凭证。worker 发生长暂停后可能在租约过期时继续发送因此下游幂等仍不可省。清理发件箱也要等到消息已发布、完成回执已持久化且保留期满足不能只看到published1就删除。失败消息应进入可检查状态而非无上限热循环。五、验证守恒关系比成功日志可靠对每个会产生命令的事件数据库中必须恰有一个稳定命令 ID任何发件箱行都必须能追溯到源运行完成事件必须引用已存在命令。测试在事务中途抛异常应看到事件和命令都没有提交在发送后、确认前杀死发布器应看到重复投递但只有一个业务结果。到这里事实、命令意图和外部回执已经连成链。下一篇将系统化讨论崩溃恢复怎样从租约、心跳和明确的“不确定”状态中判断该自动接管还是停给人工。参考来源Microsoft事务发件箱模式AWS事务发件箱SQLite原子提交 觉得有用就点个赞 收藏方便回头查阅有疑问直接在评论区留言我看到都会回。 本文属于《持久化执行前置课》系列持续更新关注不迷路。 文章里的代码都能直接跑。想要可直接 clone 的完整工程 配套部署脚本 / 踩坑清单评论一声或发邮件到cj2664qq.com我免费发你。如果你正好在做类似系统、或有工程化难题想找人做也欢迎邮件聊一句——我按实际情况评估能落地的就接单或出方案。评论和邮件都能直接找到我不用跳别的平台。
最近半年,我所在的技术群里,关于“AI写测试用例”的讨论翻了十倍不止。不是因为这东西新鲜,而是因为越来越多人发现——自己花一整天写的用例,工具30秒就吐出来了,质量还说得过去。
恐慌就是这么来的。
但更多人试了一…
📅 2026/8/2 5:16:52
1. 从一次慢查询说起:为什么数组操作值得深究最近在排查一个数仓慢SQL作业时,遇到了一个典型的性能瓶颈。一个看似简单的报表查询,在数据量增长到TB级别后,执行时间从几分钟飙升到了几十分钟。通过监控工具和EXPLAIN命令层层剖析&…
📅 2026/8/2 5:15:51
1. 项目概述:从一道编程题看“完美”的定义最近在整理一些经典的编程题目时,又看到了“1397 - 完美的偶数?”这个标题。乍一看,这像是一个简单的数学判断题,但真正深入进去,你会发现它远不止于此。它实际上…
📅 2026/8/2 5:15:51
终极解决方案:如何在Mac上免费实现NTFS移动硬盘完全读写 【免费下载链接】Free-NTFS-for-Mac Nigate: An open-source NTFS utility for Mac. It supports all Mac models (Intel and Apple Silicon), providing full read-write access, mounting, and management …
📅 2026/8/2 12:22:39
1. 项目概述:为什么Grove Arduino套件是初学者的“神兵利器” 如果你刚接触电子制作和编程,面对一堆散乱的电阻、电容、面包板和五颜六色的杜邦线,是不是感觉头大,不知道从何下手?我刚开始玩Arduino那会儿,…
📅 2026/8/2 12:22:39
1. 从“信息孤岛”到“复试战场”:我的东南大学通信复试突围记 又到了一年一度保研/考研复试季,对于电子信息、通信工程这类热门工科专业的同学来说,拿到初试高分只是拿到了入场券,真正的“硬仗”往往在复试环节。尤其是像东南大学…
📅 2026/8/2 12:22:39
3分钟上手!用这个网页版暗黑2存档编辑器,告别繁琐安装 【免费下载链接】d2s-editor 项目地址: https://gitcode.com/gh_mirrors/d2/d2s-editor
还在为暗黑破坏神2的存档修改烦恼吗?每次都要下载安装复杂的工具,还要担心兼…
📅 2026/8/2 12:22:39
FF14 ACT辍学插件完整指南:三步快速跳过副本动画的终极方案 【免费下载链接】FFXIV_ACT_CutsceneSkip 项目地址: https://gitcode.com/gh_mirrors/ff/FFXIV_ACT_CutsceneSkip
还在为《最终幻想14》副本中冗长的过场动画而烦恼吗?FF14 ACT辍学插件…
📅 2026/8/2 12:22:39
如何快速搭建高效的WMS仓库管理系统:完整免费教程 🚀 【免费下载链接】WMS 项目地址: https://gitcode.com/gh_mirrors/wms1/WMS
你是否正在寻找一款功能强大、易于部署的仓库管理解决方案?ZEQP仓储管理系统(WMSÿ…
📅 2026/8/2 12:21:38
1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…
📅 2026/8/2 0:00:48
温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…
📅 2026/8/2 0:00:48
1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同ÿ…
📅 2026/8/2 0:00:48
1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…
📅 2026/8/2 0:00:48
温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…
📅 2026/8/2 0:00:48
1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同ÿ…
📅 2026/8/2 0:00:48
AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言
HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…
📅 2026/8/2 1:22:12
无损视频剪辑终极指南:如何实现快速高效的多媒体处理 【免费下载链接】lossless-cut The swiss army knife of lossless video/audio editing 项目地址: https://gitcode.com/gh_mirrors/lo/lossless-cut
在数字媒体创作领域,视频编辑处理的质量损…
📅 2026/8/2 0:02:59
1. 本科生论文写作的AI辅助现状本科毕业论文是每个大学生必须跨越的一道坎。记得我当年写论文时,光是文献检索就花了整整两周时间,打印的参考文献堆满了半个书桌。如今AI技术的发展为学术写作带来了革命性变化,合理使用这些工具可以节省80%以…
📅 2026/8/2 1:22:12