基于Hologres+Flink的曹操出行实时数仓建设

作者:林震|曹操出行实时计算负责人

曹操出行业务背景介绍

曹操出行创立于2015年5月21日,是吉利控股集团布局"新能源汽车共享生态"的战略性投资业务,以"科技重塑绿色共享出行"为使命,将全球领先的互联网、车联网、自动驾驶技术以及新能源科技,创新应用于共享出行领域,以"用心服务国民出行"为品牌主张,致力于打造服务口碑最好的出行品牌。

作为一家互联网出行平台,主要提供了网约车、顺风车、专车等一些出行服务。打车为其主要的一个业务场景。用户会在我们的平台中去进行下单,然后我们的系统会给司机进行派单,接到订单之后,进行履约服务。结束一次订单服务后,乘客会在平台做出支付。

曹操出行业务痛点分析

整个流程中这些数据会流转到我们的业务系统,主要会有营销、订单、派单、风控、支付、履约这些系统。这些系统的数据会进入到RDS数据库,流转到实时数仓中去做一个分析和处理。最终数据会进入到不同的使用场景中,比如实时的标签,实时大屏、多维BI,还有业务监控以及算法决策。

在传统lambda架构中,架构主要会分做实时数据流和离线数据流。在实时链路中,业务数据库会在RDS中通过Canal、Binlog同步的方式进入Kafka,同时app的log也会通过实时采集的方式进入到Kafka。在这些数据准备完成之后,在Kafka中构建实时数仓。整个数仓也是基于数仓分层理念去构建,主要是ODS、DWD、DWS和ADS,整个链路中会通过Flink Streaming Sql去做一个串联。

在离线链路中,数据主要是通过DataX定时同步的方式,将RDS数据同步到HDFS。同时App的log会通过定时任务同步到HDFS,整个离线数仓会通过Spark Sql的定时调度任务去逐层执行。数据在离线数仓中会通过不同的数据域去组织不同粒度的计算,最终数据会通过Flink Sink以及离线同步工具写到不同的数据应用组件中。同时为了保证某些应用场景中数据的一致性,有可能会对离线和实时两条链路的数据做些合并处理加工。

基于曹操出行整体对于成本的诉求,对于传统lambda架构,从架构中可以看到一些问题:

  • 需要使用非常丰富的大数据组件,来适配不同应用场景。
  • 研发成本非常高,不仅在实时链路中做大量的处理,而且在离线链路中也是多做一套研发。
  • 运维效率较为低效,整个实时数仓是构建在Kafka上,因此我们这种数据探查以及这种数据订正就会变得非常困难。
  • 资源成本较大,主要体现在组件使用多,需要专门的工作人员进行运维与管理;一些场景需要精准的一致性需求,因此在链路中需要做出数据的同步和计算。
  • 在某些Flink场景中,需要处理大状态场景下,可能会造成额外性能与资源的浪费。

另外从对于公司开发者使用的角度,我们对实时数仓提出了以下几点诉求:

  • 拥有统一组件满足不同业务场景诉求。
  • 再实施复杂链路中保证数据的订正。
  • Flink中一些大状态下的技术难点需要克服。

Hologres+Flink企业级实时数仓构建

Hologres能力分析

曹操出行作为Hologres的深度用户,在前期调研与测试阶段,我们对对Hologres的相关能力做了比较详细的分析,主要有以下优势:

1、业务场景能力丰富:

  • 具备OLAP分析能力
  • 具备高并发点查能力
  • 具备半结构化日志分析能力
  • 具备基于PostGIS的扩展能力,支持空间地理信息信息数据的分析与使用,对于曹操出行的业务属性来说非常重要。

2、一站式实时开发能力

  • 契合数仓分层结构理念(可以像离线数仓一样去构建分层体系,数据实时流动,实时存储)
  • Flink Streaming态高度融(Flink CDC组件集成,Flink Catalog集成)
  • 统一Ad-hoc能力,能以外表加载离线数仓中数据进行加速联邦分析

3、解决的痛点问题

  • 全链路低时延
  • 多流join场景很好提供数据打宽的能力,支持主键模型和行级,局部字段更新的能力
  • 支持Count distinct大状态精确去重场景

Hologres支持高并发更新

通过观察存储架构,我们发现Hologres在最底层是分布式存储系统,在此之上是一个存储引擎,主要是有Block Cache,shard是分多个Tablet与WAL,市面上主流的这种服务产品大多数都是基于这种LSM架构。

