Apache Flink Stream API之State & Fault Tolerance(章节四)

State & Fault Tolerance

针对于流处理的有状态function和operators可以存储流计算过程中的每个Event的计算状态。状态计算是构建精确操作不会或缺的板块。Flink需要获知计算节点的状态,从而使用checkpoint和savepoint机制实现数据的故障恢复和容错。其中Queryable State允许外部在Flink运行过程中查询数据状态,当用户使用State操作flink提供了state backend机制用于存储状态信息,其中计算状态可以存储在Java的堆内和堆外,这取决于采取的statebackend机制。配置Statebackend不会影响应用的处理逻辑。

State Backends

Flink提供了不同的Sate backend,用于指定状态的存储方式和位置。 根据您的State Backend,State可以位于Java的堆上或堆外。Flink管理应用程序的Sate,这意味着Flink处理内存管理(如果需要可能会溢出到磁盘)以允许应用程序保持非常大的状态。默认情况下,配置文件flink-conf.yaml确定所有Flink作业的状态后端。 但是,可以基于每个作业覆盖默认状态后端,如下所示。

var env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(...);

(1)MemoryStateBackend:state数据保存在java堆内存中,执行checkpoint的时候,会把state的快照数据保存到jobmanager的内存中,基于内存的state backend在生产环境下不建议使用。

(2)FsStateBackend:state数据保存在taskmanager的内存中,执行checkpoint的时候,会把state的快照数据保存到配置的文件系统中,可以使用hdfs等分布式文件系统。

(3)RocksDBStateBackend:RocksDB跟上面的都略有不同,它会在本地文件系统中维护状态,state会直接写入本地rocksdb中。同时它需要配置一个远端的filesystem uri(一般是HDFS),在做checkpoint的时候,会把本地的数据直接复制到filesystem中。fail over的时候从filesystem中恢复到本地。RocksDB克服了state受内存限制的缺点,同时又能够持久化到远端文件系统中,比较适合在生产中使用。

如果不做任何配置的情况下,系统使用的是MemoryStateBackend Sate

配置RocksDBStateBackend状态存储

[root@CentOS flink-1.8.0]# 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: false

测试savepoint

[root@CentOS flink-1.8.0]# ./bin/flink list -m CentOS:8081
------------------ Running/Restarting Jobs -------------------
27.04.2019 13:49:01 : 788af53fa5e8cc6d1d9e6381496727a9 : state tests (RUNNING)
--------------------------------------------------------------
[root@CentOS flink-1.8.0]# ./bin/flink cancel 
									-m CentOS:8081 
									-s 
									788af53fa5e8cc6d1d9e6381496727a9

-s 后面可以指定savepoint地址,如果没有给定则使用默认配置地址。

Checkpointing

Flink中的每个function和operator都可以是有状态的。 为了使状态容错,Flink需要检查状态。检查点允许Flink恢复流中的状态和位置,从而为应用程序提供与无故障执行相同的语义。 Flink的检查点机制与流和状态的持久存储交互。一般来说,它需要:

  • 持久化的数据源,可以在一定时间内重放记录。这种源的示例是持久消息队列(例如,Apache Kafka,RabbitMQ,Amazon Kinesis,Google PubSub)或文件系统(例如,HDFS,S3,GFS,NFS,Ceph,…)。
  • 状态的持久存储,通常是分布式文件系统(例如,HDFS,S3,GFS,NFS,Ceph,…)

默认情况下,检查点并未开启。要启用检查点,请在StreamExecutionEnvironment上调用enableCheckpointing(n),其中n是检查点间隔(以毫秒为单位)。

val env = StreamExecutionEnvironment.getExecutionEnvironment
env.enableCheckpointing(10000)//设置10秒钟系统自动chekpoint一次,记录状态

Checkpoint的其他参数包括:

  • exactly-once vs. at-least-once:您可以选择将模式传递给enableCheckpointing(n)方法,以便在两个保证级别之间进行选择。对于大多数应用来说,恰好一次是优选的。至少一次可能与某些超低延迟的应用程序相关。
