在分布式系统中,实现 durable execution(持久化执行)常需复杂引擎如 Temporal 或 Cadence,但对于中小规模任务队列,SQLite 的 Outbox Pattern 结合 WAL(Write-Ahead Logging)模式提供廉价、高可靠方案。该模式将业务变更与事件写入同一事务的 outbox 表,利用 WAL 持久化确保崩溃恢复,并通过 Debezium CDC 无缝流式投递到 Kafka,实现 exactly-once 语义。
Outbox Pattern 在 SQLite 中的核心设计
Outbox Pattern 解决 “双写不一致” 问题:业务服务在本地事务中同时写入业务表和 outbox 表,后者存储待发布事件。SQLite 天然支持此模式,因其 ACID 事务保证原子性。
典型 outbox 表 schema:
CREATE TABLE IF NOT EXISTS outbox_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
aggregate_type TEXT NOT NULL,
aggregate_id TEXT NOT NULL,
event_type TEXT NOT NULL,
payload JSON NOT NULL, -- 事件负载,序列化为 JSON
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP NULL
);
CREATE INDEX idx_outbox_aggregate ON outbox_events(aggregate_type, aggregate_id);
CREATE INDEX idx_outbox_created ON outbox_events(created_at);
业务事务示例(伪代码):
BEGIN TRANSACTION;
-- 业务变更,如订单创建
INSERT INTO orders (id, status) VALUES ('order-123', 'created');
-- 写入 outbox
INSERT INTO outbox_events (aggregate_type, aggregate_id, event_type, payload)
VALUES ('Order', 'order-123', 'OrderCreated', '{"id": "order-123", "status": "created"}');
COMMIT;
崩溃时,事务回滚确保业务与事件一致;成功提交后,事件持久化在 outbox。
WAL 模式:持久化与并发保障
默认 rollback journal 下,SQLite 写阻塞读,不适高并发任务队列。启用 WAL 模式(PRAGMA journal_mode=WAL;)将变更追加到 -wal 文件,支持读写并发:读从主文件 + wal 合并,写仅追加 wal。
关键 PRAGMA 配置:
PRAGMA journal_mode = WAL;// 启用 WALPRAGMA synchronous = NORMAL;// 平衡耐久性与性能,崩溃可能丢最近事务PRAGMA wal_autocheckpoint = 1000;//wal 文件达 1000 页(默认~1MB)自动 checkpointPRAGMA busy_timeout = 5000;// 锁冲突等待 5sPRAGMA cache_size = 10000;// 增大缓存,提升吞吐
WAL 确保耐崩溃:wal 文件 fsync 后事务持久,重启自动 replay 未 checkpoint 变更。测试显示,WAL 下写吞吐提升 2-4 倍,读写并发无阻塞。
Checkpoint 机制:
- 被动:wal 满时当前 COMMIT 线程执行。
- 主动:闲时调用
PRAGMA wal_checkpoint(FULL);,回写 wal 到主文件,truncate wal。 监控 wal 文件大小(<100MB),若过大,手动 FULL checkpoint。
Debezium CDC:Exactly-Once 事件流消费
轮询 outbox 易积压、延迟高。集成 Debezium(支持 SQLite connector)捕获 outbox 变更,实时流式投递到 Kafka。
Debezium SQLite connector 配置(docker-compose 示例):
debezium:
image: debezium/connect:2.5
depends_on: [kafka, sqlite-db]
environment:
BOOTSTRAP_SERVERS: kafka:9092
GROUP_ID: 1
CONFIG_STORAGE_TOPIC: dbserver1-config
OFFSET_STORAGE_TOPIC: dbserver1-offset
STATUS_STORAGE_TOPIC: dbserver1-status
command: ["connect-standalone", "/etc/kafka-connect-sqlite.properties"]
kafka-connect-sqlite.properties:
connector.class=io.debezium.connector.sqlite.SqliteConnector
database.hostname=sqlite-db
database.path=/var/lib/sqlite/tasks.db
database.history.kafka.bootstrap.servers=kafka:9092
database.history.kafka.topic=dbhistory.tasks
table.include.list=outbox_events
transforms=unwrap
transforms.unwrap.type=io.debezium.transforms.ExtractNewRecordState
transforms.unwrap.drop.tombstones=true
Debezium 订阅 binlog/wal,捕获 INSERT 到 outbox,转换为 Kafka 事件。消费端 Kafka Streams / 消费者处理后,更新 processed_at(幂等)。
Exactly-Once:Debezium 事务边界 + Kafka exactly-once + 消费幂等(unique aggregate_id)。
Crash 恢复与监控参数
崩溃恢复流程:
- SQLite 重启:自动从 wal replay 未 checkpoint 事务。
- Debezium 重连:从 offset 恢复,replay outbox 变更。
- 任务队列:未 processed 事件重发,确保不丢。
落地参数清单:
- 吞吐阈值:SQLite WAL 适 <1k TPS,高吞吐迁 Postgres。
- Checkpoint 频率:每 5min 或 wal>50MB 执行 FULL。
- 监控指标:
指标 阈值 告警 wal 文件大小 <100MB >200MB Checkpoint 滞后 <1h >2h Outbox 未处理数 <1k >10k Debezium 滞后 <10s >1min
回滚策略:若 wal 膨胀,PRAGMA journal_mode=DELETE; 切换回默认。
实战风险与优化
风险:SQLite 单写者锁,wal 共享内存限单机;Debezium SQLite connector 实验性,生产慎用(fallback 轮询 publisher)。
优化:分区 outbox(按 aggregate_type),结合 Watermill 等库简化 pub/sub。
此方案成本低、无运维,适 edge computing 或单机 durable queue。
资料来源:
- Gunnar Morling: Building a Durable Execution Engine With SQLite (morling.dev)
- Debezium SQLite Connector Docs
- SQLite WAL Mode (sqlite.org/wal.html)