NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

Flink 高级应用模式第一辑:欺诈检测系统案例研究

  • 2020-02-05
  • 本文字数:4172 字

    阅读完需:约 14 分钟

Flink高级应用模式第一辑:欺诈检测系统案例研究

在这个博客文章系列中,你将学习到三种用来构建流应用程序的强大的 Flink 模式:


  • 动态更新应用程序逻辑

  • 动态数据分区(混排),在运行时控制

  • 基于自定义窗口逻辑的低延迟警报(不使用窗口 API)


这些模式带来了更多使用静态定义的数据流实现的功能,并提供了满足复杂业务需求的构建块。


应用程序逻辑的动态更新允许 Flink 作业在运行时更改,而不会因停止和代码重新提交而导致停机。


动态数据分区提供了在运行时更改 Flink 分配事件和分组方式的能力。当使用可动态重新配置的应用程序逻辑构建作业时,往往会自然而然需求这样的能力。


自定义窗口管理展示了当原生窗口API与你的需求不完全匹配时,如何使用底层进程函数API。具体来说,你将学习如何在 Windows 上实现低延迟警报以及如何使用计时器限制状态增长。


这些模式都是建立在 Flink 核心功能的基础上的,但框架的文档可能不够一目了然,因为不用具体的场景举例的话,往往很难解释和展示这些模式背后的机制。所以我们将通过一个实际示例来展示这些模式,这个示例为 Apache Flink 提供了一个真实的使用场景,那就是一个欺诈检测引擎。我们希望本系列文章能帮助你将这些功能强大的方法放到自己的工具箱中,从而执行一些激动人心的新任务。


在该系列的第一篇博文中,我们将介绍这个演示应用程序的高级架构,描述它的各个组件及其交互。然后,我们将深入研究该系列中第一个模式的实现细节——动态数据分区


你可以在本地完整运行这个欺诈检测演示应用程序,并通过随附的 GitHub 存储库查看实现的细节。

欺诈检测演示

我们的欺诈检测演示的完整源代码都是开源的,可以在线获取。要在本地运行它,请访问以下存储库并按照自述文件中的步骤操作:


https://github.com/afedulov/fraud-detection-demo


