flink stream broadcast广播变量

本文深入探讨了Apache Flink中的广播状态功能,包括其工作原理、使用限制以及如何在批处理和流处理中应用。通过具体示例展示了如何定义广播状态、将其与数据流连接以及在任务中使用。

Apache Flink 官方文档提供了广播状态的功能以及有关 API 的详细指南。在使用广播状态时要记住以下4个重要事项:

  • 使用广播状态,operator task 之间不会相互通信
  • 广播状态中事件的顺序在各个并发实例中可能不尽相同
  • 所有 operator task 都会快照下他们的广播状态
  • RocksDB 状态后端目前还不支持广播状态

广播变量创建后,它可以运行在集群中的任何function上,而不需要多次传递给集群节点。
另外需要记住,不应该修改广播变量,这样才能确保每个节点获取到的值都是一致的。

一句话解释,可以理解为是一个公共的共享变量,我们可以把一个dataset 数据集广播出去,然后不同的task在节点上都能够获取到,这个数据在每个节点上只会存在一份,节约内存。

用法
  • 1、定义一个MapStateDescriptor来描述我们要广播的数据的格式
  • 2、需要一个Stream来广播下游的operator
  • 3、添加数据源并把数据源注册成广播流
  • 4、连接广播流和处理数据的流
示例

1、flink batch

package flink.batch;

import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.functions.RichMapFunction;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.operators.DataSource;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * broadcast广播变量
 *
 * 需求:
 * flink会从数据源中获取到用户的姓名
 * 最终需要把用户的姓名和年龄信息打印出来
 *
 * 所以就需要在中间map处理的时候,就需要获取用户的年龄信息
 * 建议将用户的关系数据集使用广播变量进行处理
 *
 *
 * 注意:如果多个算子需要使用同一份数据集,那么需要在对应的多个算子后面分别注册广播变量
 */
public class BatchDemoBroadcast {
    public static void main(String[] args) throws Exception {
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

        //1、准备广播变量的数据
        ArrayList<Tuple2<String, Integer>> broadData = new ArrayList<>();
        broadData.add(new Tuple2<>("python",18));
        broadData.add(new Tuple2<>("scala",20));
        broadData.add(new Tuple2<>("java",17));
        DataSource <Tuple2 <String, Integer>> dataBroad = env.fromCollection(broadData);

        //2、对需要广播的数据进行处理,将tuple2类型转换成hashMap类型
        DataSet<HashMap <String, Integer>> baseData = dataBroad.map(new MapFunction <Tuple2 <String, Integer>, HashMap <String, Integer>>() {
            @Override
            public HashMap <String, Integer> map(Tuple2 <String, Integer> value) throws Exception {
                HashMap <String, Integer> res = new HashMap <>();
                res.put(value.f0, value.f1);
                return res;
            }
        });

        DataSet <String> mainData = env.fromElements("python", "java","java","kafka","scala","redis");

        DataSet <String> result = mainData.map(new RichMapFunction <String, String>() {
            List <HashMap <String, Integer>> broadCastMap = new ArrayList <HashMap <String, Integer>>();
            HashMap <String, Integer> allMap = new HashMap <String, Integer>();

            /**
             * 这个方法只会执行一次
             * 可以在这里实现一些初始化的功能
             *
             * 所以,就可以在open方法中获取广播变量数据
             *
             */
            @Override
            public void open(Configuration parameters) throws Exception {
                super.open(parameters);
                //3:获取广播数据
                this.broadCastMap = getRuntimeContext().getBroadcastVariable("broadCastMapName");
                for (HashMap map : broadCastMap) {
                    allMap.putAll(map);
                }

            }

            @Override
            public String map(String value) throws Exception {
                Integer age = allMap.get(value);
                return value + "," + age;
            }
        }).withBroadcastSet(baseData,"broadCastMapName");

        result.print();
    }
}

2、flink stream