env.enableCheckpointing(10000,CheckpointingMode.EXACTLY_ONCE)
  • checkpoint timeout:如果在指定的时间之前没有完成,则中止正在进行的checkpoint。
env.getCheckpointConfig.setCheckpointTimeout(2000)
  • minimum time between checkpoints:为了确保流应用程序在checkoint之间存在一定的时间间隔,可以定义checkpoint之间需要经过多长时间。如果将此值设置为例如5000,也就意味着下一轮checkpoint在开始Checpoint的时候至少要在上一次checkpoint结束再往后推迟至少5秒的时间。当配置该参数的时候,该参数的优先级别高于Checkpoint Interval.(默认系统只用一个checkpoint进程)
env.getCheckpointConfig.setMinPauseBetweenCheckpoints(5000)
  • number of concurrent checkpoints: 默认情况下,处于放置程序花费过多时间在checkpoint而导致影响流计算的时间,所以默认flink的只会一个进程做Checkpoint,当然用户可以设置多个Checkpoint线程,实现checkpoint重叠,这样在故障的时候恢复更加方便,但是会拉低程序的性能,该属性不能和minimum time between checkpoints连用。
env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
  • externalized checkpoints:默认情况下,检查点不会保留,仅用于从失败中恢复作业。取消程序时会删除它们。配置要保留的定期检查点。根据配置,当作业失败或取消时,不会自动清除这些保留的检查点。

ExternalizedCheckpointCleanup模式配置取消作业时检查点发生的情况:

RETAIN_ON_CANCELLATION:取消作业时保留检查点。请注意,在这种情况下,必须在取消后手动清理检查点状态。

DELETE_ON_CANCELLATION:取消作业时删除检查点。仅当作业失败时,检查点状态才可用。(默认)

val env = StreamExecutionEnvironment.getExecutionEnvironment

env.enableCheckpointing(5000,CheckpointingMode.EXACTLY_ONCE)
env.getCheckpointConfig.setCheckpointTimeout(2000)
env.getCheckpointConfig.setMinPauseBetweenCheckpoints(1000)
env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)
env.getCheckpointConfig.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION)

env.socketTextStream("CentOS",9999)

  .flatMap(line => for(i <- line.split("\\W+")) yield (i,1))
  .keyBy(0)
  .sum(1)
.print()
env.execute("state tests")

用户可以执行:

[root@CentOS flink-1.8.0]# ./bin/flink list -m CentOS:8081                                 Waiting for response...
------------------ Running/Restarting Jobs -------------------
27.04.2019 16:19:22 : 00abda5f7ac362b854f0e95d9a567bf5 : state tests (RUNNING)
--------------------------------------------------------------
No scheduled jobs.
[root@CentOS flink-1.8.0]# ./bin/flink cancel -m CentOS:8081 00abda5f7ac362b854f0e95d9a567bf5

用户可以在hdfs的目录下查看checkpoint内容
在这里插入图片描述
然后重启任务测试状态是否保存
在这里插入图片描述

会发现Savepoint和Checkpoint都可以做状态恢复,但是不同于Savepoint的是Checkpoint是自动触发,通过程序指定触发频率。SavePoint使用户手动显示调用,因此如果用户手动调用了savepoint 程序退出后,会自动的删除checkpoint的数据,也就意味着上诉配置的ExternalizedCheckpointCleanup策略不奏效(测试版本1.8.0)。

State状态分类

  • Keyed State:针对KeyedStream上的一些操作,基于key和其对应的状态
  • Operator State:针对一个non-keyed state操作的状态,例如FlinkKafkaConsumer就是一个Operators state,每个Kafka Conusmer实例管理该实例消费Topic分区和偏移量信息等。

