字节跳动埋点数据流建设与治理实践( 三 )


在介绍业务场景时 , 提到我们一个主要的需求是ETL规则的动态更新 , 那么我们来看一下埋点数据流FlinkETL任务是如何基于规则引擎支持动态更新的 , 如何在不重启任务的情况下 , 实时的更新上下游的Schema信息、规则的处理逻辑以及修改路由拓扑 。
字节跳动埋点数据流建设与治理实践
文章图片
首先 , 我们在流量平台上配置了上下游数据集的拓扑关系、Schema和ETL规则 , 然后通过ConfigCenter将这些元数据发送给FlinkETLJob , 每个FlinkETLJob的TaskManager都有一个MetaUpdater更新线程 , 更新线程每分钟通过RPC请求从流量平台拉取并更新相关的元数据 , Sourceoperator从MQTopic中消费到的数据传入ProcessFunction , 根据MQTopic对应的Schema信息反序列化为InputMessage , 然后进入到规则引擎中 , 通过规则索引算法匹配出需要运行的规则 , 每条规则我们抽象为一个Filter模块和一个Action模块 , Fliter和Action都支持UDF , Filter筛选命中后 , 会通过Action模块对数据进行字段的映射和清洗 , 然后输出到OutputMessage中 , 每条规则也指定了对应的下游数据集 , 路由信息也会一并写出 。
当OutputMessage输出到Slink后 , Slink根据其中的路由信息将数据发送到SlinkManager管理的不同的Client中 , 然后由对应的Client发送到下游的MQ中 。
规则引擎
规则引擎为埋点数据流ETL链路提供了动态更新规则的能力 , 而埋点数据流FlinkETLJob使用的规则引擎也经历了从Python到Groovy再到Janino的迭代 。
字节跳动埋点数据流建设与治理实践
文章图片
由于Python脚本语言本身的灵活性 , 基于Python实现动态加载规则比较简单 。 通过Compile函数可以将一段代码片段编译成字节代码 , 再通过eval函数进行调用就可以实现 。 但Python规则引擎存在性能较弱、规则缺乏管理等问题 。
迁移到JavaFlink后 , 在流量平台上统一管理运维ETL规则以及schema、数据集等元数据 , 用户在流量平台编辑相应的ETL规则 , 从前端发送到后端 , 经过一系列的校验最终保存为逻辑规则 。 引擎会将这个逻辑规则编译为实际执行的物理规则 , 基于Groovy的引擎通过GroovyClassLoader动态加载规则和对应的UDF 。 虽然Groovy引擎性能比Python引擎提升了多倍 , 但Groovy本身也存在额外的性能开销 , 因此我们又借助Janino可以动态高效地编译Java代码直接执行的能力 , 将Groovy替换成了Janino , 同时也将处理Protobuf数据时使用的DynamicMessage替换成了GeneratedMessage , 整体性能提升了10倍 。
除了规则引擎的迭代 , 我们在平台侧的测试发布和监控方面也做了很多建设 。 测试发布环节支持了规则的线下测试 , 线上调试 , 以及灰度发布的功能 。 监控环节支持了字段、规则、任务等不同粒度的异常监控 , 如规则的流量波动报警、任务的资源报警等 。
Flink拆分任务
规则引擎的应用解决了埋点数据流ETL链路如何快速响应业务需求的问题 , 实现了ETL规则的动态更新 , 从而修改ETL规则不需要修改代码和重启任务 。
字节跳动埋点数据流建设与治理实践
文章图片
但规则引擎本身的迭代、流量增长导致的资源扩容等场景 , 还是需要升级重启Flink任务 , 导致下游断流 。
除了重启断流外 , 大任务还可能在重启时遇到启动慢、队列资源不足或者资源碎片导致起不来等情况 。
针对这些痛点我们上线了Flink拆分任务 , 本质上是将一个大任务拆分为一组子任务 , 每个子任务按比例去消费上游Topic的部分Partition , 按相同的逻辑处理后再分别写出到下游Topic 。