4. In-Memory State Store:内存状态存储
内存状态存储,说白了就是把数据放在 JVM 堆内存里。没有磁盘写入,没有序列化开销,速度就是它的王牌。但代价呢?你猜得到——进程一挂,数据全丢。
我个人习惯把这种 Store 叫做“快但脆”。它适合的场景其实很明确:要么数据丢了无所谓,要么你有可靠的冷备方案兜底。今天我们就把它掰开揉碎聊清楚。
4.1 核心原理
Kafka Streams 里的 In-Memory State Store,底层就是一个 ConcurrentHashMap 的变体。它实现了 KeyValueStore 接口,但所有数据只存在内存里。
我截一段简化后的核心逻辑,你一看就明白:
public class InMemoryKeyValueStore<K, V> implements KeyValueStore<K, V> {
private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
private final String name;
public InMemoryKeyValueStore(String name) {
this.name = name;
}
@Override
public V get(K key) {
return map.get(key);
}
@Override
public void put(K key, V value) {
map.put(key, value);
}
@Override
public KeyValueIterator<K, V> all() {
// 返回一个基于内存快照的迭代器
return new InMemoryKeyValueIterator(map.entrySet().iterator());
}
// ... 其他方法类似
}
看到没?没有文件 I/O,没有序列化反序列化。读写就是一次哈希表操作。这也是为什么它的延迟能低到微秒级。
关键点:In-Memory Store 不参与 Kafka Streams 的日志机制。它没有 changelog topic,也不做写前日志(WAL)。这意味着——重启后,状态完全丢失。
4.2 适用场景
我这些年见过不少团队在错误的地方用了内存存储,结果线上出事故。这里我列几个真正适合的场景:
- 临时计算中间结果:比如窗口聚合里的中间状态,窗口结束后数据就没用了。
- 可重建的缓存:数据可以从原始 topic 重新消费恢复,丢失几秒数据业务能接受。
- 测试环境:单元测试和集成测试里,用内存存储跑得飞快,不用清理磁盘。
- 只读参考数据:从外部加载的配置表,重启后重新加载就行。
举个例子。我之前做过一个实时风控系统,规则引擎需要缓存用户最近 5 分钟的行为计数。窗口一过期,计数就没意义了。这种场景用 In-Memory Store 再合适不过。
我的建议:如果你能回答“进程重启后,状态从哪来?”这个问题,并且答案不是“从天上掉下来”,那就可以考虑用 In-Memory Store。
4.3 优缺点分析
咱们直接上表格,一目了然:
| 维度 | 优点 | 缺点 |
|---|---|---|
| 读写性能 | 微秒级延迟,无磁盘 I/O | 受 GC 影响,大对象频繁创建时会有停顿 |
| 数据持久性 | — | 进程崩溃或重启后数据全部丢失 |
| 内存占用 | 无磁盘空间限制 | 受 JVM 堆大小限制,OOM 风险高 |
| 运维复杂度 | 零配置,开箱即用 | 无法做数据备份和恢复 |
| 适用数据量 | 适合小数据量(GB 级别以下) | 大数据量下 GC 压力大,容易 Full GC |
嗯,这里要注意一个容易被忽略的点:内存存储的“快”是有前提的。当你的数据量逼近堆内存上限时,GC 会成为瓶颈。我曾经见过一个案例,某团队把 50GB 数据塞进 16GB 堆里,结果每分钟一次 Full GC,吞吐量直接归零。
4.4 避坑指南
我踩过的坑,希望你别再踩一遍:
- 不要在生产环境做“无备份”的内存存储。如果你必须用,至少确保上游 topic 有足够长的保留期,方便重建。
- 监控堆内存使用率。设置告警,当堆使用超过 70% 时就要警惕了。
- 避免存储大对象。比如把整个 JSON 字符串当 value 存,这会加剧 GC 压力。
- 注意迭代器使用。In-Memory Store 的
all()返回的是当前内存快照的迭代器,如果你在迭代过程中修改数据,行为是未定义的。
我曾经犯过一个低级错误:在内存存储里保存了未序列化的 Protobuf 对象。结果对象图太复杂,GC 根节点扫描耗时飙升。后来改成存 byte[],问题解决。记住:存什么格式,直接影响 GC 表现。
4.5 核心架构图
下面这张图展示了 In-Memory State Store 在 Kafka Streams 任务中的位置和数据流向:
从图里你能看到,In-Memory Store 完全活在 JVM 堆里。它和外部系统没有数据交换通道。一旦进程崩溃,那个红色方块里的数据就彻底消失了。
4.6 何时该选它?
我总结了一个简单的决策树:
- 数据丢了会怎样?—— 如果答案是“重新算一下就行”,继续往下看。
- 数据量多大?—— 如果小于 JVM 堆的 60%,可以考虑。
- 延迟要求多高?—— 如果要求 P99 延迟 < 5ms,In-Memory Store 是首选。
- 有备份方案吗?—— 比如上游 topic 保留 7 天数据,方便重建。
如果以上四条都满足,那 In-Memory State Store 就是你的菜。否则,我建议你看看后面几章要讲的持久化存储方案。
一个小技巧:如果你不确定该不该用内存存储,先在测试环境压一把。把数据量压到生产预估的 1.5 倍,观察 GC 频率和停顿时间。这个数据比任何理论分析都靠谱。