Worker崩溃了怎么办:honker At-Least-Once语义、崩溃恢复与故障注入完全指南

📅 发布时间:2026/9/29 18:53:11
Worker崩溃了怎么办:honker At-Least-Once语义、崩溃恢复与故障注入完全指南
Worker崩溃了怎么办honker At-Least-Once语义、崩溃恢复与故障注入完全指南【免费下载链接】honkerSQLite extension bindings for Postgres NOTIFY/LISTEN semantics with durable queues, streams, pub/sub, and scheduler项目地址: https://gitcode.com/gh_mirrors/ho/honker你的任务队列 Worker 突然崩溃了正在处理的消息丢了吗honker 是一款为 SQLite 提供 Postgres NOTIFY/LISTEN 语义的扩展内置持久化队列、流、发布/订阅与调度器。它采用At-Least-Once至少一次投递语义Worker 崩溃、进程被强杀、甚至磁盘写满任务都不会凭空消失而是通过可见性超时、重试预算和死信表自动恢复。本文将带你彻底理解这套机制以及项目如何用真实的SIGKILL和故障注入测试来证明它。为什么需要 At-Least-Once 语义任务队列的核心难题是Worker 拿到任务后崩溃了任务怎么办honker 的回答是机制说明可见性超时任务被认领后有claim_expires_at截止时间超时未确认则重新可见重试预算max_attempts限制重试次数防止无限循环死信表重试耗尽的任务进入_honker_dead表附带last_error原因同事务入队enqueue与业务写入在同一事务提交回滚则一起消失核心思想只有一句话任务行就写在 SQLite 文件里崩溃改变不了已经提交的事实而没提交的写入会随进程一起回滚。三种典型崩溃场景与恢复机制场景一Worker 认领后直接猝死这是最常见的场景。Worker 通过claim()拿到任务还没执行ack()就挂了。honker 的恢复流程是任务的claim_expires_at到期后行变回可认领状态其他 Worker或重启后的同一 Worker再次认领attempts计数 1若attempts超过max_attempts任务不再被认领而是移入死信表last_error标记为max attempts exceeded这套逻辑在回归测试中被逐行验证tests/test_max_attempts_reclaim.py 中模拟了 Worker 认领后不ack就死亡的过程断言第三次认领时任务已被死信而非重新投递——这修复过一个真实 bug早先claim的回收路径不检查max_attempts导致任务被无限回收。场景二进程在事务中途被 SIGKILL比崩溃更狠的是内核级强杀。项目测试 tests/test_crash_recovery.py 的做法堪称教科书# 子进程开启 BEGIN IMMEDIATE 并写入一条任务然后父进程直接杀它 with db.transaction() as tx: q.enqueue({i: 999}, txtx) print(READY, flushTrue) time.sleep(60) # 父进程在这里 SIGKILL 我们强杀之后一个全新进程打开同一个.db文件验证四件事见 test_sigkill_mid_enqueue_tx_leaves_db_clean✅ 文件未损坏PRAGMA integrity_check返回ok✅ 被杀掉的写入没有泄漏任务表里零残留行✅ 崩溃后 enqueue → claim → ack 完整链路照常工作✅ 数据库不卡在写锁状态新写者能立即获取锁WAL 模式自动恢复还有一个精妙细节被强杀的、携带notify()通知的事务不会产生幽灵通知——回滚的 INSERT 从未离开 WAL新挂上的监听器看不到任何来自已死事务的消息test_sigkill_mid_honk_tx_delivers_no_notification。场景三Worker 收到任务但处理失败对于活着但处理出错的场景Worker 应显式调用job.retry(delay_s..., error...)。典型的 Worker 循环长这样完整示例见 packages/honker/examples/worker.pyasync for job in emails.claim(worker-1): try: await send_email(job.payload) job.ack() except Exception as e: job.retry(delay_s0, errorstr(e)) # 重试耗尽后自动进死信注意max_attempts同时约束主动重试和可见性超时回收——两条路径共享同一个重试预算任务不会从任何一个口子绕过死信机制。故障注入沉默失败是持久化库的最大罪honker 的测试哲学写在 tests/test_fault_injection.py 的注释里对持久化库来说最坏的结果是沉默失败——一个不报错就丢任务的队列或者卡在不可写 WAL 上的监听器。每个故障模式都必须抛出清晰、可向上传播的错误。项目实测的故障清单故障期望行为测试位置数据库文件损坏头信息被毁首次使用即抛出not a database类错误绝不伪装成空库test_corrupted_db_file_raises_on_first_use只读目录打开时明确报unable to opentest_readonly_directory_raises_clear_error只读 .db 文件首次写入报readonly database绝不静默丢弃test_readonly_db_file_raises_on_write父目录不存在立即报错不静默建目录也不挂起test_nonexistent_parent_dir_raises磁盘写满ENOSPC挂载 1MB tmpfs 后持续写入必须抛SQLITE_FULLtest_enqueue_on_full_filesystem_raises_disk_full最后一项尤其硬核测试在 Linux 上挂载一个只有 1MB 的 tmpfs把数据库放上去然后不断 enqueue 大负载直到磁盘写满断言第 N 次写入必须抛出可识别的错误——而不是挂起更不是静默吞掉任务。生产环境实践清单基于 honker 的语义你的 Worker 服务只需要记住这几点enqueue与业务数据放同一事务——INSERT INTO orders和queue.enqueue(...)同提交同回滚不存在双写不一致。认领任务前先想好可见性超时——visibility_timeout_s应大于任务最长执行时间否则慢任务会被其他 Worker 抢走。监控死信表——定期查询_honker_deadlast_error字段直接告诉你任务死因如max attempts exceeded。重启无需手工清理——崩溃的 Worker 不ack即可可见性超时会把它手中的任务自动收回队列。用文件型 SQLite别用:memory:——跨进程唤醒依赖PRAGMA data_version计数器变化内存库没有这条路径。总结honker 把Worker 崩溃从噩梦变成了确定性问题已提交的任务在文件里崩溃的写入随事务回滚超时未确认的任务自动回收重试耗尽的任务进入死信表可审计。这一切不是口头承诺——项目用真实子进程的SIGKILL、损坏的文件头、只读目录和 1MB 的满盘 tmpfs 逐一验证了每种故障下的行为测试入口见 tests/快速跑法为make test。如果你正在用 SQLite 做主存储并需要一个可靠的任务队列这套崩溃后依然正确的设计值得参考。更多用法可浏览 examples 目录 与 BINDINGS.md 中的各语言绑定支持矩阵。【免费下载链接】honkerSQLite extension bindings for Postgres NOTIFY/LISTEN semantics with durable queues, streams, pub/sub, and scheduler项目地址: https://gitcode.com/gh_mirrors/ho/honker创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考