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分区和偏移量信息等。
上述两种状态可以managed 和 raw形式存储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("测试广播状态")