广播流之BroadcastStream
广播状态支持这样的用例:来自一个流的一些数据需要广播到所有下游任务,在那里它被本地存储,并用于处理另一个流上的所有传入元素。
一条流需要根据规则或配置处理数据,而规则或配置又是随时变化的。此时,就可将规则或配置作为广播流广播出去,并以Broadcast State的形式存储在下游Task中。下游Task根据Broadcast State中的规则或配置来处理常规流中的数据。
场景:动态更新计算规则: 如事件流需要根据最新的规则进行计算,则可将规则作为广播状态广播到下游Task中。实时增加额外字段: 如事件流需要实时增加用户的基础信息,则可将用户的基础信息作为广播状态广播到下游Task中。
Broadcast State是Map类型,即K-V类型。
Broadcast State只有在广播的一侧,即在BroadcastProcessFunction或KeyedBroadcastProcessFunction的processBroadcastElement方法中可以修改。在非广播的一侧,即在BroadcastProcessFunction或KeyedBroadcastProcessFunction的processElement方法中只读。
Broadcast State中元素的顺序,在各Task中可能不同。基于顺序的处理,需要注意。Broadcast State在Checkpoint时,每个Task都会Checkpoint广播状态。Broadcast State在运行时保存在内存中,目前还不能保存在RocksDB State Backend中。 在处理数据的时候,有些配置是要实时动态改变的。在MYSQL里随时配置修改的,在高吞吐计算的Function中动态查询配置文件有可能使整个计算阻塞,甚至任务停止。广播流可以通过查询配置文件,广播到某个 operator 的所有并发实例中,然后与另一条流数据连接进行计算。
public class BroadcastStatTest { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); DataStreamSource<Action> actionStream = env.fromElements(new Action("Alice", "login"), new Action("Alice", "pay"), new Action("Bob", "login"), new Action("Bob", "buy") ); // 定义行为模式流,代表了要检测的标准 DataStreamSource<Pattern> patternStream = env.fromElements( new Pattern("login", "pay"), new Pattern("login", "buy") ); // 定义广播状态的描述器,创建广播流 MapStateDescriptor<Void, Pattern> bcStateDescriptor = new MapStateDescriptor<>("patterns", Types.VOID, Types.POJO(Pattern.class)); BroadcastStream<Pattern> broadcast = patternStream.broadcast(bcStateDescriptor); // 将事件流和广播流连接起来,进行处理 actionStream.keyBy(data -> data.userId) .connect(broadcast) .process(new KeyedBroadcastProcessFunction<String, Action, Pattern, Tuple2<String, Pattern>>() { // 定义一个值状态,保存上一次用户行为 ValueState<String> prevActionState; @Override public void open(Configuration parameters) throws Exception { prevActionState = getRuntimeContext().getState(new ValueStateDescriptor<String>("lastAction", String.class)); } @Override public void processElement(Action action, ReadOnlyContext readOnlyContext, Collector<Tuple2<String, Pattern>> collector) throws Exception { // 数据流,只拥有配置流读取的权力 Pattern patterns = readOnlyContext.getBroadcastState(new MapStateDescriptor<>("patterns", Types.VOID, Types.POJO(Pattern.class))).get(null); String prevAction = prevActionState.value(); if (null != patterns && null != prevAction) { // 如果前后两次行为都符合模式定义,输出一组匹配 // 比如:Alice行为为login,而且动作为pay则符合条件 if (patterns.action1.equals(prevAction) && patterns.action2.equals(action.action)) { collector.collect(new Tuple2<>(readOnlyContext.getCurrentKey(), patterns)); } } // 更新状态 prevActionState.update(action.action); } @Override public void processBroadcastElement(Pattern pattern, Context context, Collector<Tuple2<String, Pattern>> collector) throws Exception { // 配置流,拥有上下文修改的权利 BroadcastState<Void, Pattern> bcState = context.getBroadcastState(new MapStateDescriptor<>("patterns", Types.VOID, Types.POJO(Pattern.class))); // 将广播状态更新为当前的 pattern bcState.put(null, pattern); } }).setParallelism(2) .print(); env.execute(); } public static class Action { public String userId; public String action; public Action() { } public Action(String userId, String action) { this.userId = userId; this.action = action; } @Override public String toString() { return "Action{" + "userId=" + userId + ", action='" + action + '\'' + '}'; } } public static class Pattern { public String action1; public String action2; public Pattern() { } public Pattern(String action1, String action2) { this.action1 = action1; this.action2 = action2; } @Override public String toString() { return "Pattern{" + "action1='" + action1 + '\'' + ", action2='" + action2 + '\'' + '}'; } } }
立志如山 静心求实
浙公网安备 33010602011771号