Flink的狀態管理

State 和Fault Tolerance(重點)

有狀態操作或者操作算子在處理DataStream的元素或者事件的時候需要存儲計算狀態,這就使得狀態在整個Flink的精細化計算中扮演着非常重要的地位:

  • 記錄數據某一個過去時間段到當前時間期間數據狀態信息。
  • 在每分鐘/小時/天彙總事件時,狀態保留待處理的彙總記錄。
  • 在訓練機器學習模型時,狀態保持當前版本的模型參數。

Flink=管理狀態,以便使用checkpoint和savepoint實現狀態容錯。Flink的狀態在計算規模發送變化的時候,Flink可以自動在並行實例間實現狀態的重新分發。Flink底層使用StateBackend策略存儲計算狀態,StateBackend決定了狀態存儲的方式和位置。

在Flink狀態管理中將所有能操作的狀態分爲Keyed StateOperator State,其中Keyed State中狀態是和key一一綁定的,並且只能在KeyedStream中使用。所有non-KeyedStream狀態操作都叫做Operator State。底層Flink在做狀態管理的時候是將Keyed State和<parallel-operator-instance, key>由於某一個key僅僅落入其中一個operator-instance中,因此可以簡單的理解Keyed State是和<operator,key>進行綁定的。Flink底層會採用Key Group機制對Keyed State進行管理或者分類,所有的keyed-operator在做狀態操作的時候可能需要和1~n個KeyGroup進行交互。

Flink在分發keyed-state狀態的時候,並不是以key爲單位,Key Group是最小分發單元

Operator State (也稱爲 non-keyed state), 每個operator state 和 一個parallel operator instance綁定。Keyed StateOperator State 以兩種形式存在 managed(管理) 和 raw(原生).所有的Flink已知的操作符都支持managed state,但是Raw Sate僅僅是在用戶自定義operator時候使用,並且不支持在並行度發生變化的時候狀態重新分發。因此Flink雖然支持Raw Sate但是在絕大多數場景,一般使用的都是managed State。

Keyed-state

keyed-state接口提供對不同類型的狀態的訪問,所有狀態都限於當前輸入元素的key。

類型 說明 方法
ValueState 這個狀態主要存儲一個可以用作更新的值。 update(T)
T value()
clear()
ListState 存儲List集合元素. add(T)
addAll(List)
Iterable get()
update(List)
clear()
ReducingState 這將保留一個值,該值表示添加到狀態的所有值的彙總,
需要用戶提供ReduceFunction
add(T)
T get()
clear()
AggregatingState<IN, OUT> 這將保留一個值,該值表示添加到狀態的所有值的彙總,
需要用戶提供AggregateFunction
add(IN)
T get()
clear()
FoldingState<T, ACC> 這將保留一個值,該值表示添加到狀態的所有值的彙總,
需要用戶提供FoldFunction
add(IN)
T get()
clear()
MapState<UK, UV> 這會保留一個Map。 put(UK, UV)
putAll(Map<UK, UV>)
entries()
keys()
values()
clear()

value state

var fsEnv=StreamExecutionEnvironment.getExecutionEnvironment

fsEnv.socketTextStream("CentOS",9999)
.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(0)
.map(new RichMapFunction[(String,Int),(String,Int)] {
    var vs:ValueState[Int]=_
    override def open(parameters: Configuration): Unit = {
        val vsd=new ValueStateDescriptor[Int]("valueCount",createTypeInformation[Int])
        vs=getRuntimeContext.getState[Int](vsd)
    }
    override def map(value: (String, Int)): (String, Int) = {
        val histroyCount = vs.value()
        val currentCount=histroyCount+value._2
        vs.update(currentCount)
        (value._1,currentCount)
    }
}).print()

fsEnv.execute("wordcount")

AggregatingState<IN, OUT>

var fsEnv=StreamExecutionEnvironment.getExecutionEnvironment

