大多数开发者已经习惯了无状态服务的理念,倾向于将所有数据存放在远端数据库中,难以理解流式计算中为何需要「局部状态」的存在。此文将阐述流计算中「局部状态」的含义、动机、适用场景和优劣势。
什么是状态?
想象你在使用 SQL 执行一些操作。
如果所有请求都只需要操作单行数据(如使用主键ID执行基本的 select 检索操作),那么此服务对数据的依赖可以称之为是「无状态」的。
然而现实场景中往往存在各类聚合(aggregation)、联合(join)操作。
聚合操作如:在某段时间窗口内,对若干页面的广告点击率(CTR,click-through rate)进行统计。
流连接(streaming join)操作如:广告展示(ad impression)数据流同广告点击率数据流两者存在时间先后顺序,但又是强关联的,需进行流连接操作。
字段填充(enrichment)操作如:给仅包含用户ID的广告点击率数据流,补充更详细的用户属性值,以便下游分析系统处理。
远程状态
一种常见的处理模式是,从输入流中依次获取记录,对每条记录执行若干次针对远程分布式数据库的请求。

如上图可见,数据流被分派至多个机器上的多个处理单元(processor)上进行处理,每个处理单元都会发起对远端的分布式数据库的请求;由于分布式数据库的特性,这些请求最终落在位于不同机器的各个数据库分区(database partition)上。另一种可能的做法是,将处理单元和数据库分区「绑起来」(co-located),让请求不用兜一个大圈子、而是直接在本机上进行处理,以提速数据流的处理。这种做法中,同处理单元绑定的数据存储分区,我们称之为「局部状态」(local state)。
局部状态
满足如下条件的,都可以称之为局部状态。
同处理单元位于同一台机器上。
处理单元可根据输入数据流,查询/修改其中的状态数据。
存储于内存或者磁盘中。
在阐述局部状态的好处前,我们先尝试回答一个显而易见的问题:局部状态如何做到高可用,如果机器挂了怎么办?
容错机制
Samza 对于局部状态提供的容错机制是,将局部状态的变更(local state changes)建模成一种提交日志/变更日志(commit/change log),从而利用提交日志的可重放(replay)特性来保障容错。

处理单元执行变更状态时,将变更日志写入 Kafka topic 中。如果机器挂掉,那么新启动的处理单元从上述 Kafka topic 中回放变更日志,从而重建机器挂掉前的局部状态。利用 Kafka 周期性的日志压缩(log compaction)操作,能够将日志量控制在合理的大小、而不会随着时间日益
