百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Flink 高级应用模式之应用模式的动态更新

csdh11 2024-11-30 14:14 20 浏览

文章来源:加米谷大数据

我们特意略过了关于如何初始化应用的规则,以及在运行时有哪些方法来更新这些规则的细节内容。在这篇文章中我们将具体介绍这些细节。你将学习如何将第一部分中描述的数据分区方法与动态配置结合起来使用。只要共同使用这两种模式,调整很多业务逻辑时就不用再重新编译代码和重新部署 Flink 作业了。

规则广播

首先我们来看一下先前定义的数据处理管道:

DataStream<Alert> alerts =
    transactions
        .process(new DynamicKeyFunction())
        .keyBy((keyed) -> keyed.getKey());
        .process(new DynamicAlertFunction())

DynamicKeyFunction 提供动态数据分区,而 DynamicAlertFunction 负责执行处理事务的主要逻辑,并根据已定义的规则发送警报消息。

本系列的第一篇文章简化了用例,并假定应用的规则集已预先初始化,可以通过 DynamicKeyFunction 中的 List访问。

public class DynamicKeyFunction
    extends ProcessFunction<Transaction, Keyed<Transaction, String, Integer>> {

  /* Simplified */
  List<Rule> rules = /* Rules that are initialized somehow.*/;
  ...
}

显然,在初始化阶段就可以直接在 Flink 作业的代码内部向这个列表添加规则(创建一个 List 对象,使用它的 add 方法)。这样做的主要缺点是每次修改规则后都需要重新编译作业。在现实的欺诈检测系统中规则会经常更改,因此从业务和运营角度来看,这种方法是不可接受的。我们需要另一种方式。

接下来是在上篇文章中引入的规则定义示例:

图 1:规则定义


上一篇文章提到,DynamicKeyFunction 使用 groupingKeyNames 来提取消息键。该规则第二部分中的参数由 DynamicAlertFunction 使用:它们定义所执行操作的实际逻辑及其参数(例如警报触发阈值)。这意味着在 DynamicKeyFunction 和 DynamicAlertFunction 中必须存在相同的规则。为了获得这个结果,我们将使用 Apache Flink 的数据分发广播机制。

下图展示了我们正在构建系统的最终作业图:

图 2:欺诈检测 Flink 作业的作业图


事务处理管道的主要模块有:

  • 事务源(Transaction Source),它并行消费来自 Kafka 分区的事务消息。
  • 动态键函数(Dynamic Key Function),使用一个动态键执行数据强化(enrichment)。后续的 keyBy 对这个动态键进行哈希处理,并在随后的运算符的所有并行实例之间对数据进行分区操作。
  • 动态警报函数 (Dynamic Alert Function),可生成一个数据窗口并基于该窗口创建警报。

Apache Flink 内部的数据交换

上面的作业图还指出了运算符之间的各种数据交换模式。为了解广播模式的工作机制,我们先走一小段弯路,讨论 Apache Flink 的分布式运行时中存在哪些消息传播方法。

  • 事务源之后的FORWARD连接意味着事务源运算符的一个并行实例消费的所有数据,都将精确传输到后续 DynamicKeyFunction 运算符的一个实例上。它还指出两个连接的运算符(在上述情况下为 12)并行度相同。此通信模式如图 3 所示。橙色圆圈表示事务,虚线矩形表示相联运算符的并行实例。

图 3:跨运算符实例传递的 FORWARD 消息


  • DynamicKeyFunction 和 DynamicAlertFunction 之间的HASH连接意味着,对于每个消息都将计算一个哈希码,并且消息将在下一个运算符的可用并行实例之间平均分配。需要使用 keyBy 从 Flink 显式“请求”这样的连接。

图 4:在运算符实例之间传递的哈希消息(通过keyBy)


  • REBALANCE分布是由对 rebalance() 的显式调用或并行度的更改(对于图 2 中的作业图而言,为 12->1)引起的。调用 rebalance() 会使数据以循环方式重新分区,并且在某些情况下可以帮助减轻数据偏斜。

图 5:跨运算符实例传递的 REBALANCE 消息


图 2 中的欺诈检测作业图包含一个附加数据源:规则源(Rules Source)。它还从 Kafka 消费。规则通过BROADCAST通道“混合”到主处理数据流中。在运算符之间传输数据的其他方法(例如 forward、hash 或 rebalance),会让每个消息只可在接收的运算符的并行实例之一中处理;相比之下,broadcast 会让每个消息在 broadcast stream 连接的运算符的所有并行实例的输入上可用。这使得 broadcast 方法适用于多种需要影响所有消息处理的任务,而无需考虑它们的键或源分区。