fsEnv.socketTextStream("CentOS",9999)
.map(_.split("\\s+"))
.map(ts=>(ts(0),ts(1).toInt))
.keyBy(0)
.map(new RichMapFunction[(String,Int),(String,Double)] {
    var vs:AggregatingState[Int,Double]=_
    override def open(parameters: Configuration): Unit = {
        val vsd=new AggregatingStateDescriptor[Int,(Double,Int),Double]("avgCount",new AggregateFunction[Int,(Double,Int),Double] {
            override def createAccumulator(): (Double, Int) = {
                (0.0,0)
            }

            override def add(value: Int, accumulator: (Double, Int)): (Double, Int) = {
                (accumulator._1+value,accumulator._2+1)
            }
            override def merge(a: (Double, Int), b: (Double, Int)): (Double, Int) = {
                (a._1+b._1,a._2+b._2)
            }
            override def getResult(accumulator: (Double, Int)): Double = {
                accumulator._1/accumulator._2
            }
        },createTypeInformation[(Double,Int)])
        vs=getRuntimeContext.getAggregatingState(vsd)
    }
    override def map(value: (String, Int)): (String, Double) = {
        vs.add(value._2)
        val avgCount=vs.get()
        (value._1,avgCount)
    
}).print()

fsEnv.execute("wordcount")

MapState<UK, UV>

package com.hw.demo04

import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.common.state.{MapState, MapStateDescriptor}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.scala._
import scala.collection.JavaConverters._
/**
  * @aurhor:fql
  * @date 2019/10/16 19:41 
  * @type:
  */
object MapState {
  def main(args: Array[String]): Unit = {

    val fsEnv = StreamExecutionEnvironment.getExecutionEnvironment
    fsEnv.socketTextStream("CentOS",9999)
      .map(_.split("\\s+"))
      .map(ts=>Login(ts(0).toInt,ts(1),ts(2),ts(3),ts(4)))
      .keyBy("id","name")
      .map(new RichMapFunction[Login,String] {
        var vs:MapState[String,String]=_
        override def open(parameters: Configuration): Unit = {
          val msd=new MapStateDescriptor[String,String]("mapstate",createTypeInformation[String],createTypeInformation[String])
          vs=getRuntimeContext.getMapState(msd)
        }
        override def map(value: Login): String = {
          println("歷史登陸")
          for(k<- vs.keys().asScala){
            println(k+" "+vs.get(k))
          }
          var result=""
          if(vs.keys().iterator().asScala.isEmpty){
            result="ok"
          }else{
            if(!value.city.equalsIgnoreCase(vs.get("city"))){
              result="error"
            }else{
              result="ok"
            }
          }
          //更新狀態
          vs.put("ip",value.ip)
          vs.put("city",value.city)
          vs.put("time",value.time)
          result
        }
      }).print()

    fsEnv.execute("wordCount")
  }
}

總結

new Rich[Map|FaltMap]Function {
    var vs:XxxState=_ //狀態聲明
    override def open(parameters: Configuration): Unit = {
        val xxd=new XxxStateDescription //完成狀態的初始化
        vs=getRuntimeContext.getXxxState(xxd)
    }
    override def xxx(value: Xx): Xxx = {
       //狀態操作
    }
}
  • ValueState<T> getState(ValueStateDescriptor<T>)
  • ReducingState<T> getReducingState(ReducingStateDescriptor<T>)
  • ListState<T> getListState(ListStateDescriptor<T>)
  • AggregatingState<IN, OUT> getAggregatingState(AggregatingStateDescriptor<IN, ACC, OUT>)
  • FoldingState<T, ACC> getFoldingState(FoldingStateDescriptor<T, ACC>)
  • MapState<UK, UV> getMapState(MapStateDescriptor<UK, UV>)

State Time-To-Live (TTL)

基本使用

可以將state存活時間(TTL)分配給任何類型的key-state.如果配置了TTL且狀態值已過期,則flink將盡力清除存儲的值。

import org.apache.flink.api.common.state.StateTtlConfig
import org.apache.flink.api.common.state.ValueStateDescriptor
import org.apache.flink.api.common.time.Time

val ttlConfig = StateTtlConfig
    .newBuilder(Time.seconds(1))
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
    .build
    
val stateDescriptor = new ValueStateDescriptor[String]("text state", classOf[String])
stateDescriptor.enableTimeToLive(ttlConfig)
  • 案例
package com.hw.demo04
import java.util.Properties
import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.api.common.state.StateTtlConfig.{StateVisibility, UpdateType}
import org.apache.flink.api.common.state.{StateTtlConfig, ValueState, ValueStateDescriptor}
import org.apache.flink.api.common.time.Time
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
import org.apache.flink.streaming.api.scala._
/**
  * @aurhor:fql
  * @date 2019/10/16 20:32 
  * @type:
  */
object TTL {
  def main(args: Array[String]): Unit = {
    val fsEnv = StreamExecutionEnvironment.getExecutionEnvironment
    val props = new Properties()
    props.setProperty("bootstrap.servers", "CentOS:9092")
    props.setProperty("group.id", "g1")
    val lines=fsEnv.addSource(new FlinkKafkaConsumer("topic01",new SimpleStringSchema(),props))
      .flatMap(_.split("\\s+"))
      .map((_,1))
      .keyBy(0)
      .map(new RichMapFunction[(String,Int),(String,Int)] {
       var vs:ValueState[Int]=_
        override def open(parameters: Configuration): Unit = {
          val vsd = new ValueStateDescriptor[Int]("valueCount", createTypeInformation[Int])

          val ttlconfig = StateTtlConfig.newBuilder(Time.seconds(5)) //過期時間5s
            .setUpdateType(UpdateType.OnCreateAndWrite) //創建和修改的時候更新過期時間
            .setStateVisibility(StateVisibility.NeverReturnExpired) //永不返回過期的數據
            .build()

          vsd.enableTimeToLive(ttlconfig)
          vs = getRuntimeContext.getState[Int](vsd)
        }
        override def map(value: (String, Int)): (String, Int) = {
               val historCount = vs.value()
               val currentCount= historCount+value._2
          vs.update(currentCount)
          (value._1,currentCount)
        }
      }).print()

    fsEnv.execute("wordcount")
  }
}

注意:開啓TTL之後,系統會額外消耗內存存儲時間戳(Processing Time),如果用戶以前沒有開啓TTL配置,在啓動之前修改代碼開啓了TTL,在做狀態恢復的時候系統啓動不起來,,跑出兼容性失敗以及StateMigrationException異常。

清除 Expired State

默認情況下,僅當明確讀出過期值數據的時候,例如,通過調用ValueState.value(),過期的數據纔會被清除。這意味着默認情況下,如果未讀取過期狀態,則不會將其刪除,可能會導致狀態的不斷增長。

Cleanup in full snapshot

從上一次狀態恢復的時候,系統會加載所有的state快照,在加載過程中會踢除那些過期的數據,並不會影響磁盤存儲的狀態數據。該狀態數據只會在checkpoint的時候被覆蓋。依然解決不了在運行時自動清除過期且沒有用過的數據。

import org.apache.flink.api.common.state.StateTtlConfig
import org.apache.flink.api.common.time.Time

val ttlConfig = StateTtlConfig
    .newBuilder(Time.seconds(1))
    .cleanupFullSnapshot
    .build

只能應用於memory或者fs狀態後端實現,不支持RockDB state backend。

Cleanup in backgroup

可以開啓後臺清除策略,根據state Backend的實現採取默認的清除策略(不同狀態後端存儲,清除策略不同)

import org.apache.flink.api.common.state.StateTtlConfig
val ttlConfig = StateTtlConfig
    .newBuilder(Time.seconds(1))
    .cleanupInBackground
    .build

Incremental cleanup(基於內存backend)

import org.apache.flink.api.common.state.StateTtlConfig
val ttlConfig = StateTtlConfig.newBuilder(Time.seconds(5))
              .setUpdateType(UpdateType.OnCreateAndWrite)
              .setStateVisibility(StateVisibility.NeverReturnExpired)
              .cleanupIncrementally(100,true) //默認值 5 | false
              .build()

第一個參數表示每一次觸發cleanup的時候,系統一次處理100個元素。如果用戶操作任意一個state訪問系統都會觸發cleanup策略。第二參數如果爲true,表示系統會只要接收記錄數(即使用戶沒有操作狀態)就會觸發cleanup。

RocksDB compaction

RockDB(k-v存儲)底層異步壓縮狀態,會將key相同的數據進行Compact(壓縮),以減少state文件大小。但是並不 對過期state進行清理,因此可以通過配置CompactFilter讓RockDB在compact的時候對過期的state進行排除。這種特性過濾的特性默認是關閉的,如果開啓可以再flink-conf.yaml中配置 state.backend.rocksdb.ttl.compaction.filter.enabled: true 或者通過API設置 RocksDBStateBackend::enableTtlCompactionFilter.

在這裏插入圖片描述

import org.apache.flink.api.common.state.StateTtlConfig 
val ttlConfig = StateTtlConfig.newBuilder(Time.seconds(5))
              .setUpdateType(UpdateType.OnCreateAndWrite)
              .setStateVisibility(StateVisibility.NeverReturnExpired)
              .cleanupInRocksdbCompactFilter(1000)//默認配置1000
              .build()

這裏的1000表示,系統在做compact的時候,系統會檢查1000 元素是否失效。如果失效清除該過期數據。

Operator State

如果用戶想使用Operator State,用戶只需要實現通用的checkpointedFunction 接口或者ListCheckpointed<T extends Serializable> 注意目前的operator-state僅僅支持list-style風格的狀態,要求所存儲到的狀態必須是一個List,且其中的元素必須可以序列化。

CheckpointedFunction

提供兩種不同的狀態發佈方案:Even-split 和 Union

void snapshotState(FunctionSnapshotContext context) throws Exception;
void initializeState(FunctionInitializationContext context) throws Exception;
  • snapshotState():調用checkpoint的時候,系統會調用snapshotState 對狀態做快照
  • initiallizeState():第一次啓動或者從上一次狀態恢復反時候調用initializeState()
    Even-split:表示系統在故障恢復的時候,會將operator-state的元素均分給所有的operator實例,每個operator實例獲取sub-list數據。

Union:表示系統在故障恢復的時候,每一個operator實例可以獲取到整個Operator-state的全部數據。

案例:

package com.hw.demo05

import org.apache.flink.api.common.state.{ListState, ListStateDescriptor}
import org.apache.flink.runtime.state.{FunctionInitializationContext, FunctionSnapshotContext}
import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction
import org.apache.flink.streaming.api.functions.sink.SinkFunction
import org.apache.flink.streaming.api.scala._
import scala.collection.mutable.ListBuffer
import scala.collection.JavaConverters._
/**
  * @aurhor:fql
  * @date 2019/10/17 17:40 
  * @type:
  */
class BufferSink (threshold:Int=0) extends SinkFunction[(String,Int)] with CheckpointedFunction{

  var listState:ListState[(String,Int)]=_
  val bufferedElements=ListBuffer[(String,Int)]()

  //負責將數據輸出到外圍系統
  override def invoke(value: (String, Int)): Unit = {
     bufferedElements+=value     //將value的值添加到bufferedElements
    if(bufferedElements.size==threshold){  //判斷值是否達到閾值
      for(ele <-bufferedElements){   //進行遍歷
        println(ele)  //輸出元素
      }
      bufferedElements.clear()
    }
  }
  //是在savepoint|checkpoint時候數據持久化
  override def snapshotState(context: FunctionSnapshotContext): Unit = {
         listState.clear() //首先清空
    for(ele <-bufferedElements){   //遍歷bufferrdElements
      listState.add(ele)  //強元素添加到listState
    }
  }
  //狀態恢復|初始化 創建狀態
  override def initializeState(context: FunctionInitializationContext): Unit = {
    val lsd = new ListStateDescriptor[(String, Int)]("buffered-elements",createTypeInformation[(String,Int)])

    listState = context.getOperatorStateStore.getListState(lsd)  //獲取值
     if(context.isRestored){ //進行狀態判斷
       for(element <-listState.get().asScala){  //對listState進行遍歷
         bufferedElements+=element   //將遍歷出來的元素添加到bufferedElements
       }
     }
  }
}

 val fsEnv = StreamExecutionEnvironment.getExecutionEnvironment

    fsEnv.socketTextStream("CentOS",9999)
      .flatMap(_.split("\\s+"))
      .map((_,1))
      .keyBy(0)
      .addSink(new BufferSink(5)) //設置閾值
    fsEnv.execute("testOpreateo")
  • 啓動服務
[root@CentOS ~]# nc -lk 9999
  • 任務提交
    在這裏插入圖片描述
    注意:將任務的並行度設置爲 1 ,方便測試

  • 輸入數據

[root@CentOS ~]# nc -lk 9999
a1 b1 c1 d1
  • 取消任務,並創建savepoint
[root@CentOS flink-1.8.1]# ./bin/flink list -m CentOS:8081
------------------ Running/Restarting Jobs -------------------
17.10.2019 09:49:20 : f21795e74312eb06fbf0d48cb8d90489 : testoperatorstate (RUNNING)
--------------------------------------------------------------
[root@CentOS flink-1.8.1]# ./bin/flink cancel -m CentOS:8081 -s hdfs:///savepoints f21795e74312eb06fbf0d48cb8d90489
Cancelling job f21795e74312eb06fbf0d48cb8d90489 with savepoint to hdfs:///savepoints.
Cancelled job f21795e74312eb06fbf0d48cb8d90489. Savepoint stored in hdfs://CentOS:9000/savepoints/savepoint-f21795-38e7beefe07b.

注意,如果Flink需要和Hadoop整合,必須保證在當前環境變量下有HADOOP_HOME|HADOOP_CALSSPATH

  • 測試狀態
    在這裏插入圖片描述
    在這裏插入圖片描述

ListCheckpointed

該接口是CheckpointedFunction一種變體形式,僅僅只支持Even-split狀態的分發策略。

List<T> snapshotState(long checkpointId, long timestamp) throws Exception;
void restoreState(List<T> state) throws Exception;
  • snapshotState: 調用checkpoint的時候,系統會調用SnapshotState 對狀態做快照。
  • restoreState: 等價上述CheckpointedFunction中聲明的initializeState方法,用作狀態恢復。

案例

import java.lang.{Long => JLong} //修改類別名
import scala.{Long => SLong} //修改類別名
class CustomStatefulSourceFunction extends ParallelSourceFunction[SLong] with ListCheckpointed[JLong]{
  @volatile
  var isRunning:Boolean = true
  var offset = 0L
  override def run(ctx: SourceFunction.SourceContext[SLong]): Unit = {
    val lock = ctx.getCheckpointLock
    while(isRunning){
       Thread.sleep(1000)
       lock.synchronized({
         ctx.collect(offset)
         offset += 1
       })
    }
  }

  override def cancel(): Unit = {
    isRunning=false
  }

  override def snapshotState(checkpointId: Long, timestamp: Long): util.List[JLong] = {
    Collections.singletonList(offset) //存儲的是 當前source的偏移量,如果狀態不可拆分,用戶可以使Collections.singletonList
  }

  override def restoreState(state: util.List[JLong]): Unit = {
    for (s <- state.asScala) {
      offset = s
    }
  }
}
var fsEnv=StreamExecutionEnvironment.getExecutionEnvironment

fsEnv.addSource[Long](new CustomStatefulSourceFunction)
.print("offset:")

fsEnv.execute("testOffset")

廣播狀態

支持的operator state的第三種類型是廣播狀態。引入了廣播狀態以支持用例,其中需求將來自一個流的某些數據廣播到所有下游任務,廣播的狀態將存儲在本地,用於處理另一個流上的所有傳入數據。

A third type of supported operator state is the Broadcast State. Broadcast state was introduced to support use cases where some data coming from one stream is required to be broadcasted to all downstream tasks, where it is stored locally and is used to process all incoming elements on the other stream.

√non-keyed

case  class Rule(channel:String,threshold:Int)

case  class UserAction(id:String,name:String,channel:String,action:String)

case  class UserBuyPath(id:String,name:String,channel:String,path:Int)

package com.hw.demo06
import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.common.state.{MapState, MapStateDescriptor}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.scala._

/**
  * @aurhor:fql
  * @date 2019/10/17 20:58 
  * @type:
  */
class UserActionRichMapFunction extends RichMapFunction[UserAction,UserBuyPath]{

  var buyPathState:MapState[String,Int]=_

  override def open(parameters: Configuration): Unit = {
    val msd= new MapStateDescriptor[String,Int]("buy-path",createTypeInformation[String],createTypeInformation[Int])
    buyPathState=getRuntimeContext.getMapState(msd)
  }
  override def map(value: UserAction): UserBuyPath = {
         val channel=value.channel
         var path=0

        if(value.action.equals("buy")){
          buyPathState.remove(channel)
        }else{
          buyPathState.put(channel,path+1)
        }
    UserBuyPath(value.id,value.name,value.channel,buyPathState.get(channel))
  }
}

package com.hw.demo06


import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.common.state.{MapState, MapStateDescriptor}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.scala._


/**
  * @aurhor:fql
  * @date 2019/10/17 20:58 
  * @type:
  */
class UserActionRichMapFunction extends RichMapFunction[UserAction,UserBuyPath]{

  var buyPathState:MapState[String,Int]=_

  override def open(parameters: Configuration): Unit = {
    val msd= new MapStateDescriptor[String,Int]("buy-path",createTypeInformation[String],createTypeInformation[Int])
    buyPathState=getRuntimeContext.getMapState(msd) //獲取
  }


  override def map(value: UserAction): UserBuyPath = {
         val channel=value.channel  //讀取channel的值
         var path=0   //設定path的初始值

        if(value.action.equals("buy")){  //判斷動作是否是buy
          buyPathState.remove(channel)   //爲buy則交易已完成,則移除這個channel
        }else{ 
          buyPathState.put(channel,path+1)  //動作不爲buy,顧客還在觀望,path+1,存儲狀態
        }

    UserBuyPath(value.id,value.name,value.channel,buyPathState.get(channel))
  }
}

package com.hw.demo06

import org.apache.flink.api.common.state.MapStateDescriptor
import org.apache.flink.streaming.api.functions.co.BroadcastProcessFunction
import org.apache.flink.util.Collector
import scala.collection.JavaConverters._

/**
  * @aurhor:fql
  * @date 2019/10/17 20:54 
  * @type:
  */
class  UserBuyPathBroadcastProcessFunction(msd:MapStateDescriptor[String,Int]) extends BroadcastProcessFunction[UserBuyPath,Rule,String]{



  //處理的是UserBuyParh 讀取廣播狀態
  override def processElement(value: UserBuyPath,
                              ctx: BroadcastProcessFunction[UserBuyPath, Rule, String]#ReadOnlyContext,
                              out: Collector[String]): Unit = {
    val broadcastState = ctx.getBroadcastState(msd)  //進行廣播狀態的讀取
    if(broadcastState.contains(value.channel)){  //判斷廣播狀態中是否含有value的channel
      val threshold = broadcastState.get(value.channel)   //讀取輸入的channel閾值
      if(value.path>=threshold){  //判斷訪問的path是否大於閾值
        out.collect(value.id+" "+value.name+" "+value.channel+" "+value.path)  //輸出這條數據
      }
    }

  }
  //處理的是規則 Rule數據 ,記錄修改廣播狀態
  override def processBroadcastElement(value: Rule,
                                       ctx: BroadcastProcessFunction[UserBuyPath,
                                         Rule, String]#Context,
                                       out: Collector[String]): Unit = {
    val broadcastState = ctx.getBroadcastState(msd) //獲取廣播狀態
    broadcastState.put(value.channel,value.threshold) //將得到的channel和threshold存入廣播狀態中

    println("=========================")
    for(entry <- broadcastState.entries().asScala){  //遍歷廣播狀態
      println(entry.getKey+"\t"+entry.getValue)   //輸出channel和Threashold
    }
    println()
    println()
  }
}

package com.hw.demo06

import org.apache.flink.api.common.state.MapStateDescriptor
import org.apache.flink.streaming.api.datastream.BroadcastStream
import org.apache.flink.streaming.api.scala._

object FlinkStreamNonKeyedBroadCastState {
  def main(args: Array[String]): Unit = {
    var fsEnv=StreamExecutionEnvironment.getExecutionEnvironment
    // id   name    channel  action
    // 001 zhangsan 手機      view
    // 001 zhangsan 手機      view
    // 001 zhangsan 手機      addToCart
    // 001 zhangsan 手機      buy
    val userStream = fsEnv.socketTextStream("CentOS", 9999)
      .map(line => line.split("\\s+"))
      .map(ts => UserAction(ts(0), ts(1), ts(2), ts(3)))
      .keyBy("id", "name")
      .map(new UserActionRichMapFunction)  //狀態的存貯

    val msd=new MapStateDescriptor[String,Int]("braodcast-sate",createTypeInformation[String],
      createTypeInformation[Int])   
    // channel 閾值
    // 手機類 10
    val broadcastStream: BroadcastStream[Rule] = fsEnv.socketTextStream("CentOS", 8888)
      .map(line => line.split("\\s+"))
      .map(ts => Rule(ts(0), ts(1).toInt))
      .broadcast(msd)  // msd的廣播

    userStream.connect(broadcastStream)   //兩個流的connect
      .process(new UserBuyPathBroadcastProcessFunction(msd))
      .print()
    fsEnv.execute("testoperatorstate")
  }

}

keyed

class UserBuyPathKeyedBroadcastProcessFunction(msd:MapStateDescriptor[String,Int]) extends KeyedBroadcastProcessFunction[String,UserAction,Rule,String]{
  override def processElement(value: UserAction,
                              ctx: KeyedBroadcastProcessFunction[String, UserAction, Rule, String]#ReadOnlyContext,
                              out: Collector[String]): Unit = {
    println("value:"+value +" key:"+ctx.getCurrentKey)
    println("=====state======")
    for(entry <- ctx.getBroadcastState(msd).immutableEntries().asScala){
      println(entry.getKey+"\t"+entry.getValue)
    }
  }

  override def processBroadcastElement(value: Rule, ctx: KeyedBroadcastProcessFunction[String, UserAction, Rule, String]#Context, out: Collector[String]): Unit = {
     println("Rule:"+value)
    //更新狀態
    ctx.getBroadcastState(msd).put(value.channel,value.threshold)
  }
}
case class Rule(channel:String,threshold:Int)
case class UserAction(id:String,name:String ,channel:String,action:String)
var fsEnv=StreamExecutionEnvironment.getExecutionEnvironment
// id   name    channel  action
// 001 zhangsan 手機      view
// 001 zhangsan 手機      view
// 001 zhangsan 手機      addToCart
// 001 zhangsan 手機 buy
val userKeyedStream = fsEnv.socketTextStream("CentOS", 9999)
.map(line => line.split("\\s+"))
.map(ts => UserAction(ts(0), ts(1), ts(2), ts(3)))
.keyBy(0)//只可以寫一個參數


val msd=new MapStateDescriptor[String,Int]("braodcast-sate",createTypeInformation[String],
                                           createTypeInformation[Int])
// channel 閾值
// 手機類 10
// 電子類 10
val broadcastStream: BroadcastStream[Rule] = fsEnv.socketTextStream("CentOS", 8888)
.map(line => line.split("\\s+"))
.map(ts => Rule(ts(0), ts(1).toInt))
.broadcast(msd)

userKeyedStream.connect(broadcastStream)
.process(new UserBuyPathKeyedBroadcastProcessFunction(msd))
.print()


fsEnv.execute("testoperatorstate")

Checkpoint & SavePoints

Checkpoint 是Flink實現故障容錯一種機制,系統根據配置的檢查點定期自動對程序計算狀態進行備份。一旦程序計算過程中出現故障,系統會選擇一個最近的檢查點進行故障恢復。

SavePoint是一種有效運維手段,需要用戶手動觸發程序進行狀態備份。本質也是在做checkpoint。

實現故障恢復先決條件:

  • 持久(或持久)數據源,可以在一定時間內重複記錄。(FlinkKafkaConsumer)
  • 狀態的永久性存儲,通常是分佈式文件系統(例如,HDFS)
var fsEnv=StreamExecutionEnvironment.getExecutionEnvironment
//啓動檢查點機制
fsEnv.enableCheckpointing(5000,CheckpointingMode.EXACTLY_ONCE)
//配置checkpoint必須在2s內完成一次checkpoint,否則檢查點終止
fsEnv.getCheckpointConfig.setCheckpointTimeout(2000)
//設置checkpoint之間時間間隔 <=  Checkpoint interval
fsEnv.getCheckpointConfig.setMinPauseBetweenCheckpoints(5)
//配置checkpoint並行度,不配置默認1
fsEnv.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
//一旦檢查點不能正常運行,Task也將終止
fsEnv.getCheckpointConfig.setFailOnCheckpointingErrors(true)
//將檢查點存儲外圍系統 filesystem、rocksdb,可以配置在cancel任務時候,系統是否保留checkpoint
fsEnv.getCheckpointConfig.enableExternalizedCheckpoints(ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)
val props = new Properties()
props.setProperty("bootstrap.servers", "CentOS:9092")
props.setProperty("group.id", "g1")

fsEnv.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(),props))
.flatMap(line => line.split("\\s+"))
.map((_,1))
.keyBy(0)//只可以寫一個參數
.sum(1)
.print()