主流数据主键模型更新模式也有Copy On Write 和Merge On Read。这两种场景都有各自的问题,Copy On Write具有写放大的问题,数据的延迟会比较高。Merge On Read由于在读的过程中需要做数据的大量合并,因此其读的性能会非常差。在Hologres中,行存使用Merge On Read方式,列存主要基于Merge On Write。基于这种架构,一条数据在进入Hologres中,首先会到达WAL Manager中,同时也会进入到Memtable,在Memtable中主要会存储三类数据:数据文件、删除标志的文件、例如基于RoaringBitmap、索引文件。当数据积累到一定阶段后会生成不可变的Memtable,后面会通过异步的线程,定时做 flush到Data File。

Hologres Binlog支持

Hologres Binlog也是一种物理表的存储方式,其跟原表的主要区别是内置的几种自身结构,包含自身递增序列,数据修改类型以及数据修改的时间,Binlog本质上也是分shard做存储,所以也为一种分布式表,并且在WAL之前生成,因此在数据上可以与原表保证强一致性。

其次Hologres Binlog修改类型也还原了Flink中四种RowKind类型。在数据更新过程中可以产生两条更新记录,并且保证更新记录是一个连续的存储。右边展示中,写入一个数据一个PK1,然后再写入一个PK2数据,PK2的数据再做更新,Binlog中它会产生四个数据结果。

Hologres数据模型介绍

Hologres主要会分做行存引擎以及列存引擎,包括行列共存场景。

  • 聚合场景中主要是用到列存的引擎,适合OLAP场景,适合复杂查询,统计,关联等场景。同时也提供了非常丰富的索引,包括技术聚簇索引,位图索引,字典,以及基于时间序列的范围索引。
  • KV场景中主要是用到行存的引擎,主要支持高并发组件查询。包括在Flink中做维表反查也是非常适合。
  • 订阅场景中主要是用到行存的引擎,主要在表属性中要进行声明,比如说Binlog是否开启,Binlog的TTL。在订阅方的话,Hologres支持CDC以及非CDC的模式。
  • 日志场景中主要针对聚合场景,主要是支持JsonB数据类型。JsonB在这个数据的这种处理过程中,它能够自动地平铺成列式的存储结构,就可以做聚合场景的灵活分析。同时它可以自动去对这种数据类型做解析,包括对数据类型做泛化处理,以及数据的对齐,非常适合这种非稀疏场景。

曹操出行实时数仓构建实践

实时数仓架构设计

基于以上Hologres的能力,接下来是对于曹操内部实时数仓的架构设计,左边为RDS数据库,最右边是应用系统,最下边为元数据管理,中间部分是实时数仓的部分。数据通过Binlog进入到Kafka的ODS层之后,再会通过Flink会写入到Hologres里的DIM层,然后再通过Flink做ODS的多流汇聚,再写入到Hologres的DWD层。在DWD中可以做宽表打宽的是实现。再下一层,通过Binlog的订阅的方式,再写入到Hologres的DWS层,后面会统一通过One Service的一个统一查询服务对外暴露这个服务。

dwd宽表构建实践

接下来介绍一下Hologres DWD宽表层的一个构建实践。基于之前提到的Hologres列更新能力,能够很好实现宽表Join能力。在整个生产过程中,首先关注维表的应用场景,其应用场景可能含有多种:一种是维表是不变的,或者缓慢的变化,另一种是维表频繁变化的。因此需要像离线的方式去构建一个维表拉链的数据,通过用过Start Time和End Time的方式去存储维度状态有效的一个周期。

其次需要关注维表延迟的问题。在实际生产过程中,维表链路与主表的链路是一个异步的过程,有可能在维表延迟的情况下,主表关联的数据是空的,或者主表关联到的一个数据是过时的维度状态。在这种场景下,需要在Hologres做维度缺失记录的过滤,通过补偿机制再去做维度的补偿处理,同时也需要做定时的维度检查,然后增量地把不一致的状态做一个修正。

聚合计算场景优化

接下来介绍我们对聚合场景的优化,针对我们多预聚合计算场景,将其统一收敛到Rollup计算模型中,主要解决以下问题:

  • 在Flink聚合场景中经常会出现状态兼容性的问题
  • 整个数据的复用性非常差,研发人员收到新的需求,例如新的指标或者新增维度粒度时,为了不影响生产数据的稳定性,往往选择自己去构建新的任务,久而久之这种零散的任务会变得非常多,整个管理随之会变的非常混乱。

因此曹操出行主要优化了两点:

  • 构建MapSumAgg算子,MapSum主要通过对SumAgg算子做了重新设计,使之能够支持Map内部结构的求和逻辑
  • 对Grouping Sets进行动态配置化,这样Grouping Sets动态增加维度粒度,使整个任务在不重启的情况下也能自动去做自适应

