​爱奇艺|海量数据实时分析服务技术架构演进

1.现状与挑战

爱奇艺目前使用到的大数据相关技术有Druid、Impala、Kudu、Kylin、Presto、ElasticSearch等,并且随着各技术框架的版本升级而升级。比如:

  • Druid是一个分布式的支持实时分析的数据存储系统,数据与时间强相关,已由0.10.0版本升级到0.14.2版本;
  • Impala是Cloudera受谷歌Dremel启发开发的实时交互SQL大数据查询工具;
  • Kudu是Cloudera开源的存储引擎,可以同时提供低延迟的随机读写和高效的数据分析能力;
  • Kylin是Apache开源的一个分布式引擎, 提供了在Hadoop之上的SQL查询接口及OLAP能力,支持超大规模数据;
  • Presto是一个分布式的SQL查询引擎,其设计专门用于进行高速、实时的数据分析;
  • ElasticSearch是一个高可靠、可扩展、分布式的全文搜索引擎。

不同的业务场景需要不同的大数据技术架构,对于爱奇艺号而言,因单日数据量达到亿级且分固定时间选择和自由时间选择(至少查询近2年数据)查询数据的业务特点,因此采用的是Druid和ElasticSearch的组合技术架构,其中自由时间选择查询Druid,而固定时间选择查询ElasticSearch。同时,为了保证数据的高可用,Druid和ElasticSearch都有主备2份数据。

然而,在使用Druid和ElasticSearch的过程中也遇到了一些挑战,比如:

Druid本身对数据写入和查询只提供了基于JSON的API接口,学习接口的使用方法,了解各种字段含义,学习成本很高;另外,数据的安全性,在早期的Druid版本中支持较弱;再有,高qps长时间跨度的聚合查询也是一个很大的挑战。

对于ElasticSearch,因不适用于大数据量的聚合计算,要尽量避免此种应用场景,且ElasticSearch提供的RESTful API的查询接口学习成本也相对较高。另外,因为Druid集群是服务于公司全部业务的,如何做业务隔离也是一个严峻的挑战。

2.技术架构演进

在最初的爱奇艺号数据服务中,主要采用的是Kylin,架构如下图所示:

服务分集群部署,每个集群部署多台机器,固定时间选择和自由时间选择查询的都是Kylin,并对数据进行缓存。因爱奇艺号作品数据查询的是视频明细数据的特点,随着业务的发展,爱奇艺号用户以及上传视频量快速增长,导致Kylin Cube的构建时长和查询时长明显增加,甚至会出现查询超时的情况。另外,Kylin构建Cube过程很是不稳定,经常会出现构建失败或超时的情况,需要耗费大量的人力成本去处理上述异常情况。

基于此,我们进行了新的技术选型,对Impala+Kudu、ElasticSearch、Druid等技术架构进行了对比。最后,因Druid具有超大数据规模、毫秒级查询时延、高并发查询、高稳定性等的特点,故爱奇艺号选择Druid平台作为底层架构。而对于固定时间选择,因其时间固定且视频量级为亿级,故采用ElasticSearch存储和查询,重新选型后的架构如下图所示。

爱奇艺号的作品数据查询分为两个部分:固定时间范围查询和自由时间范围查询,我们对固定时间范围查询结果进行预计算且结果存入ElasticSearch,这样免去了大数据集上的实时聚合和排序,查询性能得到了很大提升;自由时间范围选择查询Druid,因为是分天查询视频数据,所以Druid的Segment粒度是天,但若用户选择的数据查询时间跨度比较大,那么Druid扫描的Segment数量就会增加,加载进内存的数据会增加,聚合数据速度会变慢,针对此种场景,爱奇艺号导入了按月分Segment数据的DataSource,即把每个视频1个自然月的数据汇总到一个Segment,这样减少了扫描的Segment数量,加载进内存的数据减少,数据聚合速度会变快。经过测试,当用户选择的时间范围跨度大于6个月时,将查询时间范围拆分成自然月与自然日的两个时间范围并行查询,查询时间会明显缩短。

经过上述优化,一个普通的爱奇艺号用户查询数据时长由2s+缩减至150ms+,性能提升十分明显,用户反馈良好,固定时间选择具体性能对比如下图所示:

由上图可以看出,优化后昨日/近7天/近90天的数据查询时间明显缩短,且数据查询时长并不随着时间范围的扩大而明显增加,固定时间维度查询优化明显。自由时间选择的查询性能对比如下图:

由上图可以看出,优化后自由时间选择的查询时长明显优于优化前,查询时长是数量级级别的差异。但当用户的视频量比较大时,Druid的查询性能明显下降,于是我们通过扩容集群机器等方式进一步解决用户视频量大而导致查询变慢的问题。

另外,为了预防Druid集群故障,我们采用主备Druid集群的方式存储了2份同样的数据,当主Druid集群出现故障不可用时,采用Hystrix的服务降级,改成查询备份的Druid集群数据,从而保证服务高可用。因为固定时间维度查询预计算好的ElasticSearch结果,缓解了Druid查询压力,且对查询过的数据进行Redis缓存,进一步降低ElasticSearch和Druid的查询压力,从而保证服务的稳定性,接口成功率提升至99.9%。

3.选择Druid的原因

Druid是一个用于大数据实时查询和分析的高容错、高性能开源分布式系统,旨在快速处理和查询,Druid的架构如下图所示:

