摘要: 随着社会消费模式以及经济形态的发展变化,将催生新的商业模式。腾讯新闻作为一款集游戏、教育、电商等一体的新闻资讯平台、服务亿万用户,业务应用多、数据量大。加之业务增长、场景更加复杂,业务对实时计算高可靠、可监控、低延时、数据可回溯的要求也越来越迫切。比如新闻广告投放、停单、在线推荐、电商搜索中,更快的响应用户需求、精准计费停单,意味着着更好的用户体验和更多的收入。
接下来我们将介绍基于腾讯云流计算 Oceanus Flink 平台、PipeLine 设计模式搭建的实时数据仓库思想。该方案已经落地内容商业化新闻如广告实时广告停单、实时报表、实时特征计算、游戏联运行为分析、数据异常检测等场景。
目前商业化数据主要承担了腾讯新闻客户端、体育、新闻插件、创新等业务的收入、广告数据的采集、处理、计算分析工作。在数据上报规范化、数据的准确性、数据的高质量、系统高可用性、开发成本与效率等方面做了很多工作。
伴随着腾讯新闻商业化业务增长,原先采用自行搭建的 Storm 集群,运维成本高、扩容等极为不方便、经常出现性能不足等问题;加之业务对实时性要求和逻辑复杂度的提高, Storm 在开发灵活性、低延时、扩展性难以满足现有的应用场景。
在此背景下,决定将原实时计算迁移至公司流计算 Oceanus Flink 平台并进行代码重构,搭建一套灵活通用的实时数据仓库,以支持团队在多业务场景的快速发展。
0-1搭建新闻商业化实时数仓,面临如下些挑战:
1、 迁移复杂:代码复用少、新系统与原数据核对需要保证线上业务逻辑不变情况下平稳切换新的数仓;
2、 需求多复杂:腾讯新闻商业化数据仅广告请求事件数据量高达几百亿/天,需求方有后台、搜索/推荐算法组以及运营决策方等,需要快速接入流式引擎参与实时分析;
3、 数据质量:业务的重要性决定了系统需要具备很高的可用性,数据质量必须稳定、抗风险,代码灵活复用、低耦合等;
4、 数仓数据利用率:需设计足以支撑下游需求的中间数仓存储层,下游新建一个任务,就需要消费一份与原始数据数量一致的数据造成计算资源极大的消耗,因此数仓的数据分层管理尤为迫切。
为解决以上面临问题,我们在开发之前对数据仓库相关技术进行调研给出了基本的对比情况,同时围绕 Flink 生态总结近几年业界在生产与应用中的实践。
数据仓库架构对比:
在大数据处理中,常见的数据仓库架构有两种:Lambda 架构和 Kappa 架构。如上表分析 lambda 和 Kappa 两种数仓架构在多个维度各有利弊。在架构选择上,结合 Lambda 架构在灵活度、成熟度、迁移成本表现优异特点以及腾讯新闻商业化数据已有业务特点,选择 Lambda 架构。
消息中间件对比:
Tube MQ 和 Hippo MQ 为腾讯大数据平台(Tdbank)开发面向高吞吐高性能的分布式消息中间件,TubeMQ 数据时延低,请求 TPS 高,拥有更好对外服务能力,支持客户端过滤消费,目前 Tdbank 是公司统一的传输通道,故这里我们选择 TubeMQ 作为接入/接出的消息队列组件。
实时计算引擎对比:
在实时计算引擎选型上对比几款开源。其中 Spark Streaming 利用微批处理模拟实时处理、在低延迟、复杂状态管理、流处理等方面表现欠佳。Storm 各大公司陆续淘汰。而 Flink 就是为流式计算而生的,具备低延迟、状态管理、高灵活度等特点。我们决定采用 Flink 作为实时计算引擎。另外、依托公司流计算 Oceanus 团队提供的 Flink 平台服务能够实现应用部署、集群管理等快速接入,并提供稳定的运维服务。
在实时数仓建设过程中,计算引擎的选型极为重要关系到后期能满足多少业务需求、能够实现何种复杂的功能,为了进一步了解Flink在业界各大公司的应用情况,我们对电商:淘宝天猫、京东、Shopee、唯品会;内容资讯:字节跳动、腾讯、微博、Netflix、小红书;短视频/直播:抖音、快手、哔哩哔哩各行应用场景进行了总结。
概括其实践6大场景分别为实时数仓、实时大屏、实时报表、实时监控、实时风控、在线学习,可以看出 Flink 已经成为未来流批计算引擎的趋势。
正如第二章节所述我们面诸多问题和难点,需要一套行之有效的数据组织、管理和处理方法来让我们的数据体系更加有序,使得下游接入使用成本最低、开发效率最高。下图是整个商业化数据仓库业务架构:
采集层使用大同、Boss 是数据采集上报服务,其中大同以成为集团数据治理力推的客户端上报平台,具备统一事件管理、统一参数规范上报,告别了以往上报需求散乱、上报不规范、数据难校验。当一个用户请求一个业务时,采集 SDK 会根据定义的上报规范将数据分发送到 Tube 供下游实时消费。
计算层提供实时和离线数仓两部分,离线数仓是基于 TDW、HDFS 建立的各个业务请求、曝光、点击等维度的数仓表,利用欧拉平台的数据分层、数据分类、数据血缘等能力完成数据资产的管理。实时数仓,正如第一章节所述设计的目的是解决需求多变、代码复用、系统高可用、海量数据低延时接入、数据高复用等问题,在 ODS 层(原始数据层)、DWD 层(数据明细层)、DWS 层(主题轻度数据汇总层)我们采用 TubeMQ 作为数据分发与缓存,TubeMQ 解决了海量数据的即时接入问题。
在数据利用率上我们采用:
1、不同业务差异大的需求开发申请新的 Tube 消费节点;
2、功能差异不大且数据源相同则通过 Flink 侧流方式复制流。
数据在各层由流式计算引擎进行业务清洗和转化的结果写回至消息队列中,供下游使用。对于 ODS 层的实时数据每隔1小时同步一份至 TDW 存储周期约3天,这份数据既能用于离线计算又能作为数据的备份链路异常时的问题排查和数据恢复。
存储层使用的组件比较丰富,有 Impala、Clickhouse 秒级响应多维 OLAP 数据分析查询的灯塔、人群投放系统,以及 MySQL 应用 OLTP 联机事务处理的应用场景。值得注意是 Flink 直接写 Redis 无法保障数据原子性,为此在写入 Redis 之前通过 Hash 对 key 分组、引入重试队列保障 Redis 读写稳定性。
监控、测试、管理模块,设计目的:
1、以往只能事后看日志定位问题,计算引擎代码运行异常存在监控盲区,引入一套自定义错误码和异常等级机制,一旦代码运行异常 StackTrace、错误码、异常位置等通过企业微信及时通知负责人;
2、任务参数分优先级管理,使得计算资源、系统参数等能够灵活配置,抽象模块代码复用性更强;
3、为了提高开发进度及时验证代码逻辑,巧妙搭建了一套抓取/解析 Tube 流数据字节码本地调试工具,使得本地调试代码逻辑与线上运行环境高度一致性。
总之,数仓的建设就是需要对 ODS 层数据进行 ETL,生成向 DWD 明细层(明细数据)及 DWS 服务层(主题轻度汇总数据)数据,DWD 及 DWS 两层数据也很好地解决了中间表的复用问题。接下来我们对解决方案、实时数仓详细设计进行介绍。
众所周知实时数仓建设的难点在于如何设计一条高性能、高可用、低成本以及灵活度高的数仓链路。如下图所示为我们的实时数仓架构:
存储与接入层负责将客户端、后台、实时计算中间数据上报至消息中间件 TubeMQ。TubeMQ 一方面负责实时数据的存储,一方面承担数据分发给离线/在线处理平台的功能,同时构建数据源与数据处理系统之间的桥梁。其中 DWD 层的设计目的是减少下游频繁重复消费 ODS 层原始数据,对于新需求的开发,我们只需要申请 DWD 层的 TubeMQ 新消费节点即可,极大的节省计算资源。计算层主要负责数据的 ETL、维表关联、特征抽取等业务逻辑的计算。数据仓库存储采用 TDW、HDFS 和 Impala,ODS 层的原始数据默认保存在 HDFS 上,保存周期一般3天,目的是方便问题排查及数据备份回滚;DWD 和 DWS 层数据支持写入 TDW、HDFS 用于离线计算,同时支持写入 Impala 供应用层直接使用。
正如文章开篇所提,新闻商业化数据开发面临业务需求复杂、开发周期短,系统要求高可用、低延时等问题。提出的主要解决方案有以下几点:
采用 Pipeline 模式管道式编程,组件模块化封装;
任务及系统参数按优先级配置输入,使得代码与配置高度解耦;
在代码层面的监控我们自定义了一套错误码,利用 Try{}Catch{} 捕获代码异常,报警至企业微信群实现异常实时告警;
广播流方式实现配置数据动态更新,无需重启任务;
通过侧流等方式支持多种流计算模式: 单流、多流混合, 流复制,流切分分流;
利用 Hash 分组、异常队列重试、Batch 写入重试机制等保证数据原子性、数据存储不丢失;
接下来我们将一一介绍。
PipeLine 为自定义管道流水线,可以将任务的处理分解为若干个处理阶段,即前一个处理单元的结果也是第二个模块的输入,实现计算作业流水线化。引入 PipeLine 设计模式思想可以充分利用 Flink 资源提高计算效率,使得代码结构层次更加清晰、代码解耦、模块高复用成为可能。
介绍项目中使用比较多的一个 PipeLine 模型。
从时序图可以看出,该 PipeLine 完整的执行流程:Conf->Init->Process->Sideout->Monitor->Sink,六步组成一个闭环;Conf 负责对任务参数按优先级解析;Init 初始化当前处理任务,完成 Flink 启动前环境准备流数据的接入;Process 为核心的功能处理接口,接收前一个处理阶段的输入流,并按业务逻辑完成计算将结果输入到下一个阶段;Sideout 基于 Flink 侧输出功能,可实现流的复制、筛选、过滤等操作;Monitor 为任务监控接口,开发时可选择实现;Sink 完成流的输出,如写入 Redis、Clickhouse、Tube 等。最后通过 pipeline.execute() 启动流的计算。
我们再来看该 PipeLine 类图,BaseProcedure 为全局接口,所有 PipeLine 流程上的类或接口都需要继承,BaseProcedure 完成两部分工作即 setConfig() 全局任务参数设置和 checkConfig() 在启动任务之前完成所有参数校验。这种设计的好处是参数全局化,避免参数配置错误或遗漏造成实时任务上线后异常。
结合管道示意图,执行流程可以拆封若干管道,每个管道为互相隔离的功能模块,管道与管道之间做了数据类型探测,支持“无缝连接”。我们通过侧流输出的方式实现流的复制,使得在一个计算任务中可以满足多个需求开发。另外对于复杂需求无法通过一个主处理逻辑一步实现,我们支持将主处理逻辑数据接入到用户自定制化处理逻辑模块进行处理。
Flink PipeLine 最大的价值在于为开发者统一了实时计算输入输出格式,使用者只需要关心 Process 中的逻辑即可。此外,PipeLine 将整个流计算步骤化,使得复杂任务添加、移除、替换模块变的非常轻松,而且本地调试只需要替换 Init、Sink 逻辑,确保了线上与线下代码 Process 中逻辑一致。
另外日常常用的功能我们也进行了封装,有基础的流数据读写、流的操作、监控告警、日期处理、文件处理等。
最后通过一个应用案例来感受下 PipeLine 编程模式,如何提高代码的可读性和复用性。左图代码功能包括:输入源相关参数的配置;参数全局化,获取输入数据源,流的复制,以及业务逻辑四个计算步骤。可以看到理解该伪代码功能非常依赖代码注释,如果需要新增一条复制流,则深入源码中进行修改,极容易动一发而牵全身引发线上事故。对比右图 PipeLIne 代码,可以发现,任务步骤清晰、模块解耦强、功能增删简单。
总结概括 Flink + PipeLine 模式有四个主要特点:
1、 模块复用率极大提高,不同同学在使用项目代码时,无需关心代码中的逻辑,只需要知道模块输入输出是什么,完成了什么功能;
2、 通过 PipeLine 模式将数据处理过程固化为若干步骤,使得编程灵活,代码可读性增强;
3、 利用侧流技术极大的流的复用性,一个计算任务可以完成不同业务需求,无需重复申请新的消息中间件消费节点和新建 Flink 任务;
4、 测试中只需要替换 PipeLine 的输入输出,即 Init 和 Sink 为本地测试模块。
六、高可用及稳定性设计
实时数仓设计最重要的是保证系统的高可用,一旦发生故障,即使是很短时间的中断,都会影响业务运营。
众所周知,业界在提高系统可靠性和稳定性常用方法比如有,冗余备份、流量熔断机制、容错机制、故障自动恢复、监控等。对于我们设计的系统,一方面依托流计算 Oceanus 平台提供的指标告警、指标监控以及 SLA 保障等强大能力。另一方在设计上通过捕获代码异常,分级告警;参数分层管理、参数广播动态更新;重试机制;以及高效的本地测试等方法提高系统可靠性。
实时计算模块众多,会因为上游数据格式上报出错、代码 Bug、数据库连接异常等引起计算出错甚至造成任务中断,设计的系统必须具备监控上报和告警的能力。监控报警任务为独立任务,而各个业务的任务通过 Try{}Catch(Exception e){} 方式捕获 Task 中代码异常,将报警信息统一发送到同一个消息中间件,监控报警任务将接收的数据进行 Hash(level,project name,type) 编码,通过 TimeWindow、Aggregate 聚合使得相同错误的数据聚合在一起,减少报警疲劳。
另外我们将错误分为三个等级:ERROR(错误)、WARN(警告)、INFO(提示信息),并总结了49种常见的报警错误码,比如:数据库连接异常、流量抖动异常等。实践证明一旦系统出现问题,这种代码层的监控设计能够及时发现告警,从而缩短系统的故障时间,解决原 Strom 系统代码监控盲区。
一个实时计算任务少则有几十个外部配置参数,有任务私有参数,有项目功能参数,也有涉密参数。为此我们定义了4层任务参数配置优先级:流计算 Oceanus平台配置>七彩石>各模块下的.properties > base.properties。参数优先级管理给整个项目。这种设计总结有以下几点好处:
1、配置灵活度高;
2、代码与参数解耦;
3、保密性强;
在使用 Flink 流式计算时,代码功能实现重要的同时,后期的可维护性一样很重要。任务中运行过程中,经常需要更新参数重启任务,比如实时计算过程中,源表突然增加一个字段。为此我们把配置信息广播流化,将生成广播状态流和数据流进行 Connect。广播流的是否需要发送通过定时任务检查配置版本有无变动。该方法减少了系统因频繁重启造成的数据延迟。
本地测试通过根据消息主题,从线上获取真实的字节码数据,替换 Init 接口的输入源, Process 主逻辑无需为本地调试做任何改动,使得线上、测试代码和环境高度一致,本地打印输出,结果数据直观展示。极大的提高了开发效率,缩短了问题定位时间。
数据与存储介质之间交互常常会由于连接、网络抖动等问题造成数据的读写失败,对于一些较为苛刻的场景,如交易数据分析、实时推荐特征、负反馈计算,需要数据读写零的丢失。为此我们为 Redis、Mysql 等写数据设计一套重试机制。
上游 Operator 传输的数据首先缓存在 Flink 分布式阻塞队列缓存池中,等待检查重试机制中的缓存 Queue 是否有待重试数据,我们的策略是优先输出 Queue 中数据,待所有数据重试完才操作写本次 Operator 发送来的数据。如果数据超过重试次数上限或者重试 Queue 容量上限将出发告警,本条数据将会丢弃。同时通过引入 Java8函数式编程方式完成自定义的输出函数,从而提高编码效率,以实现重试机制代码的高复用性。
最后以实时特征计算写 Redis 为例,展示重试机制的应用如何保障数据0丢失。该应用由四部分组成:各个业务输入数据源模块。Flink实时特征计算模块,各个业务单独实现数据解析和特征抽取 Flink 任务,通过重试机制模块写入同一个消息中间件;下游为同一个 Flink 特征构造任务主要完成 Protobuf 数据构造,利用业务标识字段区分接收消息中间中的混合业务特征数据,从而发到不同的 Redis 中。这个思想使用很多,比如 Mysql、Clickhouse、Http 等的 Sink。
七、总结展望
经过此次实时数仓改造及计算任务迁移,系统达到了 SLA 标准。利用 PipeLine 模式、流复制、参数分层管理等手段提升数据采集的可靠性和及时性,提高了代码数据质量,开发效率得到了极大提升,计算和存储资源得到充分利用。未来我们将基于流计算 Oceanus 平台强大能力在在线学习、搜索、人群画像等进行探索和应用。
流计算 Oceanus 限量秒杀专享活动火爆进行中↓↓
关注“腾讯云大数据”公众号,技术交流、最新活动、服务专享一站Get~