上述两种状态可以managedraw形式存储state,其中managed状态表示状态的管理托管给Flink来确定数据结构(“ValueState”, “ListState”等),Flink在运行期间自动的使用checkpoint机制持久化计算状态。Flink所有的function都支持managed形式的 ,RawState是采取在Checkpoint的时候序列化字节持久化状态,Flink并不知道state的存储结构,仅仅是在定义Operators的时候才会使用raw形式存储状态。Flink推荐使用managed 形式,因为这样Flink在当任务并行度发生变化的时候状态是可以重新分发,并且有着更好的内存管理。

Managed Keyed State

状态计算

val env = StreamExecutionEnvironment.createLocalEnvironment()
     
      env.socketTextStream("localhost",9999)
        .flatMap(line => for(i <- line.split("\\W+")) yield (i,1))
        .keyBy(0)
        .map(new StateRichMapFunction())
        .print()
    
env.execute("state tests")
  • ValueState<T>: 该状态记录的是key中所包含的值,例如可以使用upate(T)更新值或者T value()获取值。
class StateRichMapFunction extends RichMapFunction[(String,Int),(String,Int)]{
    var valueState:ValueState[Int] = _
    override def map(value: (String, Int)): (String, Int) = {
        val v = valueState.value()
        var count=0
        if(v == null) {
            count=value._2
        }else{
            count = v + value._2
        }
        valueState.update(count)
        (value._1,count)
    }
    override def open(parameters: Configuration): Unit = {
        val context = getRuntimeContext
        var vsd=new ValueStateDescriptor[Int]("count",createTypeInformation[Int])
        valueState = context.getState(vsd)
    }
}
  • ListState<T>: 可以存储一系列的元素,可以使用add(T) 或者addAll(List)添加元素,Iterable get()获取元素,或者update(List)更新元素。
class StateRichMapFunction extends RichMapFunction[(String,Int),(String,Int)]{
    var listState:ListState[Int] = _
    override def map(value: (String, Int)): (String, Int) = {
        listState.add(value._2)
        (value._1,listState.get().asScala.sum)
    }
    override def open(parameters: Configuration): Unit = {
        val context = getRuntimeContext
        var lsd=new ListStateDescriptor[Int]("list",classOf[Int])
        listState = context.getListState(lsd)
    }
}
  • ReducingState<T>:保留一个值代表所有的数据的聚合结果,在创建的时候需要给定ReduceFunction实现计算逻辑,调用add(T)方法实现数据的汇总。
class StateRichMapFunction extends RichMapFunction[(String,Int),(String,Int)]{
    var reduceState:ReducingState[(String,Int)] = _
    override def map(value: (String, Int)): (String, Int) = {
      reduceState.add(value)
      reduceState.get()
    }
    override def open(parameters: Configuration): Unit = {
      val context = getRuntimeContext
      var rsd=new ReducingStateDescriptor[(String,Int)]("reduce",
        new ReduceFunction[(String, Int)] {
          override def reduce(v1: (String, Int), v2: (String, Int)): (String, Int) = {
            (v1._1,v1._2+v2._2)
          }
        }
        ,createTypeInformation[(String,Int)])
      reduceState = context.getReducingState(rsd)
    }
  }
  • AggregatingState<IN, OUT>:保留一个值代表数据的局和结果,在创建时候需要给一个AggregateFunction实现计算逻辑,提供add(IN)方法实现数据的累计 。