你会看到该演示是一个自包含的应用程序——它只需要从源构建 docker 和 docker-compose,并且包含以下组件:


  • 带有 ZooKeeper 的 Apache Kafka(消息代理)

  • Apache Flink(应用程序集群

  • 欺诈检测 Web 应用


这款欺诈检测引擎的高级目标是消费一个金融交易流,并根据一组规则对其进行评估。这些规则会经常更改和调整。在实际的生产系统中,我们需要在运行时添加和删除它们,而不会因停止和重新启动作业而带来高昂的代价。


在浏览器中转到演示 URL 时,将显示以下 UI:



图 1:欺诈检测演示 UI


单击“Start”按钮后,你可以在左侧看到在系统中流动的财务交易的直观表示。可以使用顶部的滑块控制每秒生成的交易数。中间部分用来管理 Flink 评估的规则。在这里,你可以创建新规则以及发出控制命令,例如清除 Flink 的状态。


演示自带一组预定义的示例规则。你可以单击 Start 按钮,一段时间后就能观察到 UI 右侧部分中显示的警报。这些警报是 Flink 根据预定义规则针对生成交易流的评估结果。


我们的欺诈检测示例系统包含三大组件:


  • 前端(React)

  • 后端(SpringBoot)

  • 欺诈检测应用程序(Apache Flink)


主要元素之间的交互如图 2 所示。



图 2:欺诈检测演示组件


后端向前端公开了一个 REST API,用于创建/删除规则以及发布控制命令来管理演示应用的执行。然后通过一个“Control”Kafka 主题将这些前端动作转发给 Flink。后端还包括一个 Transaction Generator(交易生成器)组件,该组件通过单独的“Transactions”主题将模拟的汇款事件流发送到 Flink。由 Flink 生成的警报由后端的“Alerts”主题的消费,并通过 WebSockets 转发到 UI。


现在你已经熟悉了我们这款欺诈检测引擎的总体布局和目标,现在我们来详细介绍实现这种系统所需的条件。

动态数据分区

我们要研究的第一个模式是动态数据分区。


如果你过去曾经使用过 Flink 的 DataStream API,那么你无疑会熟悉 keyBy 方法。Keying 一个流会重排所有记录,以便将具有相同 key 的元素分配给同一分区。这意味着所有具有相同 key 的记录将由下一个运算符的同一个物理实例处理。


在典型的流应用程序中,key 的选择是固定的,由元素内的某些静态字段确定。例如,当构建一个简单的基于窗口的交易流聚合时,我们可能总是按交易账户 ID 进行分组。


DataStream<Transaction> input = // [...]DataStream<...> windowed = input  .keyBy(Transaction::getAccountId)  .window(/*window specification*/);
复制代码


这种方法是在众多用例中实现水平可扩展性的主要构建块。但如果应用程序试图在运行时提供业务逻辑的灵活性,这种方法还不够用。为了理解为什么会发生这种情况,我们首先以一个功能需求的形式为这款欺诈检测系统制定一个现实的示例规则定义:


“只要在一周内从同一付款人向同一收款人累计付款总额超过 1,000,000 美元,就会发出警报。”


在这个公式中,我们可以发现许多能够在新提交的规则中指定的参数,甚至可能稍后在运行时修改或调整它们:


  • 汇总字段(付款金额)

  • 分组字段(付款人+收款人)

  • 汇总函数(总和)

  • 窗口持续时间(1 周)

  • 限制(1000000)

  • 限制运算符(更大)


因此,我们将使用下面这样简单的 JSON 格式来定义上述参数:


{  "ruleId": 1,  "ruleState": "ACTIVE",  "groupingKeyNames": ["beneficiaryId", "payeeId"],  "aggregateFieldName": "paymentAmount",  "aggregatorFunctionType": "SUM",  "limitOperatorType": "GREATER",  "limit": 1000000,  "windowMinutes": 10080}
复制代码


在这里,重要的是要了解 groupingKeyNames 确定的是事件的实际物理分组——必须将具有相同指定参数值(例如,25 号付款人->12 号收款人)的所有交易汇总到评估运算符的同一个物理实例中。自然,在 Flink API 中以这种方式分发数据的过程是通过一个 keyBy()函数实现的。


Flink 的keyBy()文档)中的大多数示例都使用硬编码的 KeySelector,其会提取特定固定事件的字段。但是,为了支持所需的灵活性,我们必须根据规则的定义以更加动态的方式提取它们。为此,我们将不得不使用一个额外的运算符,该运算符为每个事件做准备以将其分发到正确的聚合实例。


在高级层面上,我们的主要处理管道如下所示:


DataStream<Alert> alerts =    transactions        .process(new DynamicKeyFunction())        .keyBy(/* some key selector */);        .process(/* actual calculations and alerting */)
复制代码


先前我们已经确定,每个规则都定义一个 groupingKeyNames 参数,该参数用来指定将哪些字段组合用于传入事件的分组。每个规则都可以使用这些字段的任意组合。同时,每个传入事件都可能需要根据多个规则进行评估。这意味着这些事件可能需要同时出现在与不同规则相对应的评估运算符的多个并行实例上,因此需要进行分叉。用 DynamicKeyFunction()来确保此类事件的分派。



图 3:具有动态 Key 函数的分叉事件


DynamicKeyFunction 迭代一组已定义的规则,并提取所需的分组 key 来为每个要由 keyBy()函数处理的事件作准备:


