Qu'est-ce qu'un flux de diffusion ?
Diffusez une donnée à tous les nœuds. Utilisez dataStream.broadCast()
Scénarios d’utilisation du streaming diffusé ?
Généralement utilisé pour charger dynamiquement des éléments de configuration. Par exemple, mdr, il y a des gens qui se plaignent et signalent tous les jours, et le service client est tout simplement trop occupé. Tencent a estimé en interne que seuls les clients VIP3 et supérieurs recevront une réponse humaine individuelle à leurs plaintes. tandis que tout le monde a découvert que seules les personnes VIP3 auraient une réponse humaine. , tous ont commencé à déposer de l'argent sur VIP3. À cette époque, il y avait encore beaucoup de monde, donc seuls les clients sur VIP4 pouvaient répondre manuellement.
Les critères de jugement de vip3->vip4 changent constamment. À ce stade, un flux de diffusion est nécessaire. Comme il n’existe qu’une seule donnée à ce moment-là, plusieurs nœuds doivent recevoir ces données modifiées.
Comment utiliser le flux de diffusion ?
Généralement, un connect b.broadcast est exploité via connect confluence. a est le flux principal, qui est le flux de données, et b est le flux de changement de configuration.
Pas grand chose à dire, il suffit d'aller voir la démo, elle est utilisable dès la sortie de la boîte.
package com.chenchi.broadcast;
import org.apache.flink.api.common.state.BroadcastState;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.BroadcastStream;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.co.CoProcessFunction;
import org.apache.flink.streaming.api.functions.co.KeyedBroadcastProcessFunction;
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import org.apache.flink.util.Collector;
import java.util.HashMap;
import java.util.Random;
public class BroadCastStreamDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<Pattern> patternDataStream = env.addSource(new ChangeSource());
DataStream<User> userDataStream = env.addSource(new CustomerSource());
userDataStream.print("user");
patternDataStream.print("pattern");
//test1 直接合流 不广播。只会在一个节点更新。 用于特殊需求?
// userDataStream
// .keyBy(user -> user.userId)
// .connect(patternDataStream)
// .process(new CustomerSimpleProcess())
// .print();
//test2
// 定义广播状态的描述器,创建广播流 如何保存需要的广播数据呢 这个案例是通过map保留变化数据
// userDataStream
// .keyBy(user -> user.userId)
// .connect(patternDataStream.broadcast())
// .process(new CustomerSimpleProcess())
// .print();
//test3
MapStateDescriptor<Void, Pattern> bcStateDescriptor = new MapStateDescriptor<>(
"patterns", Types.VOID, Types.POJO(Pattern.class));
//通过描述器 更新
BroadcastStream<Pattern> broadcast = patternDataStream.broadcast(bcStateDescriptor);
userDataStream
.keyBy(user -> user.userId)
.connect(broadcast)
.process(new CustomerBroadCastProcess())
.print();
env.execute();
}
private static class CustomerBroadCastProcess extends KeyedBroadcastProcessFunction<Integer, User, Pattern, String> {
@Override
public void processElement(User user, KeyedBroadcastProcessFunction<Integer, User, Pattern, String>.ReadOnlyContext readOnlyContext, Collector<String> collector) throws Exception {
Integer userVip = user.getVip();
//获取广播流的数据 不是通过map保存
Pattern pattern = readOnlyContext.getBroadcastState(new MapStateDescriptor<>("patterns", Types.VOID, Types.POJO(Pattern.class))).get(null);
if (pattern!=null){
Integer patternVip = pattern.vip;
String result = "当前系统需要的vip等级=" + patternVip + ",用户id=" + user.userId + ",vip=" + userVip;
if (userVip>= patternVip){
result=result+"符合要求";
}else {
result=result+"不符合要求";
}
collector.collect(result);
}else {
System.out.println("pattern is null ");
}
}
@Override
public void processBroadcastElement(Pattern pattern, KeyedBroadcastProcessFunction<Integer,
User, Pattern, String>.Context context, Collector<String> collector) throws Exception {
BroadcastState<Void, Pattern> bcState = context.getBroadcastState(
new MapStateDescriptor<>("patterns", Types.VOID, Types.POJO(Pattern.class)));
// 将广播状态更新为当前的pattern
bcState.put(null, pattern);
}
}
public static class CustomerSimpleProcess extends CoProcessFunction<User, Pattern, String> {
ValueState<Integer> vip; //这个是保留主流的state的。 不是保留广播流的state
HashMap<String,Integer> vipMap;
@Override
public void open(Configuration parameters) throws Exception {
vip = getRuntimeContext().getState(new ValueStateDescriptor<>("vip", Integer.class));
vipMap=new HashMap<String,Integer>();
super.open(parameters);
}
@Override
public void processElement1(User user, CoProcessFunction<User, Pattern, String>.Context context, Collector<String> collector) throws Exception {
Integer userVip = user.getVip();
Integer patternVip = vipMap.getOrDefault("vip", 0);
String result = "当前系统需要的vip等级=" + patternVip + ",用户id=" + user.userId + ",vip=" + userVip;
if (userVip>=patternVip){
result=result+"符合要求";
}else {
result=result+"不符合要求";
}
collector.collect(result);
}
@Override
public void processElement2(Pattern pattern, CoProcessFunction<User, Pattern, String>.Context context, Collector<String> collector) throws Exception {
vipMap.put("vip",pattern.vip);
}
}
public static class User {
public Integer userId;
public Integer vip;
public User() {
}
public User(Integer userId, Integer vip) {
this.userId = userId;
this.vip = vip;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getVip() {
return vip;
}
public void setVip(Integer vip) {
this.vip = vip;
}
@Override
public String toString() {
return "Action{" +
"userId=" + userId +
", vip='" + vip + '\'' +
'}';
}
}
// 定义行为模式POJO类,包含先后发生的两个行为
public static class Pattern {
public Integer vip;
public Pattern() {
}
public Pattern(Integer vip) {
this.vip = vip;
}
@Override
public String toString() {
return "Pattern{" +
"vip='" + vip + '\'' +
'}';
}
}
private static class CustomerSource implements SourceFunction<User> {
boolean run = true;
@Override
public void run(SourceContext<User> sourceContext) throws Exception {
while (true) {
Integer userId = new Random().nextInt(1000);
Integer vip = new Random().nextInt(10);
sourceContext.collect(new User(userId, vip));
Thread.sleep(1000);
}
}
@Override
public void cancel() {
run = false;
}
}
private static class ChangeSource implements SourceFunction<Pattern> {
boolean run = true;
@Override
public void run(SourceContext<Pattern> sourceContext) throws Exception {
int i = 1;
while (true) {
sourceContext.collect(new Pattern(i++));
Thread.sleep(5000);
}
}
@Override
public void cancel() {
run = false;
}
}
}
Idée de démonstration : prenez le VIP ci-dessus comme exemple pour obtenir l'ID et le niveau VIP dont les utilisateurs se plaignent constamment. La base de données enregistre le niveau VIP qui peut bénéficier de services manuels. Ce niveau peut être ajusté par lui-même (dans mon cas, la clé primaire augmente avec le temps).
test1 ne diffuse pas
Faites attention au modèle :4 print vip=2 message, mais cela ne signifie pas que c'est le message reçu par la tâche 4. Nous voyons que >1 génère vip=2
Cependant, task10 et task9 sont toujours vip=0, indiquant que le flux n'est pas diffusé à moins que le parallélisme soit défini ici sur 1.
La carte test2 enregistre les données modifiées
test3 obtient des données via le descripteur
Identique à test2, mais veuillez noter que, comme les données des deux flux sont séquentielles, les informations utilisateur peuvent apparaître avant le modèle, il est donc recommandé de l'initialiser en premier, ou d'ajouter d'abord le flux de modèle.