广播流之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 + '\'' +
                    '}';
        }
    }
}

 

posted on 2023-05-01 17:04  溪水静幽  阅读(270)  评论(0)    收藏  举报