1.放送入門
オペレーター状態をサポートする3番目のタイプはブロードキャスト状態であり、1つのストリームからのデータの一部またはすべてをすべてのダウンストリームタスクにブロードキャストする必要があります。ブロードキャスト状態はローカルに保存され、別のストリームのすべての着信要素を処理するために使用されます。 。
2.APIの使用法
ブロガーは、key_broadcastとnonKeyed_broadcastをそれぞれ説明する2つのケースを共有しました。
要件の説明:eコマースシステムは、プロモーションを達成するために、ユーザーの閲覧記録を監視し、さまざまな商品のマーケティング活動のルールに従ってターゲットユーザーを選別する必要があります。
ケース1: nonKeyed_broadcast
(1)エンティティクラスの準備
package com.learn.noKeyedBroadcast;
/**
* 描述:商品类别的阈值
*/
public class Rule {
private String channel; //商品类别 如:家电
private Integer threshold;//达到浏览路径长度的阈值 如:该类商品浏览了5次
public Rule(String channel, Integer threshold) {
this.channel = channel;
this.threshold = threshold;
}
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
public Integer getThreshold() {
return threshold;
}
public void setThreshold(Integer threshold) {
this.threshold = threshold;
}
@Override
public String toString() {
return "Rule{" +
"channel='" + channel + '\'' +
", threshold=" + threshold +
'}';
}
}
package com.learn.noKeyedBroadcast;
import java.io.Serializable;
/**
* 描述:用户浏览商城产生的日志
*/
public class UserAction implements Serializable {
private String id; //商品编号 如:001
private String name; //商品名称 如:电视机
private String channel; //商品类别 如:家电
private String action; //用户行为 如:购买、添加购物车、浏览
public UserAction(String id, String name, String channel, String action) {
this.id = id;
this.name = name;
this.channel = channel;
this.action = action;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
@Override
public String toString() {
return "UserAction{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", channel='" + channel + '\'' +
", action='" + action + '\'' +
'}';
}
}
package com.learn.noKeyedBroadcast;
import java.io.Serializable;
/**
* 描述:对用户的浏览路径进行分类汇总的载体
*/
public class UserBuyPath implements Serializable {
private String id;
private String name;
private String channel;
private Integer path; //浏览同类别商品的路径长度
public UserBuyPath(String id, String name, String channel, Integer path) {
this.id = id;
this.name = name;
this.channel = channel;
this.path = path;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
public Integer getPath() {
return path;
}
public void setPath(Integer path) {
this.path = path;
}
@Override
public String toString() {
return "UserBuyPath{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", channel='" + channel + '\'' +
", path=" + path +
'}';
}
}
(2)APIの準備
package com.learn.noKeyedBroadcast;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.common.state.MapState;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.configuration.Configuration;
/**
* 描述:对用户行为进行统计,结果存储在state中,如果用户发生了购买行为,则将当前用户的状态清除
*/
public class UserActionRichMapFunction extends RichMapFunction<UserAction,UserBuyPath> {
private transient MapState<String,Integer> buyPathState;
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
MapStateDescriptor<String, Integer> descriptor =
new MapStateDescriptor<>(
"buy_path"
, TypeInformation.of(new TypeHint<String>() {}),
TypeInformation.of(new TypeHint<Integer>() {})
);
buyPathState = getRuntimeContext().getMapState(descriptor);
}
@Override
public UserBuyPath map(UserAction value) throws Exception {
String channel = value.getChannel();
Integer path = 0;
if(buyPathState.contains(channel)){
path = buyPathState.get(channel);
}
if(value.getAction().equals("buy")){
buyPathState.remove(channel);
}else{
buyPathState.put(channel,path+1);
}
return new UserBuyPath(value.getId(),value.getName(),value.getChannel(),buyPathState.get(channel));
}
}
package com.learn.noKeyedBroadcast;
import org.apache.flink.api.common.state.*;
import org.apache.flink.streaming.api.functions.co.BroadcastProcessFunction;
import org.apache.flink.util.Collector;
import java.util.Map;
/**
* 描述:通过print的方式将活动规则展示,交互用户行为数据,符合条件的数据通过out输出
*/
public class UserBuyPathBroadcastProcessFunction extends BroadcastProcessFunction<UserBuyPath,Rule,String> {
private MapStateDescriptor<String, Integer> mapState;
public UserBuyPathBroadcastProcessFunction(MapStateDescriptor<String, Integer> mapState) {
this.mapState = mapState;
}
//处理的是UserBuyPath,读取广播状态
@Override
public void processElement(UserBuyPath value, ReadOnlyContext ctx, Collector<String> out) throws Exception {
ReadOnlyBroadcastState<String, Integer> broadcastState = ctx.getBroadcastState(mapState);
if(broadcastState.contains(value.getChannel())){//如果有规则,尝试计算
Integer threshold = broadcastState.get(value.getChannel());
if(value.getPath() >= threshold){ //将满足条件的用户信息输出
out.collect(value.getId()+" "+value.getName()+" "+value.getChannel()+" "+value.getPath());
}
}
}
//处理的是规则 Rule 数据 ,记录修改广播状态
@Override
public void processBroadcastElement(Rule value, Context ctx, Collector<String> out) throws Exception {
BroadcastState<String, Integer> broadcastState = ctx.getBroadcastState(mapState);
broadcastState.put(value.getChannel(),value.getThreshold());
System.out.println("=======当前商家活动规则规则如下======");
Iterable<Map.Entry<String, Integer>> entries = broadcastState.entries();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey()+"\t"+entry.getValue());
}
}
}
(3)Flinkメインクラス
package com.learn.noKeyedBroadcast;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.api.common.state.StateTtlConfig;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.streaming.api.datastream.BroadcastStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer011;
import java.util.Properties;
public class Driver {
public static void main(String[] args) throws Exception {
//1、flink运行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1); //方便效果展示,全局并行度设置1
//2.1 模拟用户访问商城产生的日志
/* id name channel action
001 mack 手机 view
001 dell 手机 addToCart
* */
Properties properties = new Properties();
properties.setProperty("bootstrap.servers","centos:9092");
properties.setProperty("group.id", "aa");
FlinkKafkaConsumer011<String> kafkaSource0 = new FlinkKafkaConsumer011<>("aaaa", new SimpleStringSchema(), properties);
DataStreamSource<String> kafkaSource01 = env.addSource(kafkaSource0);
//2.2 模拟商家发放的优惠券或者促销活动
/*
channel 阈值
手机 5
服装 3
*/
Properties properties02 = new Properties();
properties02.setProperty("bootstrap.servers","centos:9092");
properties02.setProperty("group.id", "aa");
FlinkKafkaConsumer011<String> kafkaSource1 = new FlinkKafkaConsumer011<>("bbbb", new SimpleStringSchema(), properties02);
DataStreamSource<String> kafkaSource02 = env.addSource(kafkaSource1);
//3、用户访问生成的日志做统计
SingleOutputStreamOperator<UserBuyPath> useStream = kafkaSource01
.map(t -> t.split(" "))
.map(t -> new UserAction(t[0], t[1], t[2], t[3]))
.keyBy(t -> t.getId() + t.getName())
.map(new UserActionRichMapFunction());
//4、mapState作为商家活动规则的载体,模拟优惠券或者活动的时间
MapStateDescriptor<String, Integer> mapState = new MapStateDescriptor<>("braodcast-sate",
TypeInformation.of(new TypeHint<String>() {
}),
TypeInformation.of(new TypeHint<Integer>() {
})
);
StateTtlConfig ttlConfig = StateTtlConfig.newBuilder(Time.seconds(1))
.setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
.setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
.build();
mapState.enableTimeToLive(ttlConfig);
//5、将商家的活动规则进行广播
BroadcastStream<Rule> broadcastStream = kafkaSource02
.map(t -> t.split(" "))
.map(t -> new Rule(t[0], Integer.parseInt(t[1])))
.broadcast(mapState);
//6、数据处理
useStream.connect(broadcastStream)
.process(new UserBuyPathBroadcastProcessFunction(mapState))
.printToErr(); //筛选出的目标用户用打印模拟
env.execute("metricsCounter");
}
}
ケース2: key_broadcast
package com.learn.keyedBroadcast;
import com.learn.noKeyedBroadcast.Rule;
import com.learn.noKeyedBroadcast.UserAction;
import com.learn.noKeyedBroadcast.*;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.api.common.state.*;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.typeutils.MapTypeInfo;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.*;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.co.KeyedBroadcastProcessFunction;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer011;
import org.apache.flink.util.Collector;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
public class Driver {
public static void main(String[] args) throws Exception {
//1、flink运行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
//2.1 模拟用户访问商城产生的日志
/* id name channel action
001 mack 手机 view
* */
Properties properties = new Properties();
properties.setProperty("bootstrap.servers","centos:9092");
properties.setProperty("group.id", "aa");
FlinkKafkaConsumer011<String> kafkaSource0 = new FlinkKafkaConsumer011<>("aaaa", new SimpleStringSchema(), properties);
kafkaSource0.setStartFromLatest();
DataStreamSource<String> kafkaSource01 = env.addSource(kafkaSource0);
//2.2 模拟商家发放的优惠券或者促销活动
/*
channel 阈值
手机 5
*/
Properties properties02 = new Properties();
properties02.setProperty("bootstrap.servers","centos:9092");
properties02.setProperty("group.id", "aa");
FlinkKafkaConsumer011<String> kafkaSource1 = new FlinkKafkaConsumer011<>("bbbb", new SimpleStringSchema(), properties02);
kafkaSource1.setStartFromLatest();
DataStreamSource<String> kafkaSource02 = env.addSource(kafkaSource1);
//3、用户访问生成的日志做统计
KeyedStream<UserAction, String> useStream = kafkaSource01
.map(t -> t.split(" "))
.map(t -> new UserAction(t[0], t[1], t[2], t[3]))
.keyBy(t -> t.getId());
//4、mapState作为商家活动规则的载体,模拟优惠券或者活动的时间
MapStateDescriptor<String, Integer> mapState = new MapStateDescriptor<>("braodcast-sate",
TypeInformation.of(new TypeHint<String>() {
}),
TypeInformation.of(new TypeHint<Integer>() {
})
);
//5、将商家的活动规则进行广播
BroadcastStream<Rule> broadcastStream = kafkaSource02
.map(t -> t.split(" "))
.map(t -> new Rule(t[0], Integer.parseInt(t[1])))
.broadcast(mapState);
//6、数据处理
useStream.connect(broadcastStream)
.process(new KeyedBroadcastProcessFunction<String, UserAction, Rule, String>() {
private transient MapState<String,Integer> buyPathState; //用于存储用户的流量记录
@Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
super.open(parameters);
MapStateDescriptor<String, Integer> descriptor =
new MapStateDescriptor<>(
"buy_path"
, TypeInformation.of(new TypeHint<String>() {}),
TypeInformation.of(new TypeHint<Integer>() {})
);
buyPathState = getRuntimeContext().getMapState(descriptor);
}
@Override
public void processElement(UserAction value, ReadOnlyContext ctx, Collector<String> out) throws Exception {
Integer integer = buyPathState.get(value.getChannel()); //查看当前channel的路径长度是多少
int count;
if(null == integer){ //防止空指针异常
count = 1;
}else{
count = integer + 1;
}
buyPathState.put(value.getChannel(),count); //修改state当前channel的路径长度
ReadOnlyBroadcastState<String, Integer> broadcastState = ctx.getBroadcastState(mapState); //获取广播中的数据
Integer path = broadcastState.get(value.getChannel());
Integer noHuodong = 0;
if(null == path){
noHuodong = 10000;
}else{
noHuodong = path;
}
if(buyPathState.get(value.getChannel()) > noHuodong){ //数据发送至下游 并从state中删除
out.collect(value.toString());
buyPathState.remove(value.getChannel());
}
}
@Override
public void processBroadcastElement(Rule value, Context ctx, Collector<String> out) throws Exception {
//更新状态
BroadcastState<String, Integer> state = ctx.getBroadcastState(mapState);
state.put(value.getChannel(),value.getThreshold());
Iterable<Map.Entry<String, Integer>> entries = state.entries();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey()+"\t"+entry.getValue());
}
}
}).printToErr();
env.execute("metricsCounter");
}
}
2つのケースを共有することで、誰もがブロードキャストの使用法を基本的に理解していると思います。次に、ブロガーはBroadcastProcessFunctionとKeyedBroadcastProcessFunctionを分析して比較します。
public abstract class BroadcastProcessFunction<IN1, IN2, OUT> extends BaseBroadcastProcessFunction {
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 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;
}
同じ:最初に注意することは、これら2つの関数は、ブロードキャスト側のデータを処理するためのprocessBroadcastElement()と非ブロードキャスト側の要素を処理するためのprocessElement()を実装する必要があることです。次の内容はctxから取得できます。
(1)アクセスを許可するブロードキャスト状態:ctx.getBroadcastState(MapStateDescriptor <K、V> stateDescriptor)
(2)要素のタイムスタンプのクエリを許可:ctx.timestamp()、
(3)現在のウォーターマークを取得:ctx.currentWatermark()
(4)現在の処理時間を取得します:Ctx.currentProcessingTime()、および
(5)要素をサイド出力に送信します:ctx.output(OutputTag outputTag、X value)。
また、getBroadcastState()のstateDescriptorは.broadcast(stateDescriptor)と同じである必要があることに注意してください。
差:違いは、各人がブロードキャスト状態にアクセスするタイプにあります。ブロードキャスター(processBroadcastElement)にはこれへの読み取りおよび書き込みアクセス権があり、非ブロードキャスター(processElement)には読み取り専用アクセス権があります。その理由は、Flinkにはクロスタスク通信がないためです。したがって、操作のすべての並列インスタンスでブロードキャスト状態のコンテンツが同じであることを保証するために、ブロードキャスターへの読み取りおよび書き込みアクセスのみを提供し、ブロードキャスターはすべてのタスクで同じ要素を参照する必要があります。実行計算終了の入力要素は、すべてのタスクで同じです。このルールを無視すると、状態の一貫性の保証が破られ、一貫性のない結果が発生し、結果のデバッグが困難になることがよくあります。
最後に、KeyedBroadcastProcessFunctionはキー付きストリームで実行されるため、BroadcastProcessFunctionに適用できない特定の関数を公開します。以下は次のとおりです。
(1)ReadOnlyContextのprocessElement()メソッドは、flinkの基礎となるタイマーサービスにアクセスできます。これにより、イベントの登録やタイムタイマーの処理が可能になります。タイマーがトリガーされると、onTimer()が呼び出され、OnTimerContextはReadOnlyContextplusと同じ関数を公開する必要があります。トリガーされたタイマーがイベントなのか、時間を処理する機能なのかを問い合わせ、タイマーに関連付けられたキーを問い合わせます。
(2)ContextのprocessBroadcastElement()メソッドには、メソッドapplyToKeyedState(StateDescriptor <S、VS> stateDescriptor、KeyedStateFunction <KS、S>関数)が含まれています。これにより、登録済みのKeyedStateFunctionは、すべてのキーに適用されるすべての状態を、提供されたstateDescriptorに関連付けることができます。
注:注:タイマーは、「KeyedBroadcastProcessFunction」の「processElement()」でのみ登録できます。ブロードキャスト要素に関連付けられたキーがないため、processBroadcastElement()メソッドでは使用できません。
このAPIの使用について公式ウェブサイトに記載されている注意事項を共有するには、それを追加してください。
(1)クロスタスク通信がない:前述のように、これが、aのkeyed-BroadcastProcessFunctionのみがブロードキャスト状態のコンテンツを変更できる理由です。さらに、ユーザーは、すべてのタスクが各着信要素に対して同じ方法でブロードキャスト状態のコンテンツを変更することを確認する必要があります。そうしないと、タスクごとに内容が異なり、結果に一貫性がなくなる可能性があります。
(2)ブロードキャスト状態のイベントのシーケンスは、タスク間で異なる場合があります。ブロードキャストストリームの要素は、すべての要素が(最終的に)すべてのダウンストリームタスクに入ることを保証しますが、要素は各タスクから異なる順序で到着する場合があります。したがって、各着信要素のステータス更新は、着信イベントの順序に依存してはなりません。
(3)すべてのタスクがブロードキャスト状態をチェックします:チェックポイントが発生すると、すべてのタスクはブロードキャスト状態で同じ要素を持ちます(チェックポイントバリアは要素を越えません)が、すべてのタスクはブロードキャスト状態を指します。それらの1つだけ。これは、復元プロセス中に同じファイルからすべてのタスクを読み取らないようにする(したがって、ホットスポットを回避する)という設計上の決定ですが、この代償として、チェックポイント状態のサイズがp倍(=並列度)増加します。Flinkは、復元/ズーム後にデータが繰り返されたり失われたりしないことを保証します。同じかそれ以下の並列処理を使用したリカバリの場合、各タスクはチェックポイントステータスを読み取ります。拡張後、各タスクは独自の状態を読み取り、残りのタスク(p_new-p_old)は前のタスクのチェックポイントを周期的に読み取ります。
(4)RocksDB状態バックエンドはありません。ブロードキャスト状態は実行時にメモリに保持されるため、それに応じてメモリ構成を行う必要があります。これは、すべてのオペレーターの状態に適用されます。