class StateRichMapFunction extends RichMapFunction[(String,Int),(String,Int)]{
    var aggState:AggregatingState[(String,Int),Int] = _
    override def map(value: (String, Int)): (String, Int) = {
        aggState.add(value)
        (value._1,aggState.get())
    }
    override def open(parameters: Configuration): Unit = {
      val context = getRuntimeContext
      var asd=new AggregatingStateDescriptor[(String,Int),Int,Int]("agg",
        new AggregateFunction[(String,Int),Int,Int] {
          override def createAccumulator(): Int = {
            0
          }

          override def add(value: (String, Int), accumulator: Int): Int = {
               value._2+accumulator
          }

          override def getResult(accumulator: Int): Int = {
            accumulator
          }

          override def merge(a: Int, b: Int): Int = {
            a+b
          }
        },createTypeInformation[Int])
      aggState = context.getAggregatingState(asd)
    }
}
  • FoldingState<T, ACC>:保留一个值代表所有数据的聚合结果。但是和ReduceState相反的是不要求聚合的类型和最终类型保持一致,在创建FoldingState的时候需要指定一个FoldFunction,调用add(T)方法实现数据汇总。FoldingState在Flink 1.4版本过时,预计可能会在后期版本废除。用户可以使用AggregatingState替换。
 class StateRichMapFunction extends RichMapFunction[(String,Int),(String,Int)]{
    var foldState:FoldingState[Int,Int] = _
    override def map(value: (String, Int)): (String, Int) = {
      foldState.add(value._2)
      (value._1,foldState.get())
    }
    override def open(parameters: Configuration): Unit = {
      val context = getRuntimeContext
      val fsd=new FoldingStateDescriptor[Int,Int]("mapstate",0,new FoldFunction[Int,Int]{
        override def fold(accumulator: Int, value: Int): Int = {
          accumulator+value
        }
      },createTypeInformation[Int])
      foldState = context.getFoldingState(fsd)
    }
  }
  • MapState<UK, UV>:存储一些列的Mapping,使用 put(UK, UV)和putAll(Map<UK, UV>)添加元素数据可以通过get(UK)获取一个值,可以通过entries(), keys()and values()方法查询数据。

以上所有的State都提供了一个clear()方法清除当前key所对应的状态值。需要注意的是这些状态只可以在一些带有state的接口中使用,状态不一定存储在内部,但可能驻留在磁盘或其他位置。

class StateRichMapFunction extends RichMapFunction[(String,Int),(String,Int)]{
    var mapState:MapState[String,Int] = _
    override def map(value: (String, Int)): (String, Int) = {
      var count=0;
      if(mapState.contains(value._1)){
         count=mapState.get(value._1)+value._2
       }else{
        count=value._2
      }
      (value._1,count)
    }
    override def open(parameters: Configuration): Unit = {
      val context = getRuntimeContext
      val msd=new MapStateDescriptor[String,Int]("mapstate",createTypeInformation[String],createTypeInformation[Int])
      mapState = context.getMapState(msd)
    }
  }
State Time-To-Live (TTL)

可以将生存时间(TTL)分配给任何类型的keyed状态。如果配置了TTL并且状态值已过期,则将尽力清除存储的值。所有状态集合类型都支持每个条目的TTL。这意味着列表元素和映射条目将独立到期。 为了使用状态TTL,必须首先构建StateTtlConfig配置对象。然后,可以通过传递配置在任何StateDescriptor中启用TTL功能:

 class StateRichMapFunction extends RichMapFunction[(String,Int),(String,Int)]{
    var foldState:FoldingState[Int,Int] = _
    override def map(value: (String, Int)): (String, Int) = {
      foldState.add(value._2)
      (value._1,foldState.get())
    }
    override def open(parameters: Configuration): Unit = {
      val context = getRuntimeContext
      
       //配置TTL 过期时间
      var ttlConfig = StateTtlConfig
        .newBuilder(Time.seconds(5))
        .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
        .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
        .build();

      val fsd=new FoldingStateDescriptor[Int,Int]("mapstate",0,new FoldFunction[Int,Int]{
        override def fold(accumulator: Int, value: Int): Int = {
          accumulator+value
        }
      },createTypeInformation[Int])
      fsd.enableTimeToLive(ttlConfig)
      foldState = context.getFoldingState(fsd)
    }
  }

配置有几个选项需要考虑: newBuilder方法的第一个参数是必需的,它是过期时间的值。 更新类型配置状态TTL刷新时(默认为OnCreateAndWrite):

  • StateTtlConfig.UpdateType.OnCreateAndWrite - 仅限创建和写入访问
  • StateTtlConfig.UpdateType.OnReadAndWrite - 也是读访问权限

