SQLite 项目要加消息队列?honker 让你不用再搭一套 Redis
用 SQLite 做主数据库的项目越来越多。项目做大了,难免需要消息队列、任务调度、事件推送这些功能。传统的做法是加一套 Redis,再配个 Celery 或者 RQ,问题倒是解决了,但麻烦也跟着来了。
你得多维护一个数据存储,备份策略要单独考虑,监控要单独部署。更要命的是双写问题:业务表写入成功、队列写入失败怎么办?两个系统的事务没法合并,只能自己在应用层兜底。这些都是实实在在的运维负担。
honker 的思路很直接:既然 SQLite 是主数据库,队列就应该放在同一个文件里。
它的做法是在 SQLite 上实现 Postgres 风格的 NOTIFY/LISTEN 语义。你可以在同一个事务里写入业务数据和推送任务,要么一起提交,要么一起回滚。队列就是一张表里的行,带个部分索引;任务就是一条 INSERT 语句。没有独立的代理进程,没有额外的数据存储,一个 .db 文件就是全部。
跨进程通知怎么实现?SQLite 没有网络协议,服务端推送是不可能的。honker 用的是 PRAGMA data_version 这个计数器——每次提交都会递增,跨进程可见。它每毫秒读一次这个计数器,发现变化就触发订阅者去查表。单数毫秒的响应延迟,没有应用层轮询的开销。
功能上,它提供了三套原语:临时的 pub/sub(notify)、持久化的消息流(stream)、至少执行一次的任务队列(queue)。任务队列支持重试、延迟执行、优先级、死信表;消息流支持每个消费者独立的 offset;调度器支持 crontab 语法的定时任务。这些都是建表 + SQL 函数实现的,任何能加载 SQLite 扩展的语言都能用。
语言绑定覆盖了主流选项:Python、Node.js、Rust、Go、Ruby、Bun、Elixir、C++。核心逻辑在 Rust 写的 SQLite 扩展里,各语言的包只是薄封装。这意味着 Python 写入的任务,Node.js 的 worker 可以直接认领;同一个数据库文件,不同语言的进程可以协同工作。
我总怀疑这类"把所有东西塞进一个数据库"的设计会不会有瓶颈。SQLite 本身就是单写入者,队列和业务数据争同一个写锁,吞吐量必然受影响。但 honker 的定位很清楚:单机、单写入者。如果你的应用已经跑在 SQLite 上,说明规模还没到需要分库的地步;真到了那一步,迁移到 Postgres 配合 pg-boss 或 Oban 是更成熟的选择。
一个细节值得提:它的唤醒机制是"宁可误触,不可漏掉"。任何提交都会唤醒所有订阅者,订阅者再根据 channel 过滤。这导致很多无效唤醒,但每次唤醒只是一次带索引的 SELECT,微秒级开销。比起漏消息的正确性问题,这点性能损耗是值得的。
SQLite 越来越适合作为交付项目的默认数据库。honker 补上了消息队列这块拼图。如果你的项目已经在用 SQLite,又需要任务队列或事件推送,不妨看看这个方案——至少不用再搭一套 Redis 了。

https://github.com/russellromney/honker
更多推荐

所有评论(0)