图 6:跨运算符实例传递的 BROADCAST 消息


注意:实际上 Flink 中有一些更特殊的数据分区方案,我们在这里没有提到。如果你想了解更多信息,请参阅 Flink有关流分区的文档。

广播状态模式

为了使用规则源,我们需要将其“连接”到主数据流:

// Streams setup
DataStream<Transaction> transactions = [...]
DataStream<Rule> rulesUpdateStream = [...]

BroadcastStream<Rule> rulesStream = rulesUpdateStream.broadcast(RULES_STATE_DESCRIPTOR);

// Processing pipeline setup
 DataStream<Alert> alerts =
     transactions
         .connect(rulesStream)
         .process(new DynamicKeyFunction())
         .keyBy((keyed) -> keyed.getKey())
         .connect(rulesStream)
         .process(new DynamicAlertFunction())

如你所见,可以调用 broadcast 方法并指定状态描述符,从任何常规流中创建广播流。Flink 假定在处理主数据流的事件时需要存储和检索广播的数据,因此总是从该状态描述符自动创建相应的广播状态(broadcast state)。这与其他的 Apache Flink 状态类型是不一样的,其他类型中你需要在处理函数的 open() 方法中对其进行初始化。另请注意,广播状态始终具有键值格式(MapState)。

public static final MapStateDescriptor<Integer, Rule> RULES_STATE_DESCRIPTOR =
        new MapStateDescriptor<>("rules", Integer.class, Rule.class);

连接到 rulesStream 会导致处理函数的签名发生某些变化。上一篇文章在这里做了一点简化,用的是 ProcessFunction。但是,DynamicKeyFunction 实际上是一个 BroadcastProcessFunction。

public abstract class BroadcastProcessFunction<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;

}

这里的区别在于增加了 processBroadcastElement 方法,规则流的消息将通过该方法到达。下面新版本的 DynamicKeyFunction 允许在运行时通过这个流,修改数据分配键的列表:

public class DynamicKeyFunction
    extends BroadcastProcessFunction<Transaction, Rule, Keyed<Transaction, String, Integer>> {


  @Override
  public void processBroadcastElement(Rule rule,
                                     Context ctx,
                                     Collector<Keyed<Transaction, String, Integer>> out) {
    BroadcastState<Integer, Rule> broadcastState = ctx.getBroadcastState(RULES_STATE_DESCRIPTOR);
    broadcastState.put(rule.getRuleId(), rule);
  }

  @Override
  public void processElement(Transaction event,
                           ReadOnlyContext ctx,
                           Collector<Keyed<Transaction, String, Integer>> out){
    ReadOnlyBroadcastState<Integer, Rule> rulesState =
                                  ctx.getBroadcastState(RULES_STATE_DESCRIPTOR);
    for (Map.Entry<Integer, Rule> entry : rulesState.immutableEntries()) {
        final Rule rule = entry.getValue();
        out.collect(
          new Keyed<>(
            event, KeysExtractor.getKey(rule.getGroupingKeyNames(), event), rule.getRuleId()));
    }
  }
}

在上面的代码中,processElement() 接收事务,而 processBroadcastElement() 接收规则更新。创建新规则后将按图 6 所示分配,并使用 processBroadcastState 将其保存在运算符的所有并行实例中。我们使用规则的 ID 作为存储和引用各个规则的键。我们不再迭代硬编码的 List,而是迭代动态更新的广播状态的条目。

在将规则存储在广播 MapState 中时,DynamicAlertFunction 遵循相同的逻辑。如第一部分中所述,processElement 输入中的每个消息会由一个特定规则处理,并通过 DynamicKeyFunction 带有相应 ID 的“预标记”。我们需要做的就是使用提供的 ID 从 BroadcastState 中检索相应规则的定义,并根据该规则所需的逻辑对其进行处理。在这一阶段,我们还将消息添加到内部函数状态,以便在所需的数据时间窗口上执行计算。我们将在欺诈检测系列的最后一篇文章中探讨如何做到这一点。

小结