状态可见性配置是否在读取访问时返回过期值(如果尚未清除)(默认为NeverReturnExpired):

  • StateTtlConfig.StateVisibility.NeverReturnExpired - 永远不会返回过期值
  • StateTtlConfig.StateVisibility.ReturnExpiredIfNotCleanedUp - 如果仍然可用则返回

在NeverReturnExpired的情况下,过期State不会被返回。另一个选项ReturnExpiredIfNotCleanedUp允许在清理之前返回过期状态。

  • 启用TTL功能会增加状态存储的成本,因为系统不仅仅要存储状态,还要连同最后一次修改的时间戳。
  • 目前仅支持 processing time的TTL,也就意味着过期时间是由计算节点管理的。
  • 如果以前的state没有启用TTL,在任务重启的时候也不可以使用TTL配置。否则系统会导致兼容性失败和StateMigrationException。

默认情况下,只有在明确读出过期值时才会删除过期值,例如通过调用ValueState.value()。这意味着默认情况下,如果未读取过期状态,则不会将其删除,从而可能导致状态不断增长。

Cleanup in full snapshot

此外,您可以在获取完整状态快照时激活清理,这将减小State的大小。该策略不是会减小本地的State,而是在爱加载State的时候不去加载已经过期的状态数据。它可以在StateTtlConfig中配置:

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

Incremental cleanup

另一种选择是逐步触发一些状态条目的清理。在用户每次访问操作state的时候系统会去迭代过期条目,如果发现该条目过期则将该条目删除

var ttlConfig = StateTtlConfig
    .newBuilder(Time.seconds(1))
    .cleanupIncrementally()
    .build();

目前只支持 Heap state backend,开启此策略会增加记录处理延迟。

Cleanup during RocksDB compaction

如果使用RocksDB状态后端,则另一种清理策略是激活Flink特定的压缩过滤器。 RocksDB定期运行异步压缩以合并状态更新并减少存储。 Flink压缩过滤器使用TTL检查状态条目的到期时间戳,并排除过期值。

var ttlConfig = StateTtlConfig
    .newBuilder(Time.seconds(1))
    .cleanupInRocksdbCompactFilter()
    .build();

Managed Operator State

使用托管Operator State,有状态函数可以实现更通用的CheckpointedFunction接口,或者ListCheckpointed <T extends Serializable>接口。

CheckpointedFunction

CheckpointedFunction接口提供对具有不同重新分发方案的非key状态的访问。它需要实现两种方法:

void snapshotState(FunctionSnapshotContext context) throws Exception;
void initializeState(FunctionInitializationContext context) throws Exception;

当Flink在进行Checkpoint时候,系统会调用snapshotState()方法,相应的initializeState()方法会在系统初始化该function的时候被调用。需要注意的是当function第一次被初始化的时候或者从早期的Checkpoint状态中进行恢复的时候都会调用该方法。

目前为止支持list-style风格的managed operator state。该状态需要一些列可序列化对象列表,这些对象彼此独立,因此在集群并行度发生变化的时候可以重新分配状态。目前支持状态分配方案两种:

  • 平均分配:每个operator都返回一个状态元素列表。整个状态在逻辑上是所有列表的串联。在恢复的时候系统化会均分状态
  • 联合分配:每个operator都返回一个状态元素列表。整个状态在逻辑上是所有列表的串联。在恢复/重新分配时,每个运算符都会获得完整的状态元素列表。

例如实现一个BufferSink缓冲数据

class BufferingSink extends SinkFunction[(String,Int)] with CheckpointedFunction{
    var listState:ListState[(String,Int)] = _
    var bufferList:ArrayBuffer[(String,Int)] =new ArrayBuffer[(String,Int)]

    override def invoke(value: (String, Int)): Unit = {
        bufferList += value
        if(bufferList.size >=10){
            println("缓冲数据:"+bufferList.mkString(","))
            bufferList.clear()
        }
    }
    override def snapshotState(context: FunctionSnapshotContext): Unit = {
        listState.clear()
        //将缓冲区的数据存储state
        listState.addAll(bufferList.asJava)
    }

