spark 获取广播变量_Spark/Flink广播实现作业配置动态更新

本文聚焦于实时计算作业中动态改变配置的问题,介绍了Spark Streaming和Flink的广播机制。Spark Core原生广播变量不支持更新,需手动处理,且受微批次设计限制;Flink在1.5版本引入广播状态,可实现低延迟动态更新,还给出了相关代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者:LittleMagic

来源:jianshu/p/97dae75c266c

关键词:Spark Flink 广播变量

本文主要讲解Spark和Flink中如何实现动态广播的。

更多大数据架构、实战经验,欢迎关注【大数据与机器学习】,期待与你一起成长!

前言

在实时计算作业中,往往需要动态改变一些配置,举几个栗子:

  • 实时日志ETL服务,需要在日志的格式、字段发生变化时保证正常解析;
  • 实时NLP服务,需要及时识别新添加的领域词与停用词;
  • 实时风控服务,需要根据业务情况调整触发警告的规则。

那么问题来了:配置每次变化都得手动修改代码,再重启作业吗?答案显然是否定的,毕竟实时任务的终极目标就是7 x 24无间断运行。Spark Streaming和Flink的广播机制都能做到这点,本文分别来简单说明一下。

Spark Streaming的场合

dadb6f9d9f8975849934bc6727e1c6a3.png

Spark Core内部的广播机制: 广播变量(broadcast variable)的设计初衷是简单地作为只读缓存,在Driver与Executor间共享数据,Spark文档中的原话如下:

Broadcast variables allow the programmer to keep a read-only variable cached on each machine rather than shipping a copy of it with tasks. They can be used, for example, to give every node a copy of a large input dataset in an efficient manner.

也就是说原生并未支持广播变量的更新,所以我们得自己稍微hack一下。直接贴代码吧。

public class BroadcastStringPeriodicUpdater { private static final int PERIOD = 60 * 1000; private static volatile BroadcastStringPeriodicUpdater instance; private Broadcast broadcast; private long lastUpdate = 0L; private BroadcastStringPeriodicUpdater() {} public static BroadcastStringPeriodicUpdater getInstance() { if (instance == null) { synchronized (BroadcastStringPeriodicUpdater.class) { if (instance == null) { instance = new BroadcastStringPeriodicUpdater(); } } } return instance; } public String updateAndGet(SparkContext sc) { long now = System.currentTimeMillis(); long offset = now - lastUpdate; if (offset > PERIOD || broadcast == null) { if (broadcast != null) { broadcast.unpersist(); } lastUpdate = now; String value = fetchBroadcastValue(); broadcast = JavaSparkContext.fromSparkContext(sc).broadcast(value); } return broadcast.getValue(); } private String fetchBroadcastValue() { }}

这段代码将字符串型广播变量的更新包装成了一个单例类,更新周期是60秒。在Streaming主程序中,就可以这样使用了:

 dStream.transform(rdd -> { String broadcastValue = BroadcastStringPeriodicUpdater.getInstance().updateAndGet(rdd.context()); rdd.mapPartitions(records -> { }); });

这种方法基本上解决了问题,但不是十全十美的,因为广播数据的更新始终是周期性的,并且周期不能太短(得考虑外部存储的压力),从根本上讲还是受Spark Streaming微批次的设计理念限制的。接下来看看Flink是怎样做的。

Flink的场合

Flink中也有与Spark类似的广播变量,用法也几乎相同。但是Flink在1.5版本引入了更加灵活的广播状态(broadcast state),可以视为operator state的一种特殊情况。它能够将一个流中的数据(通常是较少量的数据)广播到下游算子的所有并发实例中,实现真正的低延迟动态更新。

下图来自Data Artisans(被阿里收购了的Flink母公司)的PPT,其中流A是普通的数据流,流B就是含有配置信息的广播流(broadcast stream),也可以叫控制流(control stream)。流A的数据按照keyBy()算子的规则发往下游,而流B的数据会广播,最后再将这两个流的数据连接到一起进行处理。

ffcc9fd6654e84147cbc1e991a9c7666.png

既然它的名字叫“广播状态”,那么就一定要有与它对应的状态描述符StateDescriptor。Flink直接使用了MapStateDescriptor作为广播的状态描述符,方便存储多种不同的广播数据。示例:

MapStateDescriptor broadcastStateDesc = new MapStateDescriptor<>( "broadcast-state-desc", String.class, String.class );

接下来在控制流controlStream上调用broadcast()方法,将它转换成广播流BroadcastStream。controlStream的产生方法与正常数据流没什么不同,一般是从消息队列的某个特定topic读取。

BroadcastStream broadcastStream = controlStream .setParallelism(1) .broadcast(broadcastStateDesc);

然后在DataStream上调用connect()方法,将它与广播流连接起来,生成BroadcastConnectedStream。

BroadcastConnectedStream connectedStream = sourceStream.connect(broadcastStream);
最后就要调用process()方法对连接起来的流进行处理了。如果DataStream是一个普通的流,
需要定义BroadcastProcessFunction,反之,如果该DataStream是一个KeyedStream,
就需要定义KeyedBroadcastProcessFunction。
并且与之前我们常见的ProcessFunction不同的是,它们都多了一个专门处理广播数据的方法
processBroadcastElement()。类图如下所示。
7183eff27dd8f57d59aab68303a15eb1.png

下面给出一个说明性的代码示例。

 connectedStream.process(new BroadcastProcessFunction() { private static final long serialVersionUID = 1L; @Override public void processElement(String value, ReadOnlyContext ctx, Collector out) throws Exception { ReadOnlyBroadcastState state = ctx.getBroadcastState(broadcastStateDesc); for (Entry entry : state.immutableEntries()) { String bKey = entry.getKey(); String bValue = entry.getValue(); // 根据广播数据进行原数据流的各种处理 } out.collect(value); } @Override public void processBroadcastElement(String value, Context ctx, Collector out) throws Exception { BroadcastState state = ctx.getBroadcastState(broadcastStateDesc); // 如果需要的话,对广播数据进行转换,最后写入状态 state.put("some_key", value); } });

可见,BroadcastProcessFunction的行为与RichCoFlatMapFunction、CoProcessFunction非常相像。其基本思路是processBroadcastElement()方法从广播流中获取数据,进行必要的转换之后将其以键值对形式写入BroadcastState。而processElement()方法从BroadcastState获取广播数据,再将其与原流中的数据结合处理。也就是说,BroadcastState起到了两个流之间的桥梁作用。

最后还有一点需要注意,processElement()方法获取的Context实例是ReadOnlyContext,说明只有在广播流一侧才能修改BroadcastState,而数据流一侧只能读取BroadcastState。这提供了非常重要的一致性保证:假如数据流一侧也能修改BroadcastState的话,不同的operator实例有可能产生截然不同的结果,对下游处理造成困扰。

更多大数据架构、实战经验,欢迎关注【大数据与机器学习】,期待与你一起成长!

点击更多,查看原文。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值