package flink.stream.a
<think>我们面对的问题是如何在Apache FlinkProcessWindowFunction中传递和获取广播变量。根据Flink广播状态模式,广播变量是通过广播流(Broadcast Stream)传递的,然后在处理函数中访问。但是ProcessWindowFunction是一个窗口函数,通常用在窗口操作中,而广播状态通常与KeyedBroadcastProcessFunction或BroadcastProcessFunction结合使用。因此,我们需要考虑如何将广播状态与窗口处理结合起来。 思路: 1. 首先,我们需要创建一个广播状态描述符(BroadcastStateDescriptor),用于定义广播状态的名称和类型。 2. 然后,我们将一个流(通常是控制流)广播出去,形成广播流。 3. 将非广播流(数据流)与广播流连接起来,形成BroadcastConnectedStream。 4. 使用KeyedBroadcastProcessFunction(如果数据流是keyed)或BroadcastProcessFunction(如果数据流是non-keyed)来处理连接后的流。注意,ProcessWindowFunction不能直接访问广播状态,因此我们需要在窗口处理之前获取广播状态,或者在窗口处理函数之前使用支持广播状态的函数。 但是,用户特别提到了在ProcessWindowFunction中获取广播变量。由于ProcessWindowFunction本身并不直接支持广播状态,我们需要将广播状态的信息在窗口处理之前就准备好,并传递给窗口函数。 一种可行的方案是: - 使用KeyedBroadcastProcessFunction(假设数据流是keyed的)来处理广播流和数据流,并在其中维护每个key的广播状态。 - 然后,在KeyedBroadcastProcessFunction的processElement方法中,我们可以将广播状态作为上下文的一部分,传递给后续的窗口操作。但是,窗口操作是在KeyedBroadcastProcessFunction之后进行的,我们可以将广播状态作为数据记录的一部分传递下去,然后在ProcessWindowFunction中访问。 然而,需要注意的是,广播状态是全局的,而窗口操作是每个key和每个窗口的。在KeyedBroadcastProcessFunction中,我们可以访问到当前key的广播状态(只读),然后我们可以将广播状态的值附加到数据记录中,这样当数据记录到达窗口时,窗口函数就可以使用这个值了。 具体步骤: 步骤1:定义广播状态描述符 ```java BroadcastStateDescriptor<String, String> broadcastStateDescriptor = new BroadcastStateDescriptor<>("broadcastState", BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO); ``` 步骤2:将控制流广播 ```java // 假设controlStream是控制流,我们将其广播 BroadcastStream<String> broadcastControlStream = controlStream .broadcast(broadcastStateDescriptor); ``` 步骤3:连接数据流和广播流 ```java // 假设dataStream是数据流,并且已经keyBy DataStream<String> keyedDataStream = dataStream.keyBy(...); BroadcastConnectedStream<String, String> connectedStream = keyedDataStream.connect(broadcastControlStream); ``` 步骤4:使用KeyedBroadcastProcessFunction处理连接流 在KeyedBroadcastProcessFunction中,我们处理两种元素:数据流元素和广播流元素。 - 在processBroadcastElement中,我们更新广播状态。 - 在processElement中,我们读取广播状态(只读),然后将广播状态的值(或者部分值)与数据元素一起发送出去(输出一个新的元素,包含原数据和广播状态的值)。 然后,我们对这个处理后的流进行窗口操作,并在ProcessWindowFunction中使用之前传递的广播状态值。 示例代码: ```java DataStream<Tuple2<String, String>> enrichedStream = connectedStream .process(new KeyedBroadcastProcessFunction<String, String, String, Tuple2<String, String>>() { @Override public void processElement(String dataElement, ReadOnlyContext ctx, Collector<Tuple2<String, String>> out) throws Exception { // 获取广播状态(只读) ReadOnlyBroadcastState<String, String> broadcastState = ctx.getBroadcastState(broadcastStateDescriptor); // 假设我们只需要一个特定的广播状态值,这里用key "config" 示例 String configValue = broadcastState.get("config"); // 将数据元素和广播状态的值一起输出 out.collect(Tuple2.of(dataElement, configValue)); } @Override public void processBroadcastElement(String controlElement, Context ctx, Collector<Tuple2<String, String>> out) throws Exception { // 更新广播状态,这里简单示例:将控制元素作为新的配置值,存入"config"键 BroadcastState<String, String> broadcastState = ctx.getBroadcastState(broadcastStateDescriptor); broadcastState.put("config", controlElement); } }); ``` 步骤5:对enrichedStream进行窗口操作,并在ProcessWindowFunction中使用广播状态值 现在,enrichedStream的每个元素都包含了原始数据和广播状态的值(在Tuple2的第二个字段)。然后我们可以按照常规的窗口操作,在ProcessWindowFunction中访问这个值。 ```java enrichedStream.keyBy(...) .window(TumblingEventTimeWindows.of(Time.seconds(10))) .process(new MyProcessWindowFunction()); ``` 在MyProcessWindowFunction中,我们可以直接访问Tuple2的第二个字段,这就是广播状态的值。 注意:这种方法有一个限制,即广播状态的值在数据元素进入窗口时就已经确定,并且在窗口计算过程中不会改变(即使广播状态在窗口期间更新)。因为我们在进入窗口之前就已经将广播状态的值附加到了数据元素上。如果窗口计算过程中广播状态更新,并且希望窗口使用最新的广播状态,那么这种方法就不适用。在这种情况下,可能需要重新考虑设计,比如将窗口设置得非常小(但这可能不实用),或者不使用窗口函数,而使用KeyedBroadcastProcessFunction来模拟窗口行为。 另一种思路:如果窗口计算需要依赖最新的广播状态,并且窗口比较大,那么我们可以将广播状态作为ProcessWindowFunction的上下文信息,但是FlinkProcessWindowFunction并不直接支持访问广播状态。因此,我们可能需要将广播状态存储在某个全局可访问的地方(例如,在KeyedBroadcastProcessFunction中更新一个静态变量),然后在ProcessWindowFunction中读取这个静态变量。但是这种方法有状态一致性的问题,因为静态变量在多个并行实例中可能不同步,而且Flink的容错机制不会恢复静态变量的状态,所以不推荐。 因此,推荐的方法是第一种:在数据元素进入窗口之前,将广播状态的值附加到数据元素上。 总结:在ProcessWindowFunction中无法直接访问广播状态,但我们可以通过前处理(使用KeyedBroadcastProcessFunction)将广播状态的值作为数据元素的一部分传递下去,然后在窗口函数中使用。 引用说明: 根据引用[3],我们使用BroadcastProcessFunction或KeyedBroadcastProcessFunction,其中有两个方法:processElement和processBroadcastElement。我们在processElement中读取广播状态,然后输出包含广播状态值的新元素。这样,后续的窗口函数就可以使用这个值了[^3]。 相关问题: 1. 在Flink中,广播状态和窗口函数结合使用时,如何保证状态的一致性? 2. 如果广播状态在窗口计算过程中更新,如何让窗口函数使用最新的广播状态? 3. Flink广播状态有哪些使用限制?</think>### 在 FlinkProcessWindowFunction 中使用广播变量 在 Apache Flink 中,`ProcessWindowFunction` 本身**不支持直接访问广播变量**,因为广播状态机制是通过 `BroadcastProcessFunction` 或 `KeyedBroadcastProcessFunction` 实现的[^3]。但可以通过以下方法间接实现需求: #### 解决方案:结合 KeyedBroadcastProcessFunction 预处理 1. **定义广播状态描述符** ```java BroadcastStateDescriptor<String, Config> broadcastDescriptor = new BroadcastStateDescriptor<>("config", TypeInformation.of(Config.class)); ``` 2. **创建广播流** ```java BroadcastStream<Config> broadcastStream = configSource .broadcast(broadcastDescriptor); ``` 3. **连接数据流与广播流** ```java DataStream<Output> result = dataStream.keyBy(Data::getId) .connect(broadcastStream) .process(new KeyedBroadcastProcessFunction<Key, Data, Config, EnrichedData>() { @Override public void processElement(Data data, ReadOnlyContext ctx, Collector<EnrichedData> out) { // 获取广播状态(只读) ReadOnlyBroadcastState<String, Config> state = ctx.getBroadcastState(broadcastDescriptor); Config config = state.get("latest"); // 将广播变量注入数据 out.collect(new EnrichedData(data, config)); } @Override public void processBroadcastElement(Config config, Context ctx, Collector<EnrichedData> out) { // 更新广播状态 BroadcastState<String, Config> state = ctx.getBroadcastState(broadcastDescriptor); state.put("latest", config); } }) .keyBy(EnrichedData::getKey) .window(TumblingEventTimeWindows.of(Time.seconds(10))) .process(new ProcessWindowFunction<EnrichedData, Output, Key, TimeWindow>() { @Override public void process(Key key, Context ctx, Iterable<EnrichedData> elements, Collector<Output> out) { // 此时元素已包含广播变量 for (EnrichedData data : elements) { Config config = data.getConfig(); // 直接获取广播变量 out.collect(processWithConfig(data, config)); } } }); ``` #### 关键点说明 1. **预处理注入** 通过 `KeyedBroadcastProcessFunction` 将广播变量注入原始数据,生成 `EnrichedData`(包含数据+广播变量)[^3]。 2. **状态类型** - `ReadOnlyBroadcastState`: 数据流中只读(`processElement`) - `BroadcastState`: 广播流中可写(`processBroadcastElement`)[^3] 3. **窗口处理** `ProcessWindowFunction` 直接使用已包含广播变量的 `EnrichedData`,无需访问广播状态。 #### 注意事项 - **数据时效性**:窗口内元素使用进入窗口时的广播状态快照 - **容错保证**:广播状态通过 Checkpoint 自动恢复 - **并行度限制**:广播流并行度必须为 1(单播特性)[^3] > **替代方案**:对于简单场景,可将广播变量作为类的成员变量,但需自行处理状态一致性(不推荐)。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值