fsEnv.execute("testoperatorstate")

State backend

state backend決定Flink如何存儲系統狀態信息(Checkpoint形式),目前Flink提供了三種state backend實現。

  • Memory (jobmanager):這是Flink默認實現,通常用於測試,系統會將計算狀態存儲在JobManagwer的內存中,但是在實際生產環境下,由於計算的狀態比較大,使用Memory 很容易導致OOM(out of memory).
  • FileSystem:系統會將計算狀態存儲在TaskManager的內存中,因此一般用作生產環境,系統會更具checkpoin機制會將TaskManager狀態數據在文件系統上進行備份。如果是操大集羣規模,TaskManager內存也可能產生溢出。
  • RocksDB : 系統會將計算狀態存儲在TaskManager的內存中,如果TaskManager內存不夠,系統可以使用RocksDB配置本地磁盤完成狀態的管理,同時支持將本地的狀態數據備份到遠程文件系統,因此RocksDB backend 是推薦的選擇。

參考:https://ci.apache.org/projects/flink/flink-docs-release-1.9/ops/state/state_backends.html

每一個Job 都可以配置自己的狀態存儲後端實現,

var fsEnv=StreamExecutionEnvironment.getExecutionEnvironment
val fsStateBackend:StateBackend = new FsStateBackend("hdfs:///xxx") //MemoryStateBackend、FsStateBackend、RocksDBStateBackend
fsEnv.setStateBackend(fsStateBackend)

