摘要:本文整理自腾讯专家级工程师李天旺,在 Flink Forward Asia 2022 AI 特征工程专场的分享。本篇内容主要分为四个部分:
- 背景介绍
- 平台架构的实践
- 线上运营的挑战
- 质量保障
一、背景介绍
微信是国内较庞大而复杂的业务,平台上包括通讯、社交、短视频、支付、小程序、企业微信等等大的业务,小的业务也有上千个。逐利的黑灰产哪里流量多、能赚钱,他们就跑到哪里去,所以微信会被业务黑灰产给盯上,这时如果业务安全、风控没有做好,会让公司和用户蒙受很大的损失。
而我们风控团队的职责就是与这些黑灰产进行对抗。在对抗过程中,我们面对了很多挑战,主要有以下三点:
-
需要面对百万规模从业人员的黑产,而我们的人员远远低于这样数量级。
-
需要覆盖各个业务场景,如果哪个业务场景没有覆盖了,就会被利用。
-
业务安全还有特点就是对抗激烈。当我们做出打击后,坏人就会变招,绕过安全打击。这就需要我们快速调整策略,重新进行打击,它的要求非常快。此外,业务调整、环境变化等也需要快速调整策略来保证我们的业务,这一点比较关注效率。
了解了我们的挑战之后,再来看一看我们业务安全风控的流程,这里列举了四个重要的步骤。
- 第一步,分析,它分为事前风险点分析和事后分析。事前风险点分析指提前判断业务的风险点,做好防范。事后分析指发现问题的时候做 case 的一些分析行为。
- 第二步,特征开发,即开发需要使用的各个特征。
- 第三步,策略,包括规则或者模型。
- 第四步,运营,包括有处罚、客诉、预警、垄断、监控等。
从这个流程可以看出,特征开发在整个风控里是非常重要的一环。
策略和特征是唇齿相依的,没有好的数据特征,我们的策略无法达到预期效果。举一个大家感触都比较深的例子,就是银行卡连续输错三次密码就会被锁卡,或者今天就不能再使用了。这就是一个简单规则型的策略,即特征值达到了某个阈值后,然后进行某一种处理。从这个例子可以看出,特征是整个策略的基石,是非常重要的。
我们的特征平台建设主要经历了三个阶段。
-
第一阶段,我们写代码在 KV 上进行累计的服务,没有使用一些框架,所有的功能都是自己写代码实现的。这个阶段存在的问题就是,开发效率低,质量参差不齐。
-
第二阶段,我们使用了 Flink。Flink 有很多开箱即用的算子函数,比如窗口、双流 Join 等,它的功能会更加强大一点,但它有一定的学习门槛。我们在内部推广培训的过程中发现,它的效果不是特别理想。因为我们的安全策略人员不是 Java 栈,而是 C++和 Python 技术栈的,他们对写 SQL 还比较熟悉,但对 Flink 这种 Java 技术栈技术还不能很好的掌握。
所以我们找了几个熟悉 Flink 的人,来专门做实时特征开发。这样也会存在一个问题,就是当突然有很多紧急需求过来的时候,所有的需求都压到这几个人的身上,这几个人就会成为团队效率的瓶颈。为了解决团队效率的问题,我们开始建立了第三阶段。
- 第三阶段,我们建立了一站式的实时特征开发平台。采用画布+组件化的模式进行开发来降低门槛,提升效率。让那些安全策略人员即使没有 Flink 技术栈,也能够做实时特征开发。
从业务安全风控的特点出发,平台的特点包括以下四点:
-
业务场景多:平台接入的成本就要低。
-
强对抗:策略就会频繁变更,数据特征就要做到实时。
-
海量的行为数据:需要高性能的计算。
-
直接影响用户体验:数据质量要有保障。如果数据质量有问题,比如特征算的不准,风控的策略也有可能不准,就会导致误伤或者漏过了一些坏人作恶的行为。
总结起来,整个特征开发平台建设需要关注以下三个要点:
-
高效率。
-
高性能,我认为风控类系统对特征质量要求会比推荐类系统要求高一些。
-
高质量,我认为特征质量会比推荐类的系统要求要高一些。
二、平台架构的实践
我们以平台化的方式降低业务使用成本,利用开箱即用的能力组件解决场景问题。
首先我们拥有一站式的 WEB IDE,安全策略人员只需要在我们的 WEB IDE 上就能开发它的特征。同时,我们提供了很多能力组件,比如输入组件有实时输入、维表输入等等。计算组件有窗口计算、维表关联、排行榜、双流 Join、去重等等。通过输出组件可以快速对接到它们的服务里,比如对接到规则引擎、KV 的实时数仓、消息队列、Svrkit(RPC 的一个服务)。
那么这些能力组件又是怎么工作的呢?是由我们下层的服务引擎来支撑的。服务引擎主要分为两部分,分别是 SQL 适配引擎和统一计算 Pipeline 框架。
-
SQL 适配引擎是将用户计算的配置转换成 Flink SQL 的过程。
-
统一计算 Pipeline 框架是将输入、计算、输出等组件串起来变成 Flink Job。
最后是基础组件,它由我们的数据平台团队提供,包括 Flink 集群、Pulsar、Kafka 等等云上的组件。
上图展示的是我们画布的一站式
开发 IDE。左边是我们的组件,右边是我们的画布。组件包括数据输入、数据处理、数据输出等,用户通过拖拽就能复用这些能力,然后通过简单配置就完成它业务逻辑了。
这里有一个关键要点,通过连线来完成数据流的 Pipeline,从输入到计算到最后的输出,这样简单组装起来就可以完成它的业务逻辑过程。它的使用门槛非常低,即使是新人也能很快上手。
接下来我们来看一下数据源节点,用户只需要再画布上拖入一个数据源节点,然后配置一下就可以了。这里的配置非常简单,只需选择一个数据源表即可。没有繁琐的 DDL 的配置,由统一的元数据进行管理。
我们的计算节点有很多都是 SQL 节点,但我们的 SQL 节点是采用 SQL+配置化的方式来实现各种功能的,比如窗口、Join 等等。同时,我们还是采用 MySQL 的方式来写 SQL,降低业务的使用门槛,比如用户要写一个窗口统计,它只要按照 MySQL 写一个 group by,然后在右侧设置一下它的窗口就可以了。用户不需要学习流式 SQL 的窗口怎么写,以及窗口函数是怎样的。
同时,它还支持在线的 SQL 调试,自动提取 SQL 结果的 Schema 给下游使用。这样通过 SQL+配置的方式简化之后,用户的使用门槛就变得很低,同时又能满足业务需求的功能。
我们有一个支持 Python 代码片段的节点。
为什么要支持代码片段呢?因为有些数据的结构比较复杂,当我们要去对这些数据做解析的时候,用 SQL 很难实现,这时候只能通过写代码或是写函数的方式来实现。但我们的用户大多都是不熟悉 Flink 的人,所以写代码和写函数对于他们来说门槛就比较高了,因此我们做了 Python 代码片段的节点。
用户只需要拖拽 Python 代码片段节点,我们就能生成一个框架代码,且我们生成的框架代码没有引入除 Python 基础库以外的任何依赖,用户只需实现函数里的内容即可。这个函数有一个输入是 Dict,返回是 List。用户甚至也可以把代码复制到本地进行调试,也支持在线测试代码。配置完节点后,只要指定输出字段以及字段类型即可。然后注册 Schema,这个门槛很低。这个代码片段可以简单理解成 Flatmap 的过程。
那么我们的后台是怎么做的呢?我们将用户代码片段嵌入到 Python 的一个 UDF 里执行,然后再把 UDF 注册到 Flink 里,同时执行 UDF 的函数实现代码片段 Flatmap 的过程。
我们的输出节点设置的也比较简单,用户按照他们应用场景选择需要的输出节点,然后拖拽节点进行简单的配置就可以了。我们帮助用户做协议的转换、自动类型的转换、校验等等功能。同时还会提供一些自动化设置的能力,比如用户要输出一个数据到一个 MySQL 表,就会有很多字段需要做映射,如果字段名一样,我们会做一些自动映射,提升他们的效率。
接下来,看一下画布与纯 SQL 比较。我们在使用的时候,觉得拖拽式开发更加直观、易开发、易维护。
如果业务逻辑比较复杂,后期维护也会非常麻烦。比如像上图左侧这样密密麻麻的很多节点,如果要写成纯 Flink SQL,它的 SQL 脚本会非常长,甚至达到一两千行,后期维护 SQL 的时候就会非常麻烦。像上图左侧这样,每个节点都知道它要计算什么功能,这就会比较清晰。
因为 Flink SQL 与大家熟悉的 MySQL 语法还是有很多不同的,概念上有一些差别。为此我们做一个 SQL 适配引擎可以将 MySQL 的语法转换成 MySQL,让用户更快的上手,同时还可以分析用户 SQL,提供一些优化选项供用户选择。让不熟悉 Flink 的人也可以开发 Flink 计算,并达到一些优化的效果。此外,SQL 适配引擎也支持 SQL 调试,可以快速验证业务逻辑的正确性,提升开发效率,降低数据质量的风险。
我们看下上图中的例子。如果用户要写一个维表关联,就是 Lookup 计算,那么他只需要按 MySQL 语法写维表关联就行了,即一个 Join 语句。之后我们分析用户的 SQL,哪个是流式表,哪个是为表,然后把 SQL 解析之后再转换成 Flink SQL 到 Flink 里执行。这样用户不需要学习 Flink SQL,门槛会更低。
我们的 Pipeline 框架是将输入、处理、输出等组件转换为 Flink 算子,并对业务算子进行一些优化。例如输入节点,用户只需在页面选择即可,框架会帮助用户完成数据源的对接,设置 Schema、Watermark 水位、自动添加数据源的质量监控等等。处理节点也是类似,会帮助用户做很多复杂的事情。
还有一个非常重要的特点是,可以自动适配实现上下游的对接,这个在 Flink 里较难实现。
Pipeline 组件化设计带来好处包括以下三点:
提供常用组件,方便重用能力与快速开发,不同组件可以满足不同业务场景的需求。比如去重、窗口、排行榜,用户直接用这些组件即可完成配置,不用完整的学习 Flink 各个组件的能力。
开发可以方便的组装业务功能,它可以自由进行上下有连接。同时,我们也可以按组件做专业的优化,获得最大收益。自由组装的方式与其他平台的方式可能会由一些不同,比如 Flink 有一个 Interval Join 之后,它的 Watermark 会产生一个 delay,delay 的时间取决于 Interval Join 等待的时间。这样的场景,如果要经过一个 Interval Join 后,下游再做一个窗口计算,下一个的窗口的触发时间就会延迟。
延迟在我们的风控场景里是不可以接受的,因为风控对实时性要求比较高。如果数据不能及时到达,坏人有可能已经开始作恶了。通过我们的组件,Interval Join 之后我们可以重新设置 Watermark 生成一种策略。这种在纯 SQL 里是不可能实现的,只能通过写代码方式来实现。
最后我们来总结一下整个方案架构。
- 拖拽式页面开发:全流程数据处理 Pipeline 方式。
- 组件化:提供常用组件,方便重用能力与快速开发。
- 低门槛化:采用 MySQL 语法作为业务逻辑开发语言,并且提供代码片段节点和不同应用场景的输出节点等功能,降低学习门槛。
这样,即使是没有太多编程经验的用户也可以在平台上进行数据处理,享受高效便捷的开发体验。整个架构设计,我们非常注重效率和使用门槛,这就是我们整个的方案设计。
三、线上运营的挑战
我们在使用封装组件的时候,也针对很多应用场景做了优化。下面介绍几个组件的优化,包括窗口计算、Join 计算、实时数据源等方面。
Flink 的滑动窗口,每条记录过来之后都会注册这条记录所属的所有窗口中。比如用户一小时的登录测速,窗口长度是一小时,滑动步长是一分钟。用户只要登录一次,就会输出 60 条记录,且这 60 条记录的统计值都是 1。这个时候会重复的输出,所以在我们的安全风控里也会重复执行我们的策略。因为执行状态没有改变,所以执行策略结果是一样的;或是重复更新下游的存储 DB 等等。这就会对下游服务造成一定的压力,这是状态没有变化,重复输出的问题。
为了这个解决问题,我们做了一个滑动窗口增量输出的窗口,即一条记录过来,只注册这条记录进入和退出的两个窗口,其他窗口都不注册,这样就可以大大减少我们的输出,常常可达到减少 20 倍左右。
在风控场景中,我们常常需要统计用户维度的特征(按用户 key 进行统计),这种用户数据是不连续的,因此滑动窗口的长度越长、滑动步长越短,效果越明显。滑动窗口增量输出在风控场景里应用是非常多的。
Flink 的滚动和滑动窗口,都是在时间到了窗口结束的时候,将窗口内的所有结果统计并在一瞬间输出。比如窗口统计的 key 值达到几百万、上千万,他要瞬间把几百万、上千万的数据输出的话,输出到下游是 RPC 的服务或者是 KV 的存储。这样一瞬间产生大量的写入量写入到下游,就会对下游的系统稳定性造成一定的冲击。
为了解决这个问题,我们一开始采用了消息队列的方式,但这个方式有一个不好的地方,就是下游处理的速度不能很好的设置限制大小,如果限制大了,下游的负载比较高;如果限制小了,数据会积压,导致实时性不够。
为此我们做了一个平滑窗口,即按照窗口 key 的不同做时间偏移,使窗口的结束时间是打散的,这样输出就不会集中到某个时间点。比如用户 1 的窗口时间是 0~10,用户 2 就是 1~11,用户 3 就是 2~12,这样的话每个用户的窗口结束时间都不一样,输出就会比较平滑,但它的窗口长度是一样的。因为在风控里对窗口长度要求比较严格,对窗口开始时间要求不是那么严格,这就能很好的解决业务场景的问题。
通过优化后,输出曲线就从上面的脉冲式输出变成下面平滑的曲线了。一个场景输出瞬间峰值 80+万/s, 按 Uin 偏移后只有 5 万+/s 的输出,下游系统处理起来就会非常平顺了。
有一些业务场景它需要每秒关联几十万的数据,即每秒几十万查询 KV 的存储,它的查询量比较大。而 Flink Lookup Join 算子依赖上游的分区数,比如当上游是消息队列的时候,我们就无法直接扩容 Flink 作业来提升它的吞吐。因为如果上游有五个分区,下游只有五个 Join 算子才能拿到数据,同时缓存命中率也不高。
常见的解决方案是扩容上游的消息队列。但这个方法会对系统产生一定的影响,首先分期数增加的同时会增加它的连接数,其次会产生很多比较小的文件。为了解决这个问题,我们开始支持 hash Join 的优化算子,同时支持异步和批量查询。通过这种方法支撑我们这种业务场景需要每秒几十万的查询关联。
优化之后,因为是通过 hash 分发了,就可以扩容我们的 Flink 作业来提升它的吞吐能力,同时也提升了缓存的命中率,因为 hash 之后的缓存命中率会更高。对于用户来说,他只需在选择维表数据的时候开启 hash Join 策略即可,目前我们已默认打开了这种策略。
Flink 的双流 Join 只考虑多的多对多的场景,它会存储期间内的所有数据。但有一些应用场景,只需关心一对一或者一对多的场景。比如像一对一的场景,关联上的数据是不需要保存的,而一对多的场景,关联上多的那一侧数据也不需要保存,对此做了一些优化策略。
优化后,可以大幅的减小计算状态的存储。对用户来说,我们提供了一个组件化的优化策略供他们选择。默认是多对多,也可以选择一对多、一对一等等。
Flink 基于事件时间时,它的 Watermark 是根据时间时间增长的,没数据时不增长,这就会导特征不能及时计算输出,进而还会导致下游的风控策略也无法很好的执行,因为拿不到实时特征。对我们这边的影响还是比较大。
为此我们进行了一些优化,当数据源 idle 自动生成 Watermark 进行下发。即 idle 的时候,我们会生成一个系统层的 Watermark,Watermark 等于系统时间减去等待时间,然后 Watermark,推动整个工作流的 Watermark 增长。从而使得下游的窗口能够比较及时的输出。这样就算数据源不连续,我们也能得到实时特征。
四、质量保障
质量保障方面,我们主要做了两件事情,分别是 DO 分离和监控告警。
DO 分离是以开发生命周期为基础,制定严格流程规范。开发和测试阶段由业务开发团队负责,我们这边的安全策略人员主要负责需求、业务开发和逻辑自测等功能。测试完后提交上线,上线之后的过程由平台维护团队负责,主要做上线和服务阶段的一些监控等等。
为什么这么做呢?因为我们的业务团队对 Flink 不熟悉,对 Flink 很多运营指标也不能很好的把控,比如 Watermark 的延迟监控、内存指标监控等等。而 Flink 团队他对 Flink 很熟悉,可以快速判断系统的瓶颈在哪里,优化调整资源等等。所以我们做了这样的职责分离,更好的保障我们服务的稳定性。
监控方面,我们做了两个监控,分别是系统监控和数据监控。
- 系统监控,它由我们的数据平台技术团队提供。它支持集群基础组件监控和负载监控,比如内存、IO、网络等等;支持任务状态,比如重启、CK 等等。
- 数据监控,数据还是要通过数据监控来才能更好的把握。比如数据延迟,输入延迟是多少,输出延迟是多少等等;数据丢弃,迟到丢弃多少,脏数据丢弃多少等等;数据波动,突增突降,突然掉 0 等等。
上图展示的是部分监控指标。包括数据输入延迟是多少、数据输出延迟是多少、每天处理计算量等等。
我们未来的规划主要分为两个方面:
- 流批一体化,我们已经开始使用 Hudi 技术进行实践。这将有助于将流数据和批数据进行整合,实现更高效、更灵活的数据处理。
- 智能化运维,由于平台上将维护大量任务,我们希望能够通过智能化工具来判断任务扩缩容的容量大小。传统的人工判断方式效率较低,因此我们希望能够利用智能化工具来提高工作效率,实现更智能化的运维管理。
活动推荐
阿里云基于 Apache Flink 构建的企业级产品-实时计算 Flink 版现开启活动:
0 元试用 实时计算 Flink 版(5000CU*小时,3 个月内)
了解活动详情:https://click.aliyun.com/m/1000372333/