本文,我们继续研究了使用 Apache Flink 构建的欺诈检测系统的用例。我们研究了在并行运算符实例之间分配数据的各种方式,而最重要的是探讨了广播状态。我们演示了如何通过广播状态模式提供的功能来配合和增强动态分区(本系列第一部分中介绍的一种模式)。在运行时发送动态更新的能力是 Apache Flink 的强大功能,适用于其他多种用例,例如控制状态(清除 / 插入 / 修复)、运行 A/B 实验或执行 ML 模型系数的更新等。

相关推荐

探索Java项目中日志系统最佳实践:从入门到精通

探索Java项目中日志系统最佳实践:从入门到精通在现代软件开发中,日志系统如同一位默默无闻却至关重要的管家,它记录了程序运行中的各种事件,为我们排查问题、监控性能和优化系统提供了宝贵的依据。在Java...

用了这么多年的java日志框架,你真的弄懂了吗?

在项目开发过程中,有一个必不可少的环节就是记录日志,相信只要是个程序员都用过,可是咱们自问下,用了这么多年的日志框架,你确定自己真弄懂了日志框架的来龙去脉嘛?下面笔者就详细聊聊java中常用日志框架的...

物理老师教你学Java语言(中篇)(物理专业学编程)

第四章物质的基本结构——类与对象...

一文搞定!Spring Boot3 定时任务操作全攻略

各位互联网大厂的后端开发小伙伴们,在使用SpringBoot3开发项目时,你是否遇到过定时任务实现的难题呢?比如任务调度时间不准确,代码报错却找不到方向,是不是特别头疼?如今,随着互联网业务规模...

你还不懂java的日志系统吗 ?(java的日志类)

一、背景在java的开发中,使用最多也绕不过去的一个话题就是日志,在程序中除了业务代码外,使用最多的就是打印日志。经常听到的这样一句话就是“打个日志调试下”,没错在日常的开发、调试过程中打印日志是常干...

谈谈枚举的新用法--java(java枚举的作用与好处)

问题的由来前段时间改游戏buff功能,干了一件愚蠢的事情,那就是把枚举和运算集合在一起,然后运行一段时间后buff就出现各种问题,我当时懵逼了!事情是这样的,做过游戏的都知道,buff,需要分类型,且...

你还不懂java的日志系统吗(javaw 日志)

一、背景在java的开发中,使用最多也绕不过去的一个话题就是日志,在程序中除了业务代码外,使用最多的就是打印日志。经常听到的这样一句话就是“打个日志调试下”,没错在日常的开发、调试过程中打印日志是常干...

Java 8之后的那些新特性(三):Java System Logger

去年12月份log4j日志框架的一个漏洞,给Java整个行业造成了非常大的影响。这个事情也顺带把log4j这个日志框架推到了争议的最前线。在Java领域,log4j可能相对比较流行。而在log4j之外...

Java开发中的日志管理:让程序“开口说话”

Java开发中的日志管理:让程序“开口说话”日志是程序员的朋友,也是程序的“嘴巴”。它能让程序在运行过程中“开口说话”,告诉我们它的状态、行为以及遇到的问题。在Java开发中,良好的日志管理不仅能帮助...

吊打面试官(十二)--Java语言中ArrayList类一文全掌握

导读...

OS X 效率启动器 Alfred 详解与使用技巧

问:为什么要在Mac上使用效率启动器类应用?答:在非特殊专业用户的环境下,(每天)用户一般可以在系统中进行上百次操作,可以是点击,也可以是拖拽,但这些只是过程,而我们的真正目的是想获得结果,也就是...

Java中 高级的异常处理(java中异常处理的两种方式)

介绍异常处理是软件开发的一个关键方面,尤其是在Java中,这种语言以其稳健性和平台独立性而闻名。正确的异常处理不仅可以防止应用程序崩溃,还有助于调试并向用户提供有意义的反馈。...

【性能调优】全方位教你定位慢SQL,方法介绍下!

1.使用数据库自带工具...

全面了解mysql锁机制(InnoDB)与问题排查

MySQL/InnoDB的加锁,一直是一个常见的话题。例如,数据库如果有高并发请求,如何保证数据完整性?产生死锁问题如何排查并解决?下面是不同锁等级的区别表级锁:开销小,加锁快;不会出现死锁;锁定粒度...

看懂这篇文章,你就懂了数据库死锁产生的场景和解决方法

一、什么是死锁加锁(Locking)是数据库在并发访问时保证数据一致性和完整性的主要机制。任何事务都需要获得相应对象上的锁才能访问数据,读取数据的事务通常只需要获得读锁(共享锁),修改数据的事务需要获...