Flink Broadcast State实用指南

转载自:

https://blog.csdn.net/wflh323/article/details/102918111

https://yq.aliyun.com/articles/706760     

 

     使用过spark的人都知道广播变量这个概念。广播变量相当于一个共享变量,将一个小数据集复制分发到每个task,task直接从本地读取。flink中有两种广播变量,一种静态的广播变量,一种实时动态的广播变量。

静态广播变量示例:

      使用场景如: 黑名单判断,将黑名单广播出去进行数据匹配。

public class FlinkBroadcast2 {
    public static void main(String[] args) throws Exception {
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        DataSet ds1 = env.fromElements(1, 2, 3, 4);
        DataSource ds2 = env.fromElements(7, 8, 9, 10);
        ds2.map(new RichMapFunction() {
            List list = new ArrayList();
            public void open(Configuration parameters) throws Exception {
               list = getRuntimeContext().getBroadcastVariable("bs");
            }
 
            @Override
            public String map(Integer integer) throws Exception {
 
                return integer.intValue()+":"+list;
            }
        }).withBroadcastSet(ds1,"bs").print();
 
//       env.execute();
    }

动态广播变量示例:

   使用场景: 数据依赖某些动态变化的处理规则

   广播流一般都是从kafka或其他数据源获取,这里演示直接固定了。从kafka获取流,修改数据后,下游也会更新广播流。

   key streaming 使用KeyedBroadcastProcessFunction.

   非key streaming 使用 BroadcastProcessFunction.
 

public class FlinkBroadcast {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        DataStream ds1 = env.fromElements(2);
        DataStreamSource ds2 = env.fromElements(7,8,9,10);
        MapStateDescriptor ruleStateDescriptor = new MapStateDescriptor<>(
                "BroadcastState",
                BasicTypeInfo.STRING_TYPE_INFO,
                TypeInformation.of(Integer.TYPE));
 
        BroadcastStream ruleBroadcastStream = ds1
                .broadcast(ruleStateDescriptor);
         ds2.connect(ruleBroadcastStream)
                .process(
 
 
                        new BroadcastProcessFunction() {
 
                            @Override
                            public void processElement(Integer integer, ReadOnlyContext readOnlyContext, Collector collector) throws Exception {
                                ReadOnlyBroadcastState state = readOnlyContext.getBroadcastState(ruleStateDescriptor);
                                Integer integer1 = state.get("test");
                                collector.collect(integer+"="+integer1);
                            }
 
                            @Override
                            public void processBroadcastElement(Integer integer, Context context, Collector collector) throws Exception {
                                context.getBroadcastState(ruleStateDescriptor).put("test",integer);
                            }
                        }
                ).print();
      env.execute();
    }
}

 

================================================================================================

 

从1.5.0开始,Flink提供了一种新的State类型,称为Broadcast State。在这篇文章中,我们将解释什么是Broadcast State,并展示如何将其应用于评估事件流上的动态模式的应用的示例。我们将向您介绍处理步骤和源代码,以实现此应用。

什么是Broadcast State?

Broadcast State可用于以特定方式组合和联合处理两个事件流。第一个流的事件被广播到一个算子的所有并行实例,该算子将它们保存为状态。另一个流的事件不广播,而是发送给同一个算子的单个实例,并与广播流的事件一起处理。对于需要连接低吞吐量和高吞吐量流或需要动态更新处理逻辑的应用来说,新的broadcast state非常适合。我们将使用一个具体示例来解释broadcast state,并在本文的其余部分更详细地展示其API。

到目前为止,我们从概念上讨论了这个应用,并解释了它如何使用broadcast state来评估事件流上的动态模式。接下来,我们将展示如何使用Flink的Datastream API和broadcast state特性来实现示例应用。

让我们从应用的输入数据开始。我们有两个数据流,行为流和模式流。在这一点上,我们并不关流从何而来。这些流可能是从Kafka、Kinesis或任何其他系统中摄取的。行为和模式是Pojos,每个字段有两个:

DataStream actions = ???
DataStream patterns = ???

ActionPatternPojos有两个字段:

  • Action: Long userId, String action
  • Pattern: String firstAction, String secondAction

第一步,我们在流上使用userId属性进行keyBy操作。

KeyedStream actionsByUser = actions
  .keyBy((KeySelector) action -> action.userId);

接下来,我们准备broadcast state。broadcast state始终表示为MapState,这是Flink提供的最通用的状态原语。

MapStateDescriptor bcStateDescriptor = 
  new MapStateDescriptor<>("patterns", Types.VOID, Types.POJO(Pattern.class));