    override def initializeState(context: FunctionInitializationContext): Unit = {
        val lsd = new ListStateDescriptor[(String,Int)]("buffer list",createTypeInformation[(String,Int)])
        listState = context.getOperatorStateStore.getListState(lsd)
        //如果是状态恢复
        if(context.isRestored){
            bufferList.clear()
            bufferList ++=(listState.get().asScala)
        }
    }
}
env.socketTextStream("CentOS",9999)
    .flatMap(line => for(i <- line.split("\\W+")) yield (i,1))
    .keyBy(0)
    .sum(1)

	.addSink(new BufferingSink)

env.execute("state tests")

查看任务列表

[root@CentOS flink-1.7.2]# ./bin/flink list -m CentOS:8081
------------------ Running/Restarting Jobs -------------------
27.04.2019 18:35:35 : af6becbe2c6578d769571db8a1263596 : state tests (RUNNING)
--------------------------------------------------------------
[root@CentOS flink-1.7.2]# ./bin/flink cancel af6becbe2c6578d769571db8a1263596 -s hdfs://CentOS:9000/savepoint -m CentOS:8081

可以点击HDFS查看保存点状态,这会存储关机前数据。
在这里插入图片描述
然后用户在使用界面尝试恢复关机前的状态,观察数据是否能恢复
在这里插入图片描述

ListCheckpointed

ListCheckpointed接口是CheckpointedFunction的一个更有限的变体,它仅支持在恢复时具有偶分裂再分配方案的列表样式状态。

List<T> snapshotState(long checkpointId, long timestamp) throws Exception;
void restoreState(List<T> state) throws Exception;

在snapshotState()上,运算符应该返回检查点的对象列表,restoreState必须在恢复时处理这样的列表。如果状态不可重新分区,则始终可以在snapshotState()中返回Collections.singletonList(MY_STATE)。与其他Operator相比,有状态的Source需要更多的关注。为了使状态和输出集合的更新成为原子(在故障/恢复时精确一次语义所需),用户需要从源的上下文获取锁定。

import java.{lang, util}
import java.util.Collections

import org.apache.flink.streaming.api.checkpoint.ListCheckpointed
import org.apache.flink.streaming.api.functions.source.{RichParallelSourceFunction, SourceFunction}
import scala.collection.JavaConversions._

class CounterSource extends RichParallelSourceFunction[Long]  with ListCheckpointed[java.lang.Long] {
    @volatile
    private var isRunning = true
    private var offset = 0L
    //快照数据
    override def snapshotState(checkpointId: Long, timestamp: Long): 
    util.List[lang.Long] = {
        Collections.singletonList(offset)
    }
    //恢复快照
    override def restoreState(state: util.List[java.lang.Long]): Unit = {
        for (s <- state) {
            offset = s
        }
    }
   //运行输出
    override def run(ctx: SourceFunction.SourceContext[Long]): Unit = {
        val lock = ctx.getCheckpointLock
        while (isRunning) {
            // output and state update are atomic
            lock.synchronized({
                Thread.sleep(1000)
                ctx.collect(offset)
                offset += 1
            })
        }
    }
    override def cancel(): Unit = {
        isRunning = false
    }
}
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.api.scala._

val env =StreamExecutionEnvironment.getExecutionEnvironment
env.addSource(new CounterSource)
.map(i=> i+" offset")
.print()
env.execute("测试案例")

Broadcast State

除了Keyed State和 Operator State 之外Flink第三种状态是广播State。引入广播State是为了支持这样的场景:Broadcast State是Flink支持的一种Operator State。使用Broadcast State,可以在Flink程序的一个Stream中输入数据记录,然后将这些数据记录广播(Broadcast)到下游的每个Task中,使得这些数据记录能够为所有的Task所共享,比如一些用于配置的数据记录。这样,每个Task在处理其所对应的Stream中记录的时候,读取这些配置,来满足实际数据处理需要。

