流处理器通过维护和更新持久数据结构来处理有状态操作,这些数据结构会跟踪跨传入事件的信息。与独立处理每个事件的无状态操作不同,有状态操作需要记住先前的数据点(如计数、聚合或会话历史记录)来计算结果。为此,流处理器使用内部存储机制(通常称为状态存储),这些机制与其处理逻辑紧密集成。例如,在计算时间窗口内的运行平均值时,处理器会存储中间的总和与计数,并在新事件到达且旧事件过期时更新它们。状态通常被分区并分布在处理节点上,以实现可伸缩性。
一种常见的方法是采用键控状态,其中数据按特定属性(例如,用户 ID)进行分组,以确保相关事件一起处理。例如,在 Apache Flink 中,流中的每个唯一键都有其自己的专用状态,从而允许进行每用户会话跟踪等操作。状态使用内存哈希映射或嵌入式数据库(如 RocksDB)等后端进行管理,这些后端可以高效地处理大型数据集。窗口化是另一种有状态操作:为了计算固定间隔(例如,每小时销售总额)的结果,处理器会保留事件,直到窗口关闭,然后触发计算并丢弃或存档状态。一些系统还支持操作符状态,它不与键绑定,而是与处理管道中的特定步骤相关联,例如为批量写入缓冲记录。
容错性对于有状态处理至关重要。流处理器使用检查点(状态的周期性快照)和预写日志(在应用更新之前记录更新)等技术来从故障中恢复。例如,Kafka Streams 会将状态更改持久化到 Kafka 主题中,以便在节点崩溃时进行重处理。此外,有状态操作必须处理乱序或延迟的事件。像 Flink 这样的系统使用水位线来跟踪事件时间进度,确保即使数据延迟到达,窗口也能正确关闭。开发人员还必须管理状态大小——时间存活 (TTL) 设置等工具会自动使陈旧数据过期,防止内存使用无限增长。这些机制确保有状态操作在生产环境中保持可靠和可伸缩。