结合这两点,把已有的指标放入map结构中进行封装,这样在不改变原有的算子状态,也可以得到很好的处理。在下游中可以针对不用维度,指标做好选择,然后通过同步工具做好数据路由,提供给下游的服务。

对于第二个聚合场景的优化,是对精确去重场景的拆分。在前面例子中,我们把Count Distinct的精确去重做了剥离,主要解决两个问题:

  • 维度爆炸的问题。在Flink回撤机制下做精确去重时,存储的全量状态。那么在cube场景中,这种状态爆炸式的情况,在Flink中是难以持续去建设。解决思路是通过Hologres去构建细粒度的RoaringBitmap存储方案。
  • 查询灵活高效的问题。整个流程中,在Hologres中构建自身序列的UID维表,在主表中通过反差逻辑将UID自身序列反查出来,随之在Flink中做出Group by的操作,最终通过聚合计算,算出RoaringBitmap的结果,随之写入Hologres的DWS层中,形成UV计算的轻度汇总表,解决应用端灵活维度查询时的高效性,同时也能满足解决Flink爆炸维度问题。

链路中吞吐能力调优

整个流链路中吞吐能力的调优主要分作两个部分:

  • 数据写入侧。在Flink写入到Hologres之前,针对字段状态变更频繁的场景做了一层Union层,在Union层以及ODS层中,数据都是基于PK进行分区,然后在Union层中做了一层小的窗口进行预聚合的计算,这样可以大大减少对Hologres写入压力,从而提升整个数据吞吐量,但这种方式有一个缺点就是比如一些中间状态的数据,会变得无法捕获。
  • 数据读取侧。在Binlog中更新数据,它会产生连续的变更前后数据,在这种场景中,可以通过lag开窗的这种方式获取到一次变更中连续上下游数据的情况,根据两者数据之间的信息差异,可以过滤出数据的冗余变更,从而减轻整个处理下游的压力。

元数据血缘的改造

元数据血缘的改造主要解决了以下问题:

  • Schema的演进提供了一个更便利的管控
  • 整个依赖链需要解决实时链路发布流程的问题
  • 可以对任务元数据信息进行有效的管理

曹操出行主要进行以下措施:

  • Flink Catalog集成。在元数据中去整合Hologres的Catalog,也支持Kafka Topic表中自定义Catalog,支持多版本schema和任务数据的多版本。
  • Kafka Source和Kafka Sink的改造。结合整合整个上线发布的流程,对于数据的版本信息,通过Kafka Sink对Header进行记录,Kafka Source对header的版本信息进行过滤,从而把数据版本引入到整个上下游的链路,提供上下游数据灵活的迭代。这种做法的好处是,在整个链路中可以感知到整个下游数据的使用情况,因此可以帮助用户在下线过程中可以快速定位到下游,还有没有任务做依赖,右边的图片主要是展示一个开发流程中元数据的集成。

链路保障体系

在日常开发过程中,对于任务健康以及任务出现异常后的判断和检测,都是通过异常检测诊断工具去做支持。主要体现四个方面:

  • 对于基础信息采集,通过采集工具,把Flink内置的Metric以及Kafka信息进行采集,提供基础数据,包括作业信息,Kafka一些Topic信息,作业最新指标情况。
  • 对于异常的判断,通过内存以及Topic增长情况,包括CPU使用情况,以及任务有无出现反压,任务有无倾斜做出异常的判断。
  • 对于异常原因的诊断-内部原因,内部原因主要会看CheckPoint的失败情况,Kafka LAG具体是什么算子造成的反压,Restart的次数;
  • 对于异常原因的诊断-外部原因,外部原因主要是看Job Manager以及Task Manger所在节点自身的情况,包括CPU的使用率,包括ioutil,内存情况,然后做出综合判断,帮助用户去快速定位具体问题的原因。

链路保障体系另外一个比较重要的环节就是全链路的感知能力。曹操出行主要是在流量监控与Latency监控两方面:

  • 流量监控层面:通过Kafka Cueernt Offset以及Hologres内置的Offset信息做定时的采集,从而推算出Kafka以及Hologres表的生产速率。
  • Latency监控层面:主要采集Kafka Offset以及Flink Source的Offset情况,结合Kafka Massage Timestamp去推算出每个任务自身延迟情况,再结合整个数据血缘进行一个串联,可以得出端到任务自身整体的延迟时间,再通过任务上下游生产速率比,以及任务自身延迟情况,可以在整个生产链路中快速定位出具体异常和问题发生的节点。

