欢迎光临
我们一直在努力

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

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


一、 CRUD 的原罪:毁灭证据

想象一个温控器。

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

传统做法 (Snapshot)

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

当用户修改温度时:

  • 读取整个结构体到 RAM。

  • 修改温度字段。

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

  • 写入 新结构体。

  • 后果:

    • 真相消失:如果设备报警了,你查日志只能看到“当前设定是 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 的另一个区域生成一份 “当前状态快照”。

    启动流程优化:

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

  • 找到快照之后发生的 增量事件,重放它们。(恢复 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 当作账本,而不是草稿纸。这就是高可靠存储的秘诀。

    赞(0)
    未经允许不得转载:171主机测评 » 【架构心法】给系统装上“时光机”:事件溯源 (Event Sourcing) 与 CQRS 在嵌入式存储中的降维应用
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址