public class DynamicKeyFunction    extends ProcessFunction<Transaction, Keyed<Transaction, String, Integer>> {   ...  /* Simplified */  List<Rule> rules = /* 初始化的规则.                        细节会在未来的文章中讨论. */;
@Override public void processElement( Transaction event, Context ctx, Collector<Keyed<Transaction, String, Integer>> out) {
for (Rule rule :rules) { out.collect( new Keyed<>( event, KeysExtractor.getKey(rule.getGroupingKeyNames(), event), rule.getRuleId())); } } ...}
复制代码


KeysExtractor.getKey()使用反射来从事件中提取 groupingKeyNames 字段的必需值,并将它们组合为单个串联的字符串 key,例如“ {beneficiaryId = 25; payeeId = 12}”。Flink 将计算该 key 的哈希值,并将此特定组合的处理分配给集群中的特定服务器。这将跟踪 25 号付款人和 12 号收款人之间的所有交易,并在期望的时间窗口内评估定义的规则。


注意,这里引入了具有以下签名的包装类 Keyed,作为 DynamicKeyFunction 的输出类型


public class Keyed<IN, KEY, ID> {  private IN wrapped;  private KEY key;  private ID id;
... public KEY getKey(){ return key; }}
复制代码


该 POJO 的字段包含以下信息:wrapped 是原始交易事件,key 是使用 KeysExtractor 的结果,id 是导致事件分配的 Rule 的 ID(根据特定于规则的分组逻辑)。


这种类型的事件将成为主处理管道中 keyBy()函数的输入,并允许在实现动态数据混排的最后步骤中使用一个简单的 lambda 表达式作为一个(KeySelector)。


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


有了 DynamicKeyFunction,我们可以隐式复制事件,以便在 Flink 集群中并行执行各条规则评估。这样一来,我们获得了一个重要的属性——规则处理的水平可扩展性。我们的系统将能够通过向集群添加更多服务器来处理更多规则,也就是提高并行度。实现此属性的代价是重复数据,这可能会成为一个问题,具体取决于特定的参数集,例如传入数据速率、可用网络带宽和事件负载大小等。在实际场景中可以应用其他优化,例如合并具有相同 groupingKeyNames 的规则评估,或添加一个过滤层,以在处理特定规则时剥离所有字段中不需要的事件。

结束语

在这篇博文中,我们用一个示例用例(欺诈检测引擎)讨论了对 Flink 应用程序提供动态运行时更改能力的原因。我们描述了整体架构及其组件之间的交互,并提供了在 dockerized 设置中构建和运行示例欺诈检测应用程序的指引。然后,我们展示了将动态数据分区模式实现为第一个基础构建块以实现灵活的运行时配置的细节操作。


为了将重心放在描述模式的核心机制上,我们将 DSL 和基础规则引擎的复杂性降到了最低。走下去的话,不难想象我们会添加一些扩展,例如允许使用更复杂的规则定义,包括某些事件的过滤、逻辑规则链接以及其他更高级的功能。


在本系列的第二部分中,我们将描述规则如何进入正在运行的欺诈检测引擎。此外,我们将详细介绍管道的主要处理函数——DynamicAlertFunction()的实现细节。



图 4:端到端管道


在下一篇文章中,我们将看到如何在运行时利用 Flink 的广播流来帮助指导欺诈检测引擎中的处理(动态应用程序更新模式)。


原文链接


https://flink.apache.org/news/2020/01/15/demo-fraud-detection.html


公众号推荐:

跳进 AI 的奇妙世界,一起探索未来工作的新风貌!想要深入了解 AI 如何成为产业创新的新引擎?好奇哪些城市正成为 AI 人才的新磁场?《中国生成式 AI 开发者洞察 2024》由 InfoQ 研究中心精心打造,为你深度解锁生成式 AI 领域的最新开发者动态。无论你是资深研发者,还是对生成式 AI 充满好奇的新手,这份报告都是你不可错过的知识宝典。欢迎大家扫码关注「AI前线」公众号,回复「开发者洞察」领取。

2020-02-05 16:315540
用户头像
赵钰莹 InfoQ 主编

发布了 875 篇内容, 共 605.5 次阅读, 收获喜欢 2671 次。