Druid主要包含以下5类节点

  • MiddleManager节点:摄入数据以及生成Segment数据文件
  • Historical节点:加载已生成好的数据文件,以供数据查询
  • Coordinator节点:负责历史节点的数据负载均衡,以及通过规则管理数据的生命周期
  • Overload节点:负责数据摄入的负载均衡
  • Broker节点:对外提供数据查询服务,并同时从MiddleManager节点和Historical节点查询数据,合并后返回给调用方

同时,集群还包括以下三类外部依赖

  • Metadata:存储Druid集群的元数据信息,比如:Segment的相关信息,一般是MySQL。
  • Zookeeper:为Druid集群提供一致性协调服务。
  • Deep Storage:存放生成的Segment数据文件,并共Historical节点下载,一般是HDFS。

Druid为何能支持如何快速的查询呢?下面为你详细介绍。

在介绍Druid快速查询原理之前,首先介绍一下Druid的数据查询过程。

查询节点接收外部Client的查询请求,并根据查询中指定的interval找出相关的Segment,然后找出包含这些Segment的实时节点和历史节点,再将请求分发给相应的实时节点和历史节点,最后将来自实时节点和历史节点的查询结果合并后返回给调用方。其中,查询节点通过Zookeeper来发现历史节点和实时节点的存活状态。

下图展示了在系统架构中查询请求数据如何流动,以及哪些节点涉入其中。

查询具体过程如下:

  1. 查询请求首先进入查询节点,查询节点将与已知存在的Segment进行匹配查询;
  2. 查询节点选择一组可以提供所需要的Segment的历史节点和实时节点,将查询请求分发到这些机器上;
  3. 历史节点和实时节点都会进行查询处理,然后返回结果;
  4. 查询节点将历史节点和实时节点返回的结果合并,返回给查询请求方。

Druid快速查询主要有以下3个原因:

  • 内存式查询
  • 缓存的使用
  • Segment特殊存储格式:列式存储、Bitmap索引、RoaringBitmap Compression

首先,Druid是一个内存式的数据库,设计初衷就是数据的查询落到内存中,如果内存足够大,可以保证所有的数据都加载到内存中;其次,如果Broker节点上已经缓存本次查询的结果(即之前查询过与本次查询完全相同的查询),那么Broker节点直接返回数据给客户端,而无需再查询各历史节点,进一步提高了查询速度;再次,基于Druid基本存储单位Segment的特殊存储格式,列式存储保证了,每次查询只查询其需要的列,而不必查询出一行中的所有数据列,Bitmap索引的使用,保证了其快速查询。

下面重点介绍一下其中Bitmap索引:

我们考虑如下场景,一场举世瞩目的篮球名人慈善赛在洛杉矶举行,所得善款全部用于公益事业,参加篮球赛的有现勇士球星Curry和Durant,著名歌手Beyonce、Biber,还有另外一名Curry女士参加等等。其中每人得分及相应捐款如下:

如果想查询出篮球运动员Curry的得分情况和捐款情况,如何快速查询出来呢?

首先,为每个字段中的每个值建立一个Bitmap索引,上述中共有name/gender/profession/score/donation 5个非时间字段,其中name/gender/profession属于维度列,score/donation属于度量列。对于name这个字段,因为其有4个值,Curry、Durant、Beyonce、Biber4个值,因此有4个Bitmap,每个Bitmap 0表示无,1表示有,Bitmap大小由数据条数决定,即有多少条数据,这个Bitmap的size就是多大。

Curry对应的Bitmap如下:

1

0

0

0

1

Profession中basketball player的Bitmap如下:

1

1

0

0

0

要查询勇士球星Curry的记录的话,就直接用上述两个Bitmap做"与"运算即可,得出:

1

0

0

0

0

这样的话,即可得出第一行即为其查询结果。

另外,对于同一个字段的各个值,其中只有与记录条数相等的1的个数,其余全是0(比如:对于name字段,其有4个值,5条记录,那么对于这4个值得4个Bitmap中,仅有5个值为1),可以使用压缩算法对其进行压缩,Druid使用的是Roaring Bitmap Compression,详情请见:https://roaringbitmap.org/。

4.展望

Druid在0.10版本之后开始支持SQL,且随着版本的升级支持的SQL函数也越来越多,但因为Druid SQL本质上是一个语言翻译层,受限于Druid本身的查询处理能力,支持的SQL功能有限。 Druid目前并没有支持JOIN查询,所有的聚合查询都被限制在单个DataSource进行,但在实际的使用过程中,往往需要不同DataSource进行关联查询才能得到想要结果,这也是目前Druid开发团队的难题。

现在爱奇艺大部分DataSource的Segment的粒度是天或小时级的,当需要查询的时间跨度比较大时,会导致查询变慢,占用大量Historical节点资源,可以创建一个Batch任务,把几天前(或几周前)的数据按照天或月粒度Roll up重新构建Index,当查询时间跨度较大时,性能会有明显提升。

此外,因为目前爱奇艺号不同功能使用的是同一个Druid集群,只是在DataSource间做数据隔离,但是数据查询Broker之间并未做隔离,各功能之间数据查询会互相影响,也是希望解决的难题。

原文发布于微信公众号 - Spark学习技巧(bigdatatip)

原文发表时间:2019-09-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券