【架构心法】给系统装上“时光机”:事件溯源 (Event Sourcing) 与 CQRS 在嵌入式存储中的降维应用

摘要 :大多数嵌入式设备只保存"当前状态",一旦发生故障,现场就像被破坏的犯罪现场,毫无线索。本文将挑战传统的"覆盖式存储",提出 "追加式日志 (Append-Only Log)" 的架构哲学。通过将每一次参数修改记录为不可变的 事件 (Event) ,利用 CQRS 分离写入与读取,我们不仅能实现毫秒级的参数保存,还能天然获得 Flash 磨损均衡 (Wear Leveling)全量审计日志 (Audit Log),让系统拥有回滚历史的能力。


一、 CRUD 的原罪:毁灭证据

想象一个温控器。

用户设定温度为 20℃ -> 系统运行 -> 用户改为 25℃ -> 系统报警。

传统做法 (Snapshot)

我们在 Flash 里分配一个扇区,专门存结构体 Config_t

当用户修改温度时:

  1. 读取整个结构体到 RAM。

  2. 修改温度字段。

  3. 擦除 Flash 扇区(最慢,最危险)。

  4. 写入 新结构体。

后果

  • 真相消失:如果设备报警了,你查日志只能看到"当前设定是 25℃"。你根本不知道是用户刚刚改的,还是系统自己乱跳的。

  • 写崩风险:如果在第 3 步和第 4 步之间断电,配置全丢,设备变砖。

  • 寿命磨损:哪怕只改 1 个字节,也要擦除整个 4KB 扇区。


二、 事件溯源 (Event Sourcing):一切皆历史

核心哲学:状态 (State) 不是存储出来的,而是由历史事件 (Events) 计算出来的。

我们不再存储"当前温度",我们存储"设定变更"这个动作。

1. Flash 布局:环形日志 (Circular Journal)

我们将 Flash 看作一个无限长的磁带(环形缓冲区)。

每次修改参数,我们不擦除旧数据,而是在后面追加 (Append) 一条新记录。

2. 事件结构

3. 写操作 (Command)

用户设定 25℃:

  • 检查 Flash 当前写指针位置。

  • 写入一条 EVT_SET_TEMP, 250

  • 耗时:微秒级(无需擦除)。

  • 原子性:写入完成前掉电,CRC 错误,该条记录无效,系统自动回滚到上一条。

4. 读操作 (Query) - 重放 (Replay)

系统上电时,RAM 是空的。

Bootloader 从头到尾扫描一遍 Flash 里的所有事件:

  • 遇到 EVT_SET_TEMP 20 -> RAM 里的 Temp = 20

  • 遇到 EVT_SET_TEMP 25 -> RAM 里的 Temp = 25

    重放结束,RAM 里就是最新的状态。


三、 CQRS:读写分离的艺术

在上述模型中,写很快(追加),但读很慢(要扫描整个 Flash)。

这就是 CQRS (Command Query Responsibility Segregation) 解决的问题。

1. Command Side (写端)

负责产生事件,写入 Flash。

只管写,不管查。

这完美契合 Flash 的物理特性:编程 (Program) 很快,擦除 (Erase) 很慢。 我们只编程,不擦除,直到写满整个扇区。

2. Query Side (读端)

负责提供数据查询。

我们在 RAM 里维护一个 Config_Snapshot

  • 写发生时:Command 更新 Flash,同时更新 RAM 快照。

  • 读发生时:直接读 RAM 快照。O(1) 复杂度。

架构优势

我们将"Flash 的磨损"和"复杂的查询逻辑"彻底解耦了。


四、 快照 (Snapshot):启动速度的优化

如果设备运行了 10 年,积累了 100 万条事件,每次上电都要重放一遍,启动岂不是要几分钟?

解决方案:定期快照。

每当积累了 N 条事件(或者 Flash 写满一个 Block),系统自动在 Flash 的另一个区域生成一份 "当前状态快照"

启动流程优化

  1. 找到最新的 快照,加载进 RAM。(恢复 99% 的状态)

  2. 找到快照之后发生的 增量事件,重放它们。(恢复 1% 的状态)

这就像 Redis 的 AOF (Append Only File) 和 RDB (Snapshot) 机制。嵌入式系统完全可以借鉴数据库的智慧。


五、 收益:不仅是保存参数

这套架构极其复杂,值得吗?

当你遇到以下场景时,你会感谢这个架构。

1. 磨损均衡 (Wear Leveling)

传统 FS 需要复杂的算法来平衡磨损。

事件溯源天然就是顺序写入的。写满 Sector 0,写 Sector 1... 写满 Sector N,擦除 Sector 0。

Flash 的每个单元被擦写的次数是严格均匀的。 你不需要写任何额外的磨损均衡代码。

2. 甚至可以"撤销" (Undo)

用户误操作把参数改乱了?

只需在系统里加一个命令:Rollback

逻辑是:写入一条"反向事件",或者在重放时忽略最后 N 条事件。

这在工业现场调试时是神级功能。

3. 黑匣子审计 (Auditing)

设备坏了,客户赖账说是设备问题。

你把 Flash 里的事件导出来:

[2024-02-09 10:00:00] User set voltage to 220V

[2024-02-09 10:00:05] User set voltage to 380V

[2024-02-09 10:00:06] Over-voltage Error triggered

证据确凿。 每一条操作都留下了不可磨灭的痕迹。


六、 结语:从"静态照片"到"动态电影"

传统的 CRUD 架构,像是在给系统拍照片。每次修改,我们都撕掉旧照片,贴上新照片。我们只拥有"现在"。

事件溯源架构,像是在拍电影。每一帧(事件)都被永久记录下来。我们拥有了"过去"和"现在",甚至可以预演"未来"。

在资源受限的嵌入式系统里,利用 Log-Structured 的存储方式,我们不仅克服了 Flash 的物理缺陷(擦除慢、寿命短),更赋予了设备 可追溯、可回滚、可审计 的高级特性。

把 Flash 当作账本,而不是草稿纸。这就是高可靠存储的秘诀。

相关推荐
春日见10 小时前
拉取与合并:如何让个人分支既包含你昨天的修改,也包含 develop 最新更新
大数据·人工智能·深度学习·elasticsearch·搜索引擎
青云计划11 小时前
知光项目知文发布模块
java·后端·spring·mybatis
赶路人儿11 小时前
Jsoniter(java版本)使用介绍
java·开发语言
探路者继续奋斗12 小时前
IDD意图驱动开发之意图规格说明书
java·规格说明书·开发规范·意图驱动开发·idd
Elastic 中国社区官方博客12 小时前
如何防御你的 RAG 系统免受上下文投毒攻击
大数据·运维·人工智能·elasticsearch·搜索引擎·ai·全文检索
消失的旧时光-194312 小时前
第十九课:为什么要引入消息队列?——异步系统设计思想
java·开发语言
A懿轩A13 小时前
【Java 基础编程】Java 面向对象入门:类与对象、构造器、this 关键字,小白也能写 OOP
java·开发语言
乐观勇敢坚强的老彭13 小时前
c++寒假营day03
java·开发语言·c++
biubiubiu070613 小时前
谷歌浏览器无法访问localhost:8080
java