由于我们一次仅评估和存储单个模式,我们将broadcast state配置为具有键类型Void和值类型Pattern的MapState。模式始终存储在MapState中,并将null作为键。

BroadcastStream bcedPatterns = patterns.broadcast(bcStateDescriptor);

对于broadcast state应该使用MapStateDescriptor,我们在patterns流上调用broadcast()方法将它转换为BroadcastStream流bcedPatterns.

DataStream> matches = actionsByUser
 .connect(bcedPatterns)
 .process(new PatternEvaluator());

我们得到了keyed之后的actionsByUser流与广播流bcedPatterns,我们调用connect()方法将他们连接在一起然后在流上应用PatternEvaluatorPatternEvaluator实现了KeyedBroadcastProcessFunction接口。它应用我们前面讨论过的模式匹配逻辑,并发送包含用户id和匹配模式的记录的Tuple2

public static class PatternEvaluator
    extends KeyedBroadcastProcessFunction> {
 
  // handle for keyed state (per user)
  ValueState prevActionState;
  // broadcast state descriptor
  MapStateDescriptor patternDesc;
 
  @Override
  public void open(Configuration conf) {
    // initialize keyed state
    prevActionState = getRuntimeContext().getState(
      new ValueStateDescriptor<>("lastAction", Types.STRING));
    patternDesc = 
      new MapStateDescriptor<>("patterns", Types.VOID, Types.POJO(Pattern.class));
  }

  /**
   * Called for each user action.
   * Evaluates the current pattern against the previous and
   * current action of the user.
   */
  @Override
  public void processElement(
     Action action, 
     ReadOnlyContext ctx, 
     Collector> out) throws Exception {
   // get current pattern from broadcast state
   Pattern pattern = ctx
     .getBroadcastState(this.patternDesc)
     // access MapState with null as VOID default value
     .get(null);
   // get previous action of current user from keyed state
   String prevAction = prevActionState.value();
   if (pattern != null && prevAction != null) {
     // user had an action before, check if pattern matches
     if (pattern.firstAction.equals(prevAction) && 
         pattern.secondAction.equals(action.action)) {
       // MATCH
       out.collect(new Tuple2<>(ctx.getCurrentKey(), pattern));
     }
   }
   // update keyed state and remember action for next pattern evaluation
   prevActionState.update(action.action);
 }

 /**
  * Called for each new pattern.
  * Overwrites the current pattern with the new pattern.
  */
 @Override
 public void processBroadcastElement(
     Pattern pattern, 
     Context ctx, 
     Collector> out) throws Exception {
   // store the new pattern by updating the broadcast state
   BroadcastState bcState = ctx.getBroadcastState(patternDesc);
   // storing in MapState with null as VOID default value
   bcState.put(null, pattern);
 }
}

这个KeyedBroadcastProcessFunction接口提供了处理记录和发出结果的三种方法。

  • processBroadcastElement(): 在广播流的每个记录调进来的时候用。在PatternEvaluator函数,我们简单地将接收到的Pattern使用null键(记住,我们只在MapState).
  • processElement(): 在keyed stream的每个记录进来的时候调用。它提供对Broadcast State的只读访问,以防止对跨函数并行实例的不同broadcast state的修改。这PatternEvaluator的processElement()方法从broadcast state检索当前模式,从keyed state检索用户的先前行为。如果两者都存在,它将检查前面和当前的行为是否与模式匹配,如果匹配话,它会发出模式匹配记录。最后,它将keyed state更新为当前用户行为。
  • onTimer(): 在之前注册过的计时器触发时调用。计时器可以在processElement方法中注册,用于执行计算或清除将来的状态。为了保持代码的简洁性我们没有在我们的示例中实现这个方法。但是,当用户在一段时间内没有活动时,可以使用它来删除用户的最后一个行为,以避免由于不活动的用户而导致state的增长。

你可能已经注意到KeyedBroadcastProcessFunction的process方法。context 对象允许使用其他功能,如:

  • broadcast state(读写或只读,取决于方法)
  • TimerService,它允许访问记录的时间戳、当前watermark,并且可以注册计时器
  • 当前的key(仅在processElement()方法中可用),以及一种将函数应用于每个注册key的keyed state的方法(仅在processBroadcastElement()方法中可用)

这个KeyedBroadcastProcessFunction就像其他ProcessFunction一样完全可以访问Flink中的state和时间特性,因此可以用来实现复杂的逻辑。broadcast state被设计成一个通用的特性,可以适应不同的场景和用例。虽然我们只讨论了一个相当简单和受限的应用,但您可以通过多种方式使用broadcast state来实现应用的需求。

 

你可能感兴趣的:(Flink)