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<Integer> ds1 = env.fromElements(1, 2, 3, 4);
        DataSource<Integer> ds2 = env.fromElements(7, 8, 9, 10);
        ds2.map(new RichMapFunction<Integer, String>() {
            List<Integer> list = new ArrayList<Integer>();
            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<Integer> ds1 = env.fromElements(2);
        DataStreamSource<Integer> ds2 = env.fromElements(7,8,9,10);
        MapStateDescriptor<String, Integer> ruleStateDescriptor = new MapStateDescriptor<>(
                "BroadcastState",
                BasicTypeInfo.STRING_TYPE_INFO,
                TypeInformation.of(Integer.TYPE));
 
        BroadcastStream<Integer> ruleBroadcastStream = ds1
                .broadcast(ruleStateDescriptor);
         ds2.connect(ruleBroadcastStream)
                .process(
 
 
                        new BroadcastProcessFunction<Integer,Integer,String>() {
 
                            @Override
                            public void processElement(Integer integer, ReadOnlyContext readOnlyContext, Collector<String> collector) throws Exception {
                                ReadOnlyBroadcastState<String, Integer> state = readOnlyContext.getBroadcastState(ruleStateDescriptor);
                                Integer integer1 = state.get("test");
                                collector.collect(integer+"="+integer1);
                            }
 
                            @Override
                            public void processBroadcastElement(Integer integer, Context context, Collector<String> 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<Action> actions = ???
DataStream<Pattern> patterns = ???

ActionPatternPojos有兩個字段:

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

第一步,我們在流上使用userId屬性進行keyBy操作。

KeyedStream<Action, Long> actionsByUser = actions
  .keyBy((KeySelector<Action, Long>) action -> action.userId);

接下來,我們準備broadcast state。broadcast state始終表示爲MapState,這是Flink提供的最通用的狀態原語。

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

由於我們一次僅評估和存儲單個模式,我們將broadcast state配置爲具有鍵類型Void和值類型Pattern的MapState。模式始終存儲在MapState中,並將null作爲鍵。

BroadcastStream<Pattern> bcedPatterns = patterns.broadcast(bcStateDescriptor);

對於broadcast state應該使用MapStateDescriptor,我們在patterns流上調用broadcast()方法將它轉換爲BroadcastStream流bcedPatterns.

DataStream<Tuple2<Long, Pattern>> matches = actionsByUser
 .connect(bcedPatterns)
 .process(new PatternEvaluator());

我們得到了keyed之後的actionsByUser流與廣播流bcedPatterns,我們調用connect()方法將他們連接在一起然後在流上應用PatternEvaluatorPatternEvaluator實現了KeyedBroadcastProcessFunction接口。它應用我們前面討論過的模式匹配邏輯,併發送包含用戶id和匹配模式的記錄的Tuple2<Long, Pattern>

public static class PatternEvaluator
    extends KeyedBroadcastProcessFunction<Long, Action, Pattern, Tuple2<Long, Pattern>> {
 
  // handle for keyed state (per user)
  ValueState<String> prevActionState;
  // broadcast state descriptor
  MapStateDescriptor<Void, Pattern> 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<Tuple2<Long, Pattern>> 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<Tuple2<Long, Pattern>> out) throws Exception {
   // store the new pattern by updating the broadcast state
   BroadcastState<Void, Pattern> 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來實現應用的需求。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章