​ 在Flink中,**键控状态(Keyed State)算子状态(Operator State)**是两种重要的状态管理机制,它们各自用于不同的场景,并结合Flink的容错机制(如 CheckpointSavepoint)确保系统在故障发生时能够恢复。

1. 键控状态(Keyed State)

​ 键控状态与输入流的每个键(key)相关联。它通常用于有状态的操作(如 keyBy())中,每个键的状态会被独立管理。

使用场景:当需要对每个不同的键(比如用户ID、订单ID等)保持独立的状态时,可以使用键控状态。比如,对于每个用户计算其购买总额,状态就是针对每个用户的购买金额。

如何使用:通过KeyedProcessFunction或RichFunction,可以访问和操作每个键的状态。例如,可以通过ValueState、ListState、MapState等类型来保存键控状态。

示例代码:

public class KeyedStateExample extends KeyedProcessFunction<String, MyEvent, String> {
    private ValueState<Long> sumState;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        ValueStateDescriptor<Long> descriptor = new ValueStateDescriptor<>(
            "sumState", Long.class, 0L);
        sumState = getRuntimeContext().getState(descriptor);
    }

    @Override
    public void processElement(MyEvent value, Context ctx, Collector<String> out) throws Exception {
        Long currentSum = sumState.value();
        currentSum += value.getAmount();
        sumState.update(currentSum);
        out.collect("Updated sum: " + currentSum);
    }
}

2. 算子状态(Operator State)

​ 算子状态是与整个算子实例关联的状态,不会依赖于流中的每个键。它通常用于跨键的状态存储,或者用于一些需要全局共享的状态信息。

使用场景:算子状态适用于一些跨键的计算或与单个输入流的整体处理有关的场景。例如,一个算子可以使用算子状态来记录与整个作业相关的配置信息,或者用于跟踪不同算子实例之间的状态(如操作符的分区信息)。

如何使用:通过OperatorState API和ListState等管理算子状态。算子状态通常由作业的所有任务共享,且会在任务之间进行恢复。

示例代码:

public class OperatorStateExample extends RichMapFunction<MyEvent, String> {
    private ListState<Long> operatorState;

    @Override
    public void open(Configuration parameters) throws Exception {
        ListStateDescriptor<Long> descriptor = new ListStateDescriptor<>(
            "operatorState", Long.class);
        operatorState = getRuntimeContext().getListState(descriptor);
    }

    @Override
    public String map(MyEvent value) throws Exception {
        long sum = 0;
        for (Long stateValue : operatorState.get()) {
            sum += stateValue;
        }
        operatorState.add(value.getAmount());
        return "Current sum: " + sum;
    }
}

3. Checkpoint(检查点)与 Savepoint(保存点)

Checkpoint

​ Checkpoint是Flink用来实现容错的机制,它周期性地将所有算子的状态保存到外部持久化存储中(如 HDFS 或 S3)。在发生故障时,Flink可以根据最后一次成功的Checkpoint恢复流处理作业,从而保证处理的准确性。

​ 如何工作:Flink在运行时会定期进行Checkpoint,保存所有键控状态和算子状态。它会将状态快照记录下来,当发生故障时,可以从最近的Checkpoint恢复。

​ 配置Checkpoint

env.enableCheckpointing(5000); // 每 5 秒进行一次检查点
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); // 精确一次语义

Savepoint

​ Savepoint 是一种手动触发的检查点,用于作业的版本迁移或作业重启。与Checkpoint相比,Savepoint是由用户控制的,通常用于作业升级或故障恢复场景。

​ 如何工作:Savepoint 可以保存作业状态的快照并在稍后恢复。它的优点在于可以进行作业升级,而不会丢失状态。

​ 如何触发 Savepoint

bash
./bin/flink savepoint :jobId

Savepoint通常保存到一个指定的目录,可以通过 -s 参数在恢复作业时指定恢复的Savepoint。

4. 容错机制总结

状态一致性:通过Checkpoint和Savepoint,Flink确保了流处理的状态一致性,即使发生故障也能够恢复到一个一致的状态。

容错恢复:Flink使用checkpoint来定期保存状态,一旦发生故障,作业将从最近的checkpoint恢复。如果使用savepoint,则可以更灵活地控制恢复状态,通常用于版本控制和升级。

总结

​ 键控状态适用于每个键独立的状态管理,主要应用于 keyBy操作后需要针对每个键进行状态操作的场景。

​ 算子状态则用于整个算子实例的状态管理,不与特定的键关联,适用于跨键的计算或全局共享的状态。

​ Checkpoint和Savepoint通过定期保存状态,确保作业在发生故障后能够恢复,保证数据的精确处理语义。