Skip to content

状态物化 = 事件流到工作台的投影 ​

解决什么问题:事件驱动微服务如何"记住"业务状态—— 状态物化从不可变事件流重建可变状态,让"事件流是真理"和"服务有状态"兼得。

业务问题 ​

真实场景 ​

场景 1:订单服务的状态管理

问题:
  - 订单事件流(不可变):OrderCreated → OrderConfirmed → OrderShipped
  - 服务需要查询"当前订单状态"
  - 不能每次从头扫描事件流(性能差)
  
方案 A:物化到本地状态
  - 服务消费事件 → 更新本地 state store
  - 查询从本地读(快)
  - RocksDB 持久化
  
方案 B:物化到外部数据库
  - 服务消费事件 → 写 PostgreSQL
  - 查询从 DB 读
  - 共享给其他服务

Bellemare Ch 7 原话:

"Materialized state... A projection of events from the source event stream... State store... Where your service's business state is stored."

失败模式 ​

失败模式后果
每请求重读事件流性能差
状态与事件不一致数据混乱
全局状态驱动业务重复输出

控制论视角 ​

Ch 7 §7.5:"非线性效应" + Ch 15:"测量的累积形成状态"

状态物化 = Ch 15 测量的累积
  - 事件流 = 测量日志(不可变、连续)
  - 物化状态 = 当前测量值的快照(可变)
  - 状态 = 历史的累积(受控对象的"现状")
  
类比:
  - 事件流 = 时间序列信号
  - 状态 = 当前时刻的信号值
  - 物化 = 积分运算(累积 = 当前状态)

核心概念 ​

关键区分 ​

Materialized State(物化状态):
  - 事件流的投影(派生)
  - 只读(重建 = 重新消费事件)
  - 用于业务查询
  
State Store(状态存储):
  - 微服务的可变状态
  - 可读写(含中间计算)
  - 用于业务处理

内部 vs 外部状态存储 ​

维度InternalExternal
位置同一容器独立服务
性能高(本地访问)中(网络)
共享不可可
重启恢复重建或 changelog直接读
实现RocksDBPostgreSQL/Redis
适用单一服务多服务共享

Changelog(变更日志) ​

作用:
  - 状态变更的不可变日志
  - 持久化到 broker
  - 服务重启时从 changelog 重建状态
  - 避免重放整个输入流
  
存储优化:
  - changelog 可压缩(log compaction)
  - 只保留最新 key/value

Global State(全局状态) ​

特点:
  - 每个实例都有完整副本
  - 适合小、常用、少变的数据
  - 不适合驱动业务逻辑
  - 用于 dimension table(维度表)
  
例:
  - 国家代码表(所有服务都用)
  - 产品分类表
  - 不需要服务处理逻辑的查询数据

状态重建流程 ​

场景:服务实例失败/重启

步骤:
  1. 新实例启动
  2. 从 broker 读 changelog
  3. 应用到本地 state store
  4. 从最近 offset 继续消费输入流
  5. 持续更新 state store

工程实践 ​

实战 1:选择状态存储 ​

决策树:
  1. 状态需要跨服务共享?
     - 是 → External(PostgreSQL/Redis)
     - 否 → Internal(RocksDB)
  2. 性能要求极高?
     - 是 → Internal
  3. 状态大(GB+)?
     - 是 → External(避免内存压力)
  4. 服务重启频繁?
     - 是 → Internal + Changelog

实战 2:Kafka Streams + RocksDB ​

java
StreamsBuilder builder = new StreamsBuilder();
KTable<String, Order> orders = builder.table(
    "orders-topic",
    Materialized.<String, Order>as(
        Stores.persistentKeyValueStore("orders-store")
    )
);
// orders 是物化的状态
// 任何 KTable 操作都是基于物化状态

实战 3:避免常见错误 ​

错误 1:用 Global State 驱动业务
  - 每个实例都处理 → 重复输出
  - 用分区的状态驱动业务

错误 2:状态 = 全部事件
  - 大状态拖慢重启
  - 用 changelog + 压缩

错误 3:物化但不用
  - 物化 = CPU/内存成本
  - 真需要才物化

反模式 vs 正模式 ​

反模式正模式
每次从事件流查物化到状态存储
Global state 驱动业务用分区状态驱动
无 changelog启用 changelog
状态含全部历史只保留当前状态(log compaction)

性能指标 ​

指标衡量健康值
状态大小单实例状态大小< 10GB
重建时间服务重启到可用时间< 60s
状态查询延迟p99 查询时间< 10ms
Changelog 延迟状态变更 → changelog< 1s

边界判断 ​

何时用内部状态(RocksDB) ​

  • 单一服务使用
  • 高性能要求
  • 状态小到中等
  • 服务重启频繁

何时用外部状态(DB) ​

  • 多服务共享
  • 状态大
  • 重启不频繁
  • 需要 SQL 查询能力

何时不用状态物化 ​

  • 无状态处理
  • 简单过滤
  • 事件流足够快

何时升级 ​

当前升级到触发条件
无状态物化需要查询历史
外部 DB内部 RocksDB性能瓶颈
内部外部共享需求

关键洞察 ​

  1. 物化 = "用空间换时间"——不可变事件流保留,状态快速访问。
  2. Changelog 是状态的"备份"——服务重启不用重放整个流。
  3. Global State 只用于查询——驱动业务会产生重复输出。
  4. log compaction 是关键优化——changelog 不保留全部历史,只保留最新。
  5. 与 cybernetics 同构——Ch 7 §7.5 非线性系统状态 + Ch 15 测量累积 = 同一数学原型。

从名家方法论与工程化思路中蒸馏出自己的工程体系。