ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Siberite核心实现剖析:LevelDB与8字节uint64密钥如何设计轻量级持久化FIFO消息队列

Siberite核心实现剖析:LevelDB与8字节uint64密钥如何设计轻量级持久化FIFO消息队列 Siberite核心实现剖析LevelDB与8字节uint64密钥如何设计轻量级持久化FIFO消息队列【免费下载链接】siberiteSiberite is a simple, lightweight, leveldb backed message queue written in Go.项目地址: https://gitcode.com/gh_mirrors/si/siberiteSiberite 是一个用 Go 编写的简单、轻量级消息队列服务器它基于 LevelDBgoleveldb实现持久化存储将每条消息映射到一个固定 8 字节的 uint64 大端序密钥上从而用键值数据库精巧地构建出严格有序的持久化 FIFO 队列。即使队列规模远超内存容量Siberite 也能保持极低的常驻内存占用。 为什么选择 LevelDB 作为消息队列的底座传统的内存型队列服务器如 Redis在队列增长时会不断吞噬内存而企业级服务器如 RabbitMQ则偏重复杂。Siberite 走了第三条路像它的鼻祖 Kestrel 一样把所有消息都放到进程外交给 goleveldb 这个嵌入式 LevelDB 实现来持久化存储。这样做带来的直接好处是✅队列可以比内存大得多消息落在磁盘上内存只承担读写缓冲✅进程崩溃不丢数据重启后从 LevelDB 中恢复✅常驻内存几乎不随队列规模增长每个队列就是一个独立的 LevelDB 数据库目录由 repository/repository.go 中的QueueRepository统一管理服务启动时扫描数据目录发现一个子目录就打开一个队列。 核心设计8字节 uint64 密钥如何编码 FIFO 顺序这是 Siberite 最聪明的一笔。在 queue/queue.go 的dbKey方法中每条消息的键不是随机字符串而是一个固定 8 字节、大端序编码的自增 ID消息入队时写入键为tail 1的 8 字节大端 uint64消息出队时读取并删除head 1对应的键LevelDB 本身按键的字典序排序而大端序 uint64 的字典序恰好与其数值大小完全一致。于是按键序遍历 等价于 按入队时间遍历——一条严格有序的 FIFO 队列就这样从键值存储中自然生长出来不需要任何额外的链表或排序结构。队列结构体只维护两个状态head已消费到的位置和tail最新写入位置队列长度就是tail - head。这个设计带来几个优雅的性质操作实现方式源码位置入队 Enqueue写入dbKey(tail1)tail 自增queue/queue.go出队 GetNext读取并删除dbKey(head1)head 自增queue/queue.go放回 PutBack重写dbKey(head)head 自减queue/queue.go恢复 Head/Tail打开数据库后取最小键与最大键queue/queue.go注意initialize方法服务重启时只需iter.First()和iter.Last()各取一次键就能把head和tail精确恢复现场——持久化元信息不需要单独的元数据文件因为顺序本身就编码在键里。固定 8 字节键还有两个工程上的好处键长恒定没有变长编码的歧义与比较开销ID 用uint64表示单队列理论容量超过 180 亿条消息足够绝大多数场景。 消费组用前缀命名空间复用同一个源队列Siberite 的另一个亮点是持久化游标。客户端可以用get queue.cursor_name的语法从同一个源队列里多次消费且不会删除源队列中的消息。实现上每个队列目录下除了消息数据库还有一个_.metadata元数据库见 cgroup/cgqueue.go。消费组利用 LevelDB 的键前缀命名空间共存于同一个数据库_c:组名存储该组的游标位置同样用 8 字节大端 uint64 编码见 cgroup/cgroup.go_r:组名:存储该组未确认的两阶段可靠读取失败消息消费时优先消化失败队列再按游标从源队列取下一条消息。如果游标落后于源队列的 head即老消息已被主消费路径删除游标会自动跳到当前队头重新读取——这让多消费者并行消费同一个队列变得简单又安全。 性能实测队列越大内存越平官方基准测试docs/benchmarks.md对比了 Kestrel、Darner 与 Siberite 三项指标。常驻内存队列膨胀到 52 万条消息时Kestrel 的常驻内存冲到 84 万 KB而 Siberite 只用了约 9.4 万 KB且增长曲线极其平缓。这正是消息全部放进程外架构的红利。洪峰吞吐在 10 个队列上并发灌入消息的 flood 测试中Siberite 在 50~100 连接时达到约 7.4 万 requests/s 的峰值是三者中最高的积压消化当积压消息超出内存后Siberite 的吞吐只是平缓下降而非断崖式跌落说明大量删除操作并没有拖垮 LevelDB 的读取性能详见 docs/benchmarks.md 中 64 字节与 1024 字节消息的 packing/unpacking 曲线。 快速上手三步跑起你的持久化消息队列Siberite 使用与 Kestrel 相同的 memcache TCP 文本协议默认端口 22133任何兼容 memcached 协议的客户端都能直接使用支持客户端清单见 docs/clients.md。构建确保GOPATH正确后执行go get ./...再go build siberite.go入口见 siberite.go启动./siberite -listen localhost:22133 -data ./data体验用telnet localhost 22133发送set work 0 0 10写入消息get work消费消息get work.open / close体验两阶段可靠读取stats查看队列状态常用命令速查set queue写入消息set ab扇出到多个队列get queue消费并删除get queue/peek只看不取get queue.cursor以持久游标方式消费flush queue清空队列delete queue删除队列 代码导览清单想深入源码建议按数据流从下往上阅读queue/queue.go最核心的持久化 FIFO 队列8 字节密钥与 head/tail 状态机cgroup/cgroup.go 与 cgroup/cgmanager.go消费组、持久游标与失败读取队列repository/repository.go队列仓库启动时自动恢复所有队列controller/controller.gomemcache 协议解析与命令分发service/service.goTCP 服务入口queue/queue_test.go 与 cgroup/cgqueue_test.go理解设计意图的活文档总结Siberite 的核心实现堪称少即是多的典范它没有发明任何新结构只用一个固定 8 字节的大端 uint64 密钥就让 LevelDB 天然的键序变成了 FIFO 顺序再用 head/tail 两个计数器完成了入队、出队、放回与崩溃恢复。配合前缀命名空间的消费组与持久游标它成为一款内存占用极低、却能承载超内存规模积压的持久化消息队列服务器——如果你需要队列比内存还大的轻量方案值得放进技术清单。【免费下载链接】siberiteSiberite is a simple, lightweight, leveldb backed message queue written in Go.项目地址: https://gitcode.com/gh_mirrors/si/siberite创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表