快照持久化策略
消息中间件的数据是时实变化的,如果保存行为数据,量大且费性能。所以,快照是比较好的方案。FolkMQ 的架构简单、数据结构少,非常适合快照。
快照只保存最新的全量状态,不留历史数据。
1、FolkMQ 的快照持久化时会做些什么?
快照采用逐行写入,逐行读取的方式;每一行为一条完整数据的 Json 格式。
- 全量保存“订阅关系”为
subscribe-map.fdb
。即主题的订阅关系映射,内容示例:
{"topic":"...","queues":["...","..."]}
{"topic":"...","queues":["..."]}
- 全量保持每个“消费队列的数据”为
{topic}/{consumerGroup}.fdb
。内容示例:
{"meta":"...","data":"..."}
{"meta":"...","data":"..."}
2、是怎么实现的?
FolkMQ 开放有 MqWatcher 接口,它可以观察 MqServer 所有的行为及相关数据。持久化是基于 MqWatcher 接口实现的。
- 恢复快照
在 onStartBefore 时,同步恢复订阅关系;在 onStartAfter 时,异步恢复消费队列的数据。
- 持久化快照
在 onStopAfter 时,会触发 onSave;在 onSave 时,会保存“订阅关系”和每个“消费队列的数据”。
3、什么时机会触发?
a) 停止时触发
在服务停止时,通过 Runtime.getRuntime().addShutdownHook
触发持久化。如果突然停电、强制杀进程之类的特殊情况,此策略会失效。
b) 手动触发
在服务端控制台,有菜单可手动触发。如果明确知道会停电、或特殊维护,可以手动触发。心情不好也可以点一下。
c) 运行时自动触发
可以通过环境变量(方便 docker)或系统属性进行调整:
folkmq.snapshot:
enable: true
save900: 1
save300: 10
save100: 10000
- 每 100 秒内,如果有 10000 个消息变化则触发
- 每 300 秒内,如果有 10 个消息变化则触发
- 每 900 秒内,如果有 1 个消息变化则触发
4、怎么保证可靠性?
同一份数据会存在三个文件(不保存历史数据):临时写文件(.fdb.tmp
),快照文件(.fdb
),备份文件(.fdb.bak
)。写过程为:
- 同步锁开始
- 创建
*.fdb.tmp
文件,并写入数据 - 如果顺利完成,删除
*.fdb.bak
文件 - 如果顺利完成,将
*.fdb
文件更名为*.fdb.bak
- 如果顺利完成,将
*.fdb.tmp
文件更名为*.fdb
- 同步锁结束