如果用戶不配置,系統則使用默認實現,默認實現可以通過flink-conf-yaml配置

[root@CentOS ~]# cd /usr/flink-1.8.1/
[root@CentOS flink-1.8.1]# vi conf/flink-conf.yaml
#==============================================================================
# Fault tolerance and checkpointing
#==============================================================================
# The backend that will be used to store operator state checkpoints if
# checkpointing is enabled.
#
# Supported backends are 'jobmanager', 'filesystem', 'rocksdb', or the
# <class-name-of-factory>.
#
 state.backend: rocksdb
# Directory for checkpoints filesystem, when using any of the default bundled
# state backends.
#
 state.checkpoints.dir: hdfs:///flink-checkpoints
# Default target directory for savepoints, optional.
#
 state.savepoints.dir: hdfs:///flink-savepoints
 
# Flag to enable/disable incremental checkpoints for backends that
# support incremental checkpoints (like the RocksDB state backend).
#
 state.backend.incremental: true

注: 必須在環境變量中出現HDOOP_CLASSPATH

Flink計算髮布之後,是否還能夠修改計算算子?

首先在Spark中這是不允許的,因爲Spark持久化代碼片段,一旦修改代碼,必須刪除checkpoint。但是Flink僅僅存儲的是各個算子的計算狀態,如果用戶修改代碼,需要用戶在有狀態的操作的算子上指定uid屬性。

fsEnv.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(),props))
    .uid("kakfa-consumer")
    .flatMap(line => line.split("\\s+"))
    .map((_,1))
    .keyBy(0)//只可以寫一個參數
    .sum(1)
    .uid("word-count") //唯一
    .map(t=>t._1+"->"+t._2)
    .print()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章