关注

评论

发布
暂无评论
发现更多内容

AGI时代即将降临,现代化产业建设的出路又在何处?

加入高科技仿生人

低代码 AGI 现代化产业

Application Loader及Transporter App上传ipa外、可以在Windows上架iOS APP工具

雪奈椰子

ios打包

《中国奇谭》打动万千观众,一首歌道尽现代人心酸

HIFIVE音加加

音乐 音乐播放

火山引擎DataLeap:数据秒级生产,揭秘电商实时数仓最佳实践!

字节跳动数据平台

数据治理 电商 数据研发 企业号 3 月 PK 榜

前端有边界,但低代码没有

引迈信息

前端 后端 低代码 ChatGPT

大资管行业数字化转型解决方案 | 行业方案

袋鼠云数栈

大数据 数字化转型 解决方案

【3.10-3.17】写作社区优秀技术博文一览

InfoQ写作社区官方

热门活动 优质创作周报

保姆级教程!基于声网 Web SDK实现音视频通话及屏幕共享

声网

焱融为国家电网打造存算一体的融合基础架构 助推能源行业新基建

焱融科技

文件存储 容器存储 分布式文件存储 高性能存储 国家电网

共享订阅--MQTT 5.0新特性

EMQ映云科技

物联网 IoT mqtt 企业号 3 月 PK 榜 共享订阅

实战|网站监控如何做好监测点管理与内网数据采集

云智慧AIOps社区

安全 监控 监控宝 云智慧 网站监控

Macbook技巧,Type-c接口失灵怎么办

互联网搬砖工作者

专场直播预约 | KaiwuDB 离散制造业场景解决方案

KaiwuDB

数据库 KaiwuDB 离线制造业 行业解决发展

什么是安全文件传输

镭速

什么是智慧公厕?智慧公厕存在的意义!

光明源智慧厕所

智慧城市

8年Java架构师面试官教你正确的面试姿势,10W字面试题搞定春招!

小小怪下士

Java 程序员 后端 java面试

未来智安入选FreeBuf《CCSIP 2022中国网络安全行业全景册(第五版)》

未来智安XDR SEC

从“可用”到“好用” 京东云构建融合开放适配国产化应用的全栈产品矩阵

京东科技开发者

国产化 京东云 国产化替代 京东云峰会

阿里云AIoT物联网平台如何实现设备全球就近接入——设备接入类

阿里云AIoT

运维 监控 物联网 中间件 数据采集

全能代码编辑器:CodeRunner 最新激活版

真大的脸盆

Mac 代码编辑器 Mac 软件 代码编辑 编辑代码

基于声网 Flutter SDK 实现互动直播

声网

flutter

企业支出如何一眼看全局,用友BIP很在行

用友BIP

商旅费控

GuavaCache与物模型大对象引起的内存暴涨分析——设备管理运维类

阿里云AIoT

缓存 算法 监控 物联网 数据格式

物联网数据应用开发最佳实践——数据价值类

阿里云AIoT

数据挖掘 物联网 存储 数据管理 调度

解决运行VMWare虚拟机报错“打不开 /dev/vmmon:断裂管道”

互联网搬砖工作者

Tapdata Connector 实用指南:云原生数仓场景之数据实时同步到 Databend

tapdata

数据库 大数据

Matlab常用图像处理命令108例(八)

timerring

图像处理

PS 2023版本 24.2有哪些新功能?增加了哪些相机配置?

Rose

ps ps 2023 Photoshop 2023下载

浅谈DWS函数出参方式

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 3 月 PK 榜

浪潮inBuilder低代码平台社区版来了!

inBuilder低代码平台

开源 低代码 企业级低代码平台

如何通过Java更改Word中的页面大小和页面方向

在下毛毛雨

Java word 页面布局

Flink高级应用模式第一辑:欺诈检测系统案例研究_AI&大模型_Alexander Fedulov_InfoQ精选文章