数据订正能力建设

在传统的Streaming链路中,数据订正是一个非常复杂的过程。主要要解决两个问题:

  • 如何知晓订正的数据为正确数据?验证其具有一定困难。
  • 在整个验证过程中,如何保证对下游的透明?如果丢状态去做重启的订正,肯定会对下游造成很大的影响。

因此我们主要思路是基于Hologres去做实现。首先对于原始任务进行代码修正后,并维持原有状态去做重启。第二步将对Hologres表做Schema的拷贝,然后新建一个订正的临时表。第三步会将任务进行拷贝,并将Sink调整到订正临时表,去做无状态从头消费的重启。这样可以把订正的结果数据订正进Hologres订正表中。等待消费结束后停止订正任务,然后通过修正脚本去对比原表以及订正表中关键信息,去做数据的订正。由于数据的订正,它处于数据终态,对于下游来说,不会造成大起大落。并且在整个链路中,因为正确数据可以通过整个数据链路做回撤的传导,因此整个下游就可以完成数据的自动订正。

曹操出行业务成果分析

架构清晰简单:

  • 对比Lamada架构,Hologres+Flink整体架构更加清晰,使用数据组件大大减少
  • 整体技术复杂难度降低,原先为了解决数据一致性问题,数据需要在不同的异构存储和异构链路中来回传输和计算,整个技术复杂度较高

开发效率提高:

  • 整个开发模式变得简单易用,大大缩短人力周期
  • 数据实时模型分层非常清晰,整体下游复用性以及使用门槛大幅度降低

运维体验提升:

  • 由于数据存储在Hologres之上,因此数据探查更加便捷,数据订正难易程度大幅度减少。

成本减少:

  • 组件维护成本减少。
  • 数据的离线存储和实时存储,从双份存储降低到一份存储,以及降低了数据在异构存储之间的同步与计算成本
  • 解Fflink中各类计算场景中大状态的成本,减少了计算开销并提升了处理性能。

未来展望

未来展望主要分为以下几个层面:

  • 当前Flink集群还是一个自建的集群,对于这些集群我们业务最关心的是使用过程中,其业务的稳定性和可靠性。特别是在高峰场景,资源不足时,怎么去做快速的缩扩容。在高峰期过去后怎么去做到无缝缩容,降低业务风险,包括减少业务的数据中断时间。
  • 在任务级别的动态感知和智能调控上。很多时候研发根据自己的经验去设置Flink的资源参数,往往有很多资源其实是多设或者是额外设置的。通过动态感知能力的引入,能够有效提升整体的资源使用情况,包括未来也可能会引入智能算法,包括自适应的机制去达到节约成本的目的。
  • Flink CDC来统一ODS入仓的方案。我们在离线使用DataX的入仓方案,后来实时使用了Flink CDC的入仓方案,其实本质上数据可以提供一个统一的解决思路,来解决数据的一致性和灵活性的诉求。包括在CDC方案中,也会有一些定制上的需求。比如说在CDC过程中,怎么去解决加解密的一些问题,包括RDS数据库中数据归档的一些问题。后续的话也会分阶段的做一些调整,包括一些高频迭代的诉求,会在后续的规划中提前去做解决。
  • 关于数据服务的一个规划。因为曹操出行有很多服务的场景,特别是在线应用的这种场景,包括分析型的这种服务也在上面,需要高可用的数据服务以及服务可扩展性,那怎么样通过同一份数据来做到不同服务的扩展。后续会考虑基于Hologres主从隔离的能力,通过一主多从的能力去支持多种数据服务的扩展。
相关推荐
千叶真尹2 天前
基于Flink的用户画像 OLAP 实时数仓统计分析
flink
从头再来的码农4 天前
大数据Flink相关面试题(一)
大数据·flink
MarkHD4 天前
第四天 从CAN总线到Spark/Flink实时处理
大数据·flink·spark
SparkSql4 天前
FlinkCDC采集MySQL8.4报错
大数据·flink
james的分享4 天前
Flink之Table API
flink·table api
涤生大数据5 天前
带你玩转 Flink TumblingWindow:从理论到代码的深度探索
flink·理论·代码·tumblingwindow
Apache Flink6 天前
网易游戏 Flink 云原生实践
游戏·云原生·flink
SunTecTec6 天前
SQL Server To Paimon Demo by Flink standalone cluster mode
java·大数据·flink
工作中的程序员7 天前
flink监控指标
flink
小马爱打代码7 天前
SpringBoot整合Kafka、Flink实现流式处理
spring boot·flink·kafka