import org.apache.flink.api.common.state.BroadcastState;
import org.apache.flink.api.common.state.MapState;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.api.common.state.ReadOnlyBroadcastState;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.BroadcastStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.co.KeyedBroadcastProcessFunction;
import org.apache.flink.util.Collector;
public class BroadcastAndTimer {
public static void main(String[] args) throws Exception {
// 创建flink执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 模拟两个输入流
// id,count
DataStreamSource<String> main = env.socketTextStream("localhost", 8888);
// id,name
DataStreamSource<String> line = env.socketTextStream("localhost", 9999);
// 广播状态是一种特殊的operatorState,创建状态描述器
MapStateDescriptor<String, String> mapStateDescriptor = new MapStateDescriptor<>("rule", String.class, String.class);
// 创建广播状态
BroadcastStream<String> broadcastStream = line.broadcast(mapStateDescriptor);
//非广播流和广播流进行关联
main.keyBy(e -> e.split(",")[0])
.connect(broadcastStream)
.process(new KeyedBroadcastProcessFunction<String, String, String, Tuple2<String, Integer>>() {
private MapState<String, Integer> countState;
// 定时器触发时执行
@Override
public void onTimer(long timestamp,
KeyedBroadcastProcessFunction<String, String, String, Tuple2<String, Integer>>.OnTimerContext ctx,
Collector<Tuple2<String, Integer>> out) throws Exception {
System.out.println("定时器被触发了,清除状态中的数据");
countState.clear();
}
// 处理非广播流
@Override
public void processElement(String value,
KeyedBroadcastProcessFunction<String, String, String, Tuple2<String, Integer>>.ReadOnlyContext ctx,
Collector<Tuple2<String, Integer>> out) throws Exception {
// 获取广播状态
ReadOnlyBroadcastState<String, String> state = ctx.getBroadcastState(mapStateDescriptor);
// 用于记录count的结果
countState = getRuntimeContext().getMapState(new MapStateDescriptor<String, Integer>("count", String.class, Integer.class));
// 将广播流和非广播流进行匹配,处理非广播流中数据 id/count
String[] fields = value.split(",");
String id = fields[0];
Integer count = Integer.valueOf(fields[1]);
// 获取广播流中的数据 id/name
String name = state.get(id);
// 注册处理时间定时器,设置触发时间为 1分钟
ctx.timerService().registerProcessingTimeTimer(System.currentTimeMillis() + 1000 * 60);
System.out.println(name + "=>有效时间1分钟");
// 读取历史数据中的 count
Integer oldCount = countState.get(name);
if (oldCount == null) {
oldCount = 0;
}
count += oldCount;
// 更新状态中的数据
countState.put(name, count);
out.collect(new Tuple2<String, Integer>(name, count));
}
// 处理广播流
@Override
public void processBroadcastElement(String value,
KeyedBroadcastProcessFunction<String, String, String, Tuple2<String, Integer>>.Context ctx,
Collector<Tuple2<String, Integer>> out) throws Exception {
// 获取广播状态
BroadcastState<String, String> broadcastState = ctx.getBroadcastState(mapStateDescriptor);
// 对状态进行处理
String[] fields = value.split(",");
// 将数据添加到广播状态中 id/name
broadcastState.put(fields[0], fields[1]);
}
}).print();
env.execute();
}
}
1)开启 8888 和 9999 端口,模拟两个输入流
1.开启两个端口
nc -lk 8888
nc -lk 9999
2.先输入9999端口数据
1,a
2,c
3,d
3.再输入8888端口数据
1,10
2,20
3,30
此时控制台输出结果为:
a=>有效时间1分钟
4> (a,10)
c=>有效时间1分钟
2> (c,20)
d=>有效时间1分钟
3> (d,30)
4.在定时器触发前,输入8888端口数据
1,100
2,100
3,100
此时控制台输出结果为:
a=>有效时间1分钟
4> (a,110)
c=>有效时间1分钟
2> (c,120)
d=>有效时间1分钟
3> (d,130)
5.待定时器触发后,输入8888端口数据
1,1000
2,2000
3,3000
此时控制台输出结果为:
定时器被触发了,清除状态中的数据
定时器被触发了,清除状态中的数据
定时器被触发了,清除状态中的数据
a=>有效时间1分钟
4> (a,1000)
c=>有效时间1分钟
2> (c,2000)
d=>有效时间1分钟
3> (d,3000)