Flink onTime - processFunction

轉載作者:寫bug的張小天

轉載地址:https://www.jianshu.com/p/e6297fac67cb

Process Function(過程函數)

ProcessFunction是一個低層次的流處理操作,允許返回所有(無環的)流程序的基礎構建模塊:
  1、事件(event)(流元素)
  2、狀態(state)(容錯性,一致性,僅在keyed stream中)
  3、定時器(timers)(event time和processing time, 僅在keyed stream中)

ProcessFunction可以認爲是能夠訪問到keyed state和timers的FlatMapFunction,輸入流中接收到的每個事件都會調用它來處理。

對於容錯性狀態,ProcessFunction可以通過RuntimeContext來訪問Flink的keyed state,方法與其他狀態性函數訪問keyed state一樣。

定時器允許應用程序對processing timeevent time的變化做出反應,每次對processElement(...)的調用都會得到一個Context對象,該對象允許訪問元素事件時間的時間戳和TimeServerTimeServer可以用來爲尚未發生的event-time或者processing-time註冊回調,當定時器的時間到達時,onTimer(...)方法會被調用。在這個調用期間,所有的狀態都會限定到創建定時器的鍵,並允許定時器操縱鍵控狀態(keyed states)。

注意:如果你想訪問一個鍵控狀態(keyed state)和定時器,你需要將ProcessFunction應用到一個鍵控流(keyed stream)中:

stream.keyBy(...).process(new MyProcessFunction())

低層次的Join(Low-Level Joins)

爲了在兩個輸入流中實現低層次的操作,應用程序可以使用CoProcessFunction,這個函數綁定了兩個不同的輸入流,並通過分別調用processElement1(...)processElement2(...)來獲取兩個不同輸入流中的記錄。

實現一個低層次的join通常按下面的模式進行:
  1、爲一個輸入源(或者兩個都)創建一個狀態(state)
  2、在接收到輸入源中的元素時更新狀態(state)
  3、在接收到另一個輸入源的元素時,探查狀態併產生連接結果
例如:你可能將客戶數據連接到金融交易中,同時保存客戶信息的狀態,如果您關心在無序事件的情況下進行完整和確定性的連接,則當客戶數據流的水印已經通過交易時,您可以使用計時器來評估和發佈交易的連接。

例子

以下示例維護了每個key的計數,並且每一分鐘發出一個沒有更新key的key/count對。
  1、count,key和last-modification-timestamp保存在ValueState中,該ValueState由key隱含地限定。
  2、對於每條記錄,ProcessFunction增加counter的計數並設置最後更新時間戳
  3、該函數還會在未來一分鐘內安排回調(基於event time)
  4、在每次回調時,它會根據存儲的count的最後更新時間來檢查callback的事件時間,如果匹配的話,就會將key/count對發出來

注意:這個簡單的例子可以通過session window來實現,我們這裏使用ProcessFunction是爲了說明它提供的基本模式。
Java 代碼:

import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.ProcessFunction;
import org.apache.flink.streaming.api.functions.ProcessFunction.Context;
import org.apache.flink.streaming.api.functions.ProcessFunction.OnTimerContext;
import org.apache.flink.util.Collector;


// 定義源數據流
DataStream<Tuple2<String, String>> stream = ...;

// 將 process function 應用到一個鍵控流(keyed stream)中
DataStream<Tuple2<String, Long>> result = stream
    .keyBy(0)
    .process(new CountWithTimeoutFunction());

/**
 * The data type stored in the state
 * state中保存的數據類型
 */
public class CountWithTimestamp {

    public String key;
    public long count;
    public long lastModified;
}

/**
 * The implementation of the ProcessFunction that maintains the count and timeouts
 * ProcessFunction的實現,用來維護計數和超時
 */
public class CountWithTimeoutFunction extends ProcessFunction<Tuple2<String, String>, Tuple2<String, Long>> {

    /** The state that is maintained by this process function */
    /** process function維持的狀態 */
    private ValueState<CountWithTimestamp> state;

    @Override
    public void open(Configuration parameters) throws Exception {
        state = getRuntimeContext().getState(new ValueStateDescriptor<>("myState", CountWithTimestamp.class));
    }

    @Override
    public void processElement(Tuple2<String, String> value, Context ctx, Collector<Tuple2<String, Long>> out)
            throws Exception {

        // retrieve the current count
        // 獲取當前的count
        CountWithTimestamp current = state.value();
        if (current == null) {
            current = new CountWithTimestamp();
            current.key = value.f0;
        }

        // update the state's count
         // 更新 state 的 count
        current.count++;

        // set the state's timestamp to the record's assigned event time timestamp
        // 將state的時間戳設置爲記錄的分配事件時間戳
        current.lastModified = ctx.timestamp();

        // write the state back
        // 將狀態寫回
        state.update(current);

        // schedule the next timer 60 seconds from the current event time
        // 從當前事件時間開始計劃下一個60秒的定時器
        ctx.timerService().registerEventTimeTimer(current.lastModified + 60000);
    }

    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, Collector<Tuple2<String, Long>> out)
            throws Exception {

        // get the state for the key that scheduled the timer
        //獲取計劃定時器的key的狀態
        CountWithTimestamp result = state.value();

        // 檢查是否是過時的定時器或最新的定時器
        if (timestamp == result.lastModified + 60000) {
            // emit the state on timeout
            out.collect(new Tuple2<String, Long>(result.key, result.count));
        }
    }
}

Scala 代碼:

import org.apache.flink.api.common.state.ValueState
import org.apache.flink.api.common.state.ValueStateDescriptor
import org.apache.flink.streaming.api.functions.ProcessFunction
import org.apache.flink.streaming.api.functions.ProcessFunction.Context
import org.apache.flink.streaming.api.functions.ProcessFunction.OnTimerContext
import org.apache.flink.util.Collector

//  定義源數據流
val stream: DataStream[Tuple2[String, String]] = ...

// 將 process function 應用到一個鍵控流(keyed stream)中
val result: DataStream[Tuple2[String, Long]] = stream
  .keyBy(0)
  .process(new CountWithTimeoutFunction())

/**
  *  state中保存的數據類型
  */
case class CountWithTimestamp(key: String, count: Long, lastModified: Long)

/**
  * ProcessFunction的實現,用來維護計數和超時
  */
class CountWithTimeoutFunction extends ProcessFunction[(String, String), (String, Long)] {

  /** process function維持的狀態  */
  lazy val state: ValueState[CountWithTimestamp] = getRuntimeContext
    .getState(new ValueStateDescriptor[CountWithTimestamp]("myState", classOf[CountWithTimestamp]))


  override def processElement(value: (String, String), ctx: Context, out: Collector[(String, Long)]): Unit = {
    // 初始化或者獲取/更新狀態
 
    val current: CountWithTimestamp = state.value match {
      case null =>
        CountWithTimestamp(value._1, 1, ctx.timestamp)
      case CountWithTimestamp(key, count, lastModified) =>
        CountWithTimestamp(key, count + 1, ctx.timestamp)
    }

    // 將狀態寫回
    state.update(current)

    // 從當前事件時間開始計劃下一個60秒的定時器
    ctx.timerService.registerEventTimeTimer(current.lastModified + 60000)
  }

  override def onTimer(timestamp: Long, ctx: OnTimerContext, out: Collector[(String, Long)]): Unit = {
    state.value match {
      case CountWithTimestamp(key, count, lastModified) if (timestamp == lastModified + 60000) =>
        out.collect((key, count))
      case _ =>
    }
  }
}



作者:寫Bug的張小天
鏈接:https://www.jianshu.com/p/e6297fac67cb
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

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