通往创新之巅:互联网技术架构创新案例和实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

t1

三、OLAP查询演进:基于Presto的数据查询服务

在FreeWheel,每天会产生超过十亿条数据,这些数据包括视频广告的投放信息,观看点击信息等。对这些数据,我们需要保留过去18个月的数据,所以目前超过200T的数据需要进行查询分析,并且还在增长。

数据上,我们摊平的表有将近600多列,并且上游数据还在不断增加新的数据列。为了查询效率,存储上我们使用Parquet进行列式存储。

大概三年前,FreeWheel就开始关注并选择了Presto作为分布式查询引擎,当时我们也注意到很多公司也在用Presto。我们选择了Presto作为查询引擎因为Presto可以满足我们很多要求,首先我们看一下Presto是如何工作的。

1.Presto的架构及优势

Presto是一个由Facebook开源的大规模分布式查询引擎。从图11可以看出,Presto主要有两种角色的节点,分别是Coordinator和Worker。Coordinator就类似系统中的Master节点,当用户提交一条SQL到Presto时,实际是Coordinator最先开始工作,Parser负责分析和检查SQL的合法性,生成抽象语法树(AST)。然后Parser会把AST交给优化器,优化器一方面负责根据一些特定的规则对SQL进行优化,一方面生成逻辑执行计划,将SQL执行划分为若干个阶段。最后由调度器将任务划分成更细粒度的Task任务,并分发给工作节点。

图11 Presto架构图

工作节点一旦收到任务后就会立刻开始加载数据和计算,整个过程是全内存流水线式的,也就是说Presto不会像Hive一样把SQL规划成多个MapReduce Job,而是一条内存中的数据流水线。这样一来从两个方面提高了执行速度,一是Presto在执行任务时不会把中间结果写磁盘,节省了很多磁盘的IO操作;二是流水线模式下,各个阶段之间并不是完全的强依赖关系,先计算完的数据可以尽快进入下一个阶段的计算。这是Presto效率高的重要原因。

此外,Presto作为查询引擎,在设计时考虑了对多种数据源的支持,在计算框架之外提供了一系列接口用来和数据打交道,图中红色的部分就是可以针对不同数据源进行扩展的接口(部分)。这是我们喜欢Presto的一个重要因素,它允许我们自己实现元数据提供的方法,数据读取的方法等。

此外,Presto可以支持多种数据源join在一起,这对我们也很有帮助。因为我们在做数据分析的时候,经常需要join纬度表,这些数据可能在其他的关系数据库中,甚至可能就是HDFS上一个文件。Presto这种对多数据源的支持非常方便我们做这种操作。

最后,Presto支持标准SQL,这使得用户的学习成本变得很低。

2.Presto的使用

当选定了Presto作为我们的查询引擎之后,我就有了如图12的Presto服务。

图12 Presto服务架构图

数据存储在HBase和HDFS上,HBase里存的是动态的最新的数据,而HDFS中存储的是相对稳定的数据Parquet文件。这是我们的流式架构和业务共用决定的,但是对上层用户,我们不希望有这种差别,也就是说我们希望对这两种数据的融合查询应该是对用户透明的。

为了达到这个目的,我们实现自己的Presto Plugin,实现了对两种数据的共同读取和解析。

此外,为了提高查询速度,增加查询的谓词下推功能(Predicate Pushdown)。也就是说,我们可以根据查询条件,比如时间范围、客户ID等,查询元数据来减少数据扫描的范围,按照查询需求设置Presto读入的数据内容。

要实现这一目标,我们需要知道当前存储数据的元数据。我们每天产生上万个Parquet文件,虽然Parquet文件中存储了数据块的信息和进行谓词下推优化所必须的信息,但是每次执行都要打开文件去读元数据显然是效率低下的。因此我们建立了一个独立的元数据服务,用于预先读取、缓存、更新HDFS上文件的元数据,对外提供RESTful查询接口,用来接收Coordinator的查询,和提供准确的数据块元数据。

3.Presto多集群部署

在内部使用了一段时间后,我们统计了Presto的使用情况。从图13里可以看出,超过一半的查询可以在1分钟内完成,80%可以在5分钟内完成。然而,我们也发现,大概3%的查询是超过了30分钟才完成的。

图13 Query执行时间的分布图

根据我们平时运维的观察和总结,长耗时查询的原因主要是:

1)基数很大的group by,或者join了很多纬度表;

2)SQL条件复杂;

3)时间范围比较长,或者涉及很多列,也就是需要扫描加载的数据比较大。

比较糟糕的是,我们发现在这种情况下,Presto集群的响应速度会变慢,并发执行的其他查询的效率也会有所降低,查询失败的风险也会增加。并且由于Presto不容错(执行机制是:一旦查询中某一个环节出错,整个查询都会失败),所以长耗时查询的失败重试的成本也会增加。

通过分析我们发现,这些长耗时查询很大程度上是可以预测的,换言之,是有能力把这些长耗时拎出来的,因此我们就希望由多个集群代替原来的单集群模式。

根据Slider on YARN的扩展功能,这里我们做的一个改造就是把Presto也做成Slider应用放到YARN里去。我们把Presto的启动脚本、参数配置、包括资源申请的配置、还有组件间的启动依赖都做到了Slider里。这样做首先是对运维比较友好,因为所有东西都在一个Zip包里,简化了部署,并且可以通过YARN容易调配和申请资源。

更重要的,这种部署提高了应用的可伸缩性。这里的可伸缩性一方面是指运行中的集群,我们可以运行一条指令就动态地增加或减少工作节点,另一方面,我们可以快速地在YARN上启动一个新的集群来执行查询服务。而且所有这些集群都会共享元数据服务中的元数据,不会有数据不一致的情况发生。启动的集群可以应用不同的配置,可以实现资源的隔离。这样一来,我们可以把不同应用,不同需求的SQL调度到不同的集群上去运行。

4.OLAP统一查询平台

有了多集群部署,很自然的我们就希望有一个统一的查询入口,在这里我们可以做很多事情:

• 查询预测与限制:可以在这一层对查询有一个检查,看一下查询的执行代价,根据权限等设置来拒绝或者接受。

• 负载均衡:在可用的集群中挑选负载最低的去执行当前查询,或者根据查询执行代价去调度不同的集群去执行。

• 容错:如果查询在集群中发生内部错误,服务可以自动去重试,而对用户透明。

• 权限控制:可以根据不同类型查询的标志,分配到专属的集群中去。

• 灰度发布:升级时,可以逐个逐批去替换正在运行的集群,而通过服务设置和容错机制,保证上层用户不会感知到升级动作的存在,降低停服时间。

• 审计:记录查询的提交详情。

最后在这层查询服务上,我们会有Presto的UI,可以接受API的调用,可以接入BI工具等等。以Presto为核心服务的统一查询平台如图14所示。

图14 OLAP统一查询平台

目前,在FreeWheel内部,这一套OLAP系统正在多种场景下服务于不同的团队:无论是交互式查询、调试系统问题或是生成报表,基于Presto的查询服务都可以满足我们绝大多数需求。