Broadcast State API

通常,我们首先会创建一个Keyed或Non-Keyed的Data Stream,然后再创建一个Broadcasted Stream,最后通过Data Stream来连接(调用connect方法)到Broadcasted Stream上,这样实现将Broadcast State广播到Data Stream下游的每个Task中。 如果Data Stream是Keyed Stream,则连接到Broadcasted Stream后,添加处理ProcessFunction时需要使用KeyedBroadcastProcessFunction来实现,下面是KeyedBroadcastProcessFunction的API,代码如下所示:

public abstract class KeyedBroadcastProcessFunction<KS, IN1, IN2, OUT> {

    public abstract void processElement(IN1 value, ReadOnlyContext ctx, Collector<OUT> out) throws Exception;

    public abstract void processBroadcastElement(IN2 value, Context ctx, Collector<OUT> out) throws Exception;

    public void onTimer(long timestamp, OnTimerContext ctx, Collector<OUT> out) throws Exception;
}

上面泛型中的各个参数的含义,说明如下:

  • KS:表示Flink程序从最上游的Source Operator开始构建Stream,当调用keyBy时所依赖的Key的类型;
  • IN1:表示非Broadcast的Data Stream中的数据记录的类型;
  • IN2:表示Broadcast Stream中的数据记录的类型;
  • OUT:表示经过KeyedBroadcastProcessFunction的processElement()和processBroadcastElement()方法处理后输出结果数据记录的类型。

如果Data Stream是Non-Keyed Stream,则连接到Broadcasted Stream后,添加处理ProcessFunction时需要使用BroadcastProcessFunction来实现,下面是BroadcastProcessFunction的API,代码如下所示:

public abstract class BroadcastProcessFunction<IN1, IN2, OUT> extends BaseBroadcastProcessFunction {
    public abstract void processElement(final IN1 value, final ReadOnlyContext ctx, final Collector<OUT> out) throws Exception;
    public abstract void processBroadcastElement(final IN2 value, final Context ctx, final Collector<OUT> out) throws Exception;
}

上面泛型中的各个参数的含义,与前面KeyedBroadcastProcessFunction的泛型类型中的后3个含义相同,只是没有调用keyBy操作对原始Stream进行分区操作,就不需要KS泛型参数。

val env = StreamExecutionEnvironment.createLocalEnvironment()

val stream1 = env.socketTextStream("localhost", 9999)
.flatMap(_.split(" "))
.map((_, 1))
.setParallelism(3)
.keyBy(_._1)

val mapStateDescriptor = new MapStateDescriptor[String,(String,Int)]("user state",BasicTypeInfo.STRING_TYPE_INFO,createTypeInformation[(String,Int)])

val stream2 = env.socketTextStream("localhost", 8888)
.map(line => (line.split(",")(0), line.split(",")(1).toInt))
.broadcast(mapStateDescriptor)

stream1.connect(stream2).process(new KeyedBroadcastProcessFunction[String,(String,Int),(String,Int),String]{
    override def processElement(in1: (String, Int), readOnlyContext: KeyedBroadcastProcessFunction[String, (String, Int), (String, Int), String]#ReadOnlyContext, collector: Collector[String]): Unit = {
        val mapBroadstate = readOnlyContext.getBroadcastState(mapStateDescriptor)
        println("in1:"+in1)
        println("---------state---------")
        for (i <- mapBroadstate.immutableEntries()){
            println(i.getKey+" "+i.getValue)
        }
    }

    override def processBroadcastElement(in2: (String, Int), context: KeyedBroadcastProcessFunction[String, (String, Int), (String, Int), String]#Context, collector: Collector[String]): Unit = {
        context.getBroadcastState(mapStateDescriptor).put(in2._1,in2)
    }
}).print()
env.execute("测试广播状态")

更多精彩内容关注

微信公众账号

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值