HQY

×

Honker:把队列、流、Pub/Sub 和定时调度,装进一个 SQLite 文件

hqy hqy 发表于2026-05-22 17:11:32 浏览10 评论0

抢沙发发表评论

如果你的应用已经把 SQLite 当作主存储,那么任务队列是否也应该和数据“共存”在同一个文件里?

honker 为 SQLite 增加了类似 PostgreSQL 的 NOTIFY/LISTEN 语义,同时提供持久化的 pub/sub、任务队列和事件流功能,而且不需要客户端轮询,也不需要独立的守护进程或消息中间件(broker)。在 M 系列芯片的笔记本上,跨进程唤醒延迟 p50 约为 0.7 毫秒。

图片

在最基础的形式下,它只是一个普通的 SQLite 可加载扩展,因此任何支持 SELECT load_extension('honker_ext') 的语言,都可以在同一个数据库文件上使用相同的队列、流和通知机制。Python、Node、Rust、Go、Ruby、Bun 和 Elixir 的绑定共享同一种磁盘数据格式。

SQLite 已经在真实生产环境中承担关键业务,例如 Bluesky 的 PDS、Fly 的 LiteFS、Turso。一旦真实业务流进入基于 SQLite 的应用,就必然需要队列系统。传统方案通常是“加 Redis + Celery”。这种方式是可行的,但会引入第二套数据存储系统及其备份方案,还会带来业务表与队列之间的双写问题,以及运行消息中间件的运维成本。

honker 的思路是:如果 SQLite 是主存储,那么队列也应该存在于同一个文件中。

这意味着 INSERT INTO orders 和 queue.enqueue(...) 可以在同一个事务中提交;如果事务回滚,两者都会一起撤销。队列本质上只是由表和部分索引组成的数据行。

一句话理解:

Honker = SQLite + 内建队列系统(无外部依赖)

一个例子

在一次原子事务中,同时完成业务写入与任务入队,然后进行消费。所有操作都在同一个 .db 文件中完成,采用统一的磁盘格式,并可在七种语言中使用。

import honker

db = honker.open("app.db")
q = db.queue("emails")

# 在同一个事务中:业务写入 + 入队

with
 db.transaction() as tx:
    tx.execute("INSERT INTO orders (id, total) VALUES (?, ?)", [42, 99])
    q.enqueue({"to": "wujm_xa@qq.com", "order_id": 42}, tx=tx)

# worker 在数据库任何提交发生时被唤醒,无需轮询

async
 for job in q.claim("worker-1"):
    await
 send_email(job.payload)
    job.ack()

或者使用类似 Huey 的装饰器方式:

@q.task(retries=3, timeout_s=30)
def
 send_email(to, subject):
    ...
    return
 {"sent_at": time.time()}

r = send_email("wujm_xa@qq.com", "Hi")   # 入队,返回 TaskResult
print
(r.get(timeout=10))                    # 阻塞直到 worker 执行完成

工作原理

honker 每隔 1 毫秒轮询一次 SQLite 的 PRAGMA data_version。这是一个单调递增计数器,SQLite 会在每次来自任意连接、任意日志模式或任意进程的提交发生时对其加一——读取它大约只需要 3 微秒,却可以作为一个非常精确的“唤醒信号”。

后台线程会把这个 tick 分发给所有订阅者,然后每个订阅者执行:

SELECT ... WHERE id > last_seen

并返回新的数据行。对于每个数据库而言,无论订阅者数量多少,都只需要一个轮询线程。


在空闲状态下的成本是:每个数据库每毫秒执行一次轻量级 SELECT。但不会带来 page-cache 压力,不会造成写锁竞争,也不依赖操作系统的文件监听机制。由于“唤醒信号”是一次共享轮询,而不是为每个监听器单独执行查询,所以监听器数量可以自由扩展。


队列、数据流以及 pub/sub 原语,本质上都是扩展所管理表中的 INSERT 操作。

在你的业务事务中调用:

queue.enqueue(payload, tx=tx)

意味着这个 job 行与前面的:

INSERT INTO orders

处于同一个 ACID 事务中。如果事务回滚,job 也会一并被撤销。

相关工作

pg_notify[1] 能做跨进程的快速触发,但它不负责重试,也没有任务可见性这类能力。

Huey[2] 是基于 SQLite 的 Python 任务队列,honker 主要参考的就是它的思路。

pg-boss[3] 和 Oban[4] 则是 Postgres 生态里比较成熟的队列方案。

如果你本来就已经在用 Postgres,那直接用这些就可以了。

引用链接

[1] https://www.postgresql.org/docs/current/sql-notify.html
[2] https://github.com/coleifer/huey
[3] https://github.com/timgit/pg-boss
[4] https://github.com/sorentwo/oban


打赏

本文链接:https://kinber.cn/post/6562.html 转载需授权!

分享到:


推荐本站淘宝优惠价购买喜欢的宝贝:

image.png

 您阅读本篇文章共花了: 

群贤毕至

访客