presto查询hive presto查询原理_执行计划

olap

  • Druid :
    是一个实时处理时序数据的OLAP数据库,因为它的索引按照时间分片,查询的时候也是按照时间线去路由索引。
  • Kylin
    核心是Cube,Cube是一种预计算技术,基本思路是预先对数据作多维索引,查询时只扫描索引而不访问原始数据从而提速。
  • Presto:
    它没有使用MapReduce,大部分场景下比hive快一个数量级,其中的关键是所有的处理都在内存中完成
  • Impala:
    基于内存运算,速度快,支持的数据源没有Presto多。
  • Spark SQL:
    基于Spark平台上的一个OLAP框架,基本思路是增加机器来并行运算,从而提高查询速度。
  • ES:
    最大的特点是使用了倒排索引解决问题。ES在数据获取和聚集用的资源比在Druid高。


Presto是一个开源的分布式SQL查询引擎,数据量支持GB到PB字节,主要用来处理秒级查询的场景。

虽presto可以解析SQL,但它不是一个标准的数据库,不是MySQL、Oracle的代替品,也不能用来处理在线事务(OLTP);

presto查询hive presto查询原理_执行计划_02

presto查询hive presto查询原理_SQL_03

Presto 整体架构

presto查询hive presto查询原理_SQL_04

presto查询hive presto查询原理_presto查询hive_05

1.Client:包括 prosto-cli 客户端以及 JDBC 驱动、ODBC 或 其他语言实现的Driver;2.Discovery Service:是将 coordinator 和 worker 结合到一起的服务。Worker 节点启动后向Discovery Server 服务注册,Coordinator 从 Discovery Server 获得可以正常工作的 Worker 节点;3.Coordinator:主要用于接收客户端提交的查询,解析查询语句,执行词法分析生成查询执行计划,并生成Stage 和 Task 进行调度;然后合并结果,把结果返回给客户端(Client);4.Worker:主要负责与数据的读写交互以及执行查询计划;Coordinator 和 Worker 可一起启动,这样小规模的集群或伪分布式可以节省一些资源。

Presto SQL执行步骤

1.客户端通过 HTTP 发送一个查询语句给Presto集群的Coordinator;

2.Coordinator 接收到客户端传来的查询语句,对该语句进行解析、生成查询执行计划,并根据查询执行计划依次生成 SqlQueryExecution -> SqlStageExecution -> HttpRemoteTask;

3.Coordinator 将每个Task分发到所需要处理的数据所在的Worker上进行分析;

4.执行Source Stage 的 Task,这些Task通过Connector从数据源中读取所需要的数据;

5.处于下游的Stage中用的Task会读取上游的Stage产生的输出结果,并在该Stage的每个Task所在的Worker内存中进行后续的计算和处理;

6.Coordinator 从分发的Task之后,一直持续不断的从Single Stage 中的Task获得计算结果,并将结果写入到缓存中,直到所所有的计算结束;

7.Client 从提交查询后,就一直监听 Coordinator 中的本次查询结果集,立即输出。直到轮训到所有的结果都返回,本次查询结束;

presto查询hive presto查询原理_SQL_06

presto查询hive presto查询原理_数据_07

部署方式

Presto常见的部署方式如下图所示:

presto查询hive presto查询原理_presto查询hive_08

Coordinator与Discovery Server耦合在一起混合部署,然后部署多台Worker。然而这个有个问题,就是Coordinator存在单点问题,我们目前线上使用ip漂移的方法(网卡绑定多ip)。如下图所示:

presto查询hive presto查询原理_SQL_09

 

查询流程

 

presto查询hive presto查询原理_执行计划_10

 

整体查询流程为:

  • Client使用HTTP协议发送一个query请求。
  • 通过Discovery Server发现可用的Server。
  • Coordinator构建查询计划(Connector插件提供Metadata)
  • Coordinator向workers发送任务
  • Worker通过Connector插件读取数据
  • Worker在内存里执行任务(Worker是纯内存型计算引擎)
  • Worker将数据返回给Coordinator,之后再Response Client

SQL执行流程

 

presto查询hive presto查询原理_数据_11

当Coordinator收到一个Query,其SQL执行流程如上图所示。SQL通过Anltr3解析为AST(抽象语法树),然后通过Connector获取原始数据的Metadata信息,这里会有一些优化,比如缓存Metadata信息等,根据Metadata信息生成逻辑计划,然后会依次生成分发计划和执行计划,在执行计划里需要去Discovery里获取可用的node列表,然后根据一定的策略,将这些计划分发到指定的Worker机器上,Worker机器再分别执行。

 

presto查询hive presto查询原理_数据_12

 

Presto、Impala性能比较

测试结论:Impala性能稍领先于Presto,但是Presto在数据源支持上非常丰富,包括Hive、图数据库、传统关系型数据库、Redis等。


Facebook的数据仓库存储在少量大型Hadoop/HDFS集群。Hive是Facebook在几年前专为Hadoop打造的一款数据仓库工具。在以前,Facebook的科学家和分析师一直依靠Hive来做数据分析。但Hive使用MapReduce作为底层计算框架,是专为批处理设计的。但随着数据越来越多,使用Hive进行一个简单的数据查询可能要花费几分到几小时,显然不能满足交互式查询的需求。Facebook也调研了其他比Hive更快的工具,但它们要么在功能有所限制要么就太简单,以至于无法操作Facebook庞大的数据仓库。

2012年开始试用的一些外部项目都不合适,他们决定自己开发,这就是Presto。2012年秋季开始开发,目前该项目已经在超过 1000名Facebook雇员中使用,运行超过30000个查询,每日数据在1PB级别。Facebook称Presto的性能比Hive要好上10倍多。2013年Facebook正式宣布开源Presto。

本文首先介绍Presto从用户提交SQL到执行的这一个过程,然后尝试对Presto实现实时查询的原理进行分析和总结,最后介绍Presto在美团的使用情况。

Presto架构

 

presto查询hive presto查询原理_数据_13

 

presto架构图

 

Presto查询引擎是一个Master-Slave的架构,由一个Coordinator节点,一个Discovery Server节点,多个Worker节点组成,Discovery Server通常内嵌于Coordinator节点中。Coordinator负责解析SQL语句,生成执行计划,分发执行任务给Worker节点执行。Worker节点负责实际执行查询任务。Worker节点启动后向Discovery Server服务注册,Coordinator从Discovery Server获得可以正常工作的Worker节点。如果配置了Hive Connector,需要配置一个Hive MetaStore服务为Presto提供Hive元信息,Worker节点与HDFS交互读取数据。

Presto执行查询过程简介

既然Presto是一个交互式的查询引擎,我们最关心的就是Presto实现低延时查询的原理,我认为主要是下面几个关键点,当然还有一些传统的SQL优化原理,这里不介绍了。

  1. 完全基于内存的并行计算
  2. 流水线
  3. 本地化计算
  4. 动态编译执行计划
  5. 小心使用内存和数据结构
  6. 类BlinkDB的近似查询
  7. GC控制

为了介绍上述几个要点,这里先介绍一下Presto执行查询的过程

提交查询

用户使用Presto Cli提交一个查询语句后,Cli使用HTTP协议与Coordinator通信,Coordinator收到查询请求后调用SqlParser解析SQL语句得到Statement对象,并将Statement封装成一个QueryStarter对象放入线程池中等待执行。

 

presto查询hive presto查询原理_数据_14

 

提交查询

 

SQL编译过程

Presto与Hive一样,使用Antlr编写SQL语法,语法规则定义在Statement.g和StatementBuilder.g两个文件中。 如下图中所示从SQL编译为最终的物理执行计划大概分为5部,最终生成在每个Worker节点上运行的LocalExecutionPlan,这里不详细介绍SQL解析为逻辑执行计划的过程,通过一个SQL语句来理解查询计划生成之后的计算过程。

 

presto查询hive presto查询原理_数据_15

 

SQL解析过程

 

样例SQL:

select c1.rank, count(*) from dim.city c1 join dim.city c2 on c1.id = c2.id where c1.id > 10 group by c1.rank limit 10;

 

presto查询hive presto查询原理_执行计划_16

 

逻辑执行计划

 

上面的SQL语句生成的逻辑执行计划Plan如上图所示。那么Presto是如何对上面的逻辑执行计划进行拆分以较高的并行度去执行完这个计划呢,我们来看看物理执行计划。

物理执行计划

逻辑执行计划图中的虚线就是Presto对逻辑执行计划的切分点,逻辑计划Plan生成的SubPlan分为四个部分,每一个SubPlan都会提交到一个或者多个Worker节点上执行。

SubPlan有几个重要的属性planDistribution、outputPartitioning、partitionBy属性。

  1. PlanDistribution表示一个查询Stage的分发方式,逻辑执行计划图中的4个SubPlan共有3种不同的PlanDistribution方式:Source表示这个SubPlan是数据源,Source类型的任务会按照数据源大小确定分配多少个节点进行执行;Fixed表示这个SubPlan会分配固定的节点数进行执行(Config配置中的query.initial-hash-partitions参数配置,默认是8);None表示这个SubPlan只分配到一个节点进行执行。在下面的执行计划中,SubPlan1和SubPlan0 PlanDistribution=Source,这两个SubPlan都是提供数据源的节点,SubPlan1所有节点的读取数据都会发向SubPlan0的每一个节点;SubPlan2分配8个节点执行最终的聚合操作;SubPlan3只负责输出最后计算完成的数据。
  2. OutputPartitioning属性只有两个值HASH和NONE,表示这个SubPlan的输出是否按照partitionBy的key值对数据进行Shuffle。在下面的执行计划中只有SubPlan0的OutputPartitioning=HASH,所以SubPlan2接收到的数据是按照rank字段Partition后的数据。

 

presto查询hive presto查询原理_SQL_17

 

物理执行计划

 

完全基于内存的并行计算

查询的并行执行流程

Presto SQL的执行流程如下图所示

  1. Cli通过HTTP协议提交SQL查询之后,查询请求封装成一个SqlQueryExecution对象交给Coordinator的SqlQueryManager#queryExecutor线程池去执行
  2. 每个SqlQueryExecution线程(图中Q-X线程)启动后对查询请求的SQL进行语法解析和优化并最终生成多个Stage的SqlStageExecution任务,每个SqlStageExecution任务仍然交给同样的线程池去执行
  3. 每个SqlStageExecution线程(图中S-X线程)启动后每个Stage的任务按PlanDistribution属性构造一个或者多个RemoteTask通过HTTP协议分配给远端的Worker节点执行
  4. Worker节点接收到RemoteTask请求之后,启动一个SqlTaskExecution线程(图中T-X线程)将这个任务的每个Split包装成一个PrioritizedSplitRunner任务(图中SR-X)交给Worker节点的TaskExecutor#executor线程池去执行

 

presto查询hive presto查询原理_presto查询hive_18

 

查询执行流程

 

上面的执行计划实际执行效果如下图所示。

  1. Coordinator通过HTTP协议调用Worker节点的 /v1/task 接口将执行计划分配给所有Worker节点(图中蓝色箭头)
  2. SubPlan1的每个节点读取一个Split的数据并过滤后将数据分发给每个SubPlan0节点进行Join操作和Partial Aggr操作
  3. SubPlan1的每个节点计算完成后按GroupBy Key的Hash值将数据分发到不同的SubPlan2节点
  4. 所有SubPlan2节点计算完成后将数据分发到SubPlan3节点
  5. SubPlan3节点计算完成后通知Coordinator结束查询,并将数据发送给Coordinator

 

presto查询hive presto查询原理_数据_19

 

执行计划计算流程

 

源数据的并行读取

在上面的执行计划中SubPlan1和SubPlan0都是Source节点,其实它们读取HDFS文件数据的方式就是调用的HDFS InputSplit API,然后每个InputSplit分配一个Worker节点去执行,每个Worker节点分配的InputSplit数目上限是参数可配置的,Config中的query.max-pending-splits-per-node参数配置,默认是100。

分布式的Hash聚合

上面的执行计划在SubPlan0中会进行一次Partial的聚合计算,计算每个Worker节点读取的部分数据的部分聚合结果,然后SubPlan0的输出会按照group by字段的Hash值分配不同的计算节点,最后SubPlan3合并所有结果并输出

流水线

数据模型

Presto中处理的最小数据单元是一个Page对象,Page对象的数据结构如下图所示。一个Page对象包含多个Block对象,每个Block对象是一个字节数组,存储一个字段的若干行。多个Block横切的一行是真实的一行数据。一个Page最大1MB,最多16*1024行数据。

 

presto查询hive presto查询原理_数据_20

 

数据模型

 

节点内部流水线计算

下图是一个Worker节点内部的计算流程图,左侧是任务的执行流程图。

Worker节点将最细粒度的任务封装成一个PrioritizedSplitRunner对象,放入pending split优先级队列中。每个

Worker节点启动一定数目的线程进行计算,线程数task.shard.max-threads=availableProcessors() * 4,在config中配置。

每个空闲的线程从队列中取出一个PrioritizedSplitRunner对象执行,如果执行完成一个周期,超过最大执行时间1秒钟,判断任务是否执行完成,如果完成,从allSplits队列中删除,如果没有,则放回pendingSplits队列中。

每个任务的执行流程如下图右侧,依次遍历所有Operator,尝试从上一个Operator取一个Page对象,如果取得的Page不为空,交给下一个Operator执行。

 

presto查询hive presto查询原理_SQL_21

 

节点内部流水线计算

 

节点间流水线计算

下图是ExchangeOperator的执行流程图,ExchangeOperator为每一个Split启动一个HttpPageBufferClient对象,主动向上一个Stage的Worker节点拉数据,数据的最小单位也是一个Page对象,取到数据后放入Pages队列中

 

presto查询hive presto查询原理_数据_22

 

节点间流水线计算

 

本地化计算

Presto在选择Source任务计算节点的时候,对于每一个Split,按下面的策略选择一些minCandidates

  1. 优先选择与Split同一个Host的Worker节点
  2. 如果节点不够优先选择与Split同一个Rack的Worker节点
  3. 如果节点还不够随机选择其他Rack的节点

对于所有Candidate节点,选择assignedSplits最少的节点。

动态编译执行计划

Presto会将执行计划中的ScanFilterAndProjectOperator和FilterAndProjectOperator动态编译为Byte Code,并交给JIT去编译为native代码。Presto也使用了Google Guava提供的LoadingCache缓存生成的Byte Code。

 

presto查询hive presto查询原理_数据_23

 

动态编译执行计划

 

 

presto查询hive presto查询原理_presto查询hive_24

 

动态编译执行计划

 

上面的两段代码片段中,第一段为没有动态编译前的代码,第二段代码为动态编译生成的Byte Code反编译之后还原的优化代 码,我们看到这里采用了循环展开的优化方法。

循环展开最常用来降低循环开销,为具有多个功能单元的处理器提供指令级并行。也有利于指令流水线的调度。

小心使用内存和数据结构

使用Slice进行内存操作,Slice使用Unsafe#copyMemory实现了高效的内存拷贝,Slice仓库参考:https://github.com/airlift/slice

Facebook工程师在另一篇介绍ORCFile优化的文章中也提到使用Slice将ORCFile的写性能提高了20%~30%,参考:https://code.facebook.com/posts/229861827208629/scaling-the-facebook-data-warehouse-to-300-pb/

类BlinkDB的近似查询

为了加快avg、count distinct、percentile等聚合函数的查询速度,Presto团队与BlinkDB作者之一Sameer Agarwal合作引入了一些近似查询函数approx_avg、approx_distinct、approx_percentile。approx_distinct使用HyperLogLog Counting算法实现。

GC控制

Presto团队在使用hotspot java7时发现了一个JIT的BUG,当代码缓存快要达到上限时,JIT可能会停止工作,从而无法将使用频率高的代码动态编译为native代码。

Presto团队使用了一个比较Hack的方法去解决这个问题,增加一个线程在代码缓存达到70%以上时进行显式GC,使得已经加载的Class从perm中移除,避免JIT无法正常工作的BUG。

Presto TPCH benchmark测试

介绍了上述这么多点,我们最关心的还是Presto性能测试,Presto中实现了TPCH的标准测试,下面的表格给出了Presto 0.60 TPCH的测试结果。直接运行presto-main/src/test/java/com/facebook/presto/benchmark/BenchmarkSuite.java。

benchmarkName cpuNanos(MILLISECONDS) inputRows inputBytes inputRows/s inputBytes/s outputRows outputBytes outputRows/s outputBytes/s
                      count_agg     2.055ms   1.5M  12.9MB    730M/s  6.12GB/s      1      9B     486/s  4.28KB/s
                 double_sum_agg    14.792ms   1.5M  12.9MB    101M/s   870MB/s      1      9B      67/s    608B/s
                       hash_agg   174.576ms   1.5M  21.5MB   8.59M/s   123MB/s      3     45B      17/s    257B/s
               predicate_filter    68.387ms   1.5M  12.9MB   21.9M/s   188MB/s  1.29M  11.1MB   18.8M/s   162MB/s
                     raw_stream     1.899ms   1.5M  12.9MB    790M/s  6.62GB/s   1.5M  12.9MB    790M/s  6.62GB/s
                         top100    58.735ms   1.5M  12.9MB   25.5M/s   219MB/s    100    900B    1.7K/s    15KB/s
         in_memory_orderby_1.5M  1909.524ms   1.5M  41.5MB    786K/s  21.7MB/s   1.5M  28.6MB    786K/s    15MB/s
                     hash_build   588.471ms   1.5M  25.7MB   2.55M/s  43.8MB/s   1.5M  25.7MB   2.55M/s  43.8MB/s
                      hash_join  2400.006ms     6M   103MB    2.5M/s  42.9MB/s     6M   206MB    2.5M/s  85.8MB/s
            hash_build_and_join  2996.489ms   7.5M   129MB    2.5M/s    43MB/s     6M   206MB      2M/s  68.8MB/s
              hand_tpch_query_1  3146.931ms     6M   361MB   1.91M/s   115MB/s      4    300B       1/s     95B/s
              hand_tpch_query_6   345.960ms     6M   240MB   17.3M/s   695MB/s      1      9B       2/s     26B/s
sql_groupby_agg_with_arithmetic  1211.444ms     6M   137MB   4.95M/s   113MB/s      2     30B       1/s     24B/s
                  sql_count_agg     3.635ms   1.5M  12.9MB    413M/s  3.46GB/s      1      9B     275/s  2.42KB/s
             sql_double_sum_agg    16.960ms   1.5M  12.9MB   88.4M/s   759MB/s      1      9B      58/s    530B/s
          sql_count_with_filter    81.641ms   1.5M  8.58MB   18.4M/s   105MB/s      1      9B      12/s    110B/s
                sql_groupby_agg   169.748ms   1.5M  21.5MB   8.84M/s   126MB/s      3     45B      17/s    265B/s
           sql_predicate_filter    46.540ms   1.5M  12.9MB   32.2M/s   277MB/s  1.29M  11.1MB   27.7M/s   238MB/s
                 sql_raw_stream     3.374ms   1.5M  12.9MB    445M/s  3.73GB/s   1.5M  12.9MB    445M/s  3.73GB/s
                    sql_top_100    60.663ms   1.5M  12.9MB   24.7M/s   212MB/s    100    900B   1.65K/s  14.5KB/s
                  sql_hash_join  4421.159ms   7.5M   129MB    1.7M/s  29.1MB/s     6M   206MB   1.36M/s  46.6MB/s
        sql_join_with_predicate  1008.909ms   7.5M   116MB   7.43M/s   115MB/s      1      9B       0/s      8B/s
              sql_varbinary_max   224.510ms     6M  97.3MB   26.7M/s   433MB/s      1     21B       4/s     93B/s
             sql_distinct_multi   257.958ms   1.5M    32MB   5.81M/s   124MB/s      5    112B      19/s    434B/s
            sql_distinct_single   112.849ms   1.5M  12.9MB   13.3M/s   114MB/s      1      9B       8/s     79B/s
               sql_tpch_query_1  3168.782ms     6M   361MB   1.89M/s   114MB/s      4    336B       1/s    106B/s
               sql_tpch_query_6   286.281ms     6M   240MB     21M/s   840MB/s      1      9B       3/s     31B/s
                       sql_like  3497.154ms     6M   232MB   1.72M/s  66.3MB/s  1.15M  9.84MB    328K/s  2.81MB/s
                         sql_in    80.267ms     6M  51.5MB   74.8M/s   642MB/s     25    225B     311/s  2.74KB/s
                sql_semijoin_in  1945.074ms   7.5M  64.4MB   3.86M/s  33.1MB/s     3M  25.8MB   1.54M/s  13.2MB/s
                sql_regexp_like  2233.004ms   1.5M  76.6MB    672K/s  34.3MB/s      1      9B       0/s      4B/s
     sql_approx_percentile_long   587.748ms   1.5M  12.9MB   2.55M/s  21.9MB/s      1      9B       1/s     15B/s
               sql_between_long    53.433ms   1.5M  12.9MB   28.1M/s   241MB/s      1      9B      18/s    168B/s
sampled_sql_groupby_agg_with_arithmetic  1369.485ms    6M   189MB   4.38M/s   138MB/s      2     30B       1/s     21B/s
          sampled_sql_count_agg    11.367ms   1.5M  12.9MB    132M/s  1.11GB/s      1      9B      87/s    791B/s
sampled_sql_join_with_predicate  1338.238ms   7.5M   180MB   5.61M/s   135MB/s      1      9B       0/s      6B/s
     sampled_sql_double_sum_agg    24.638ms   1.5M  25.7MB   60.9M/s  1.02GB/s      1      9B      40/s    365B/s
             stat_long_variance    26.390ms   1.5M  12.9MB   56.8M/s   488MB/s      1      9B      37/s    341B/s
         stat_long_variance_pop    26.583ms   1.5M  12.9MB   56.4M/s   484MB/s      1      9B      37/s    338B/s
           stat_double_variance    26.601ms   1.5M  12.9MB   56.4M/s   484MB/s      1      9B      37/s    338B/s
       stat_double_variance_pop    26.371ms   1.5M  12.9MB   56.9M/s   488MB/s      1      9B      37/s    341B/s
               stat_long_stddev    26.266ms   1.5M  12.9MB   57.1M/s   490MB/s      1      9B      38/s    342B/s
           stat_long_stddev_pop    26.350ms   1.5M  12.9MB   56.9M/s   489MB/s      1      9B      37/s    341B/s
             stat_double_stddev    26.316ms   1.5M  12.9MB     57M/s   489MB/s      1      9B      38/s    342B/s
         stat_double_stddev_pop    26.360ms   1.5M  12.9MB   56.9M/s   488MB/s      1      9B      37/s    341B/s
 sql_approx_count_distinct_long    35.763ms   1.5M  12.9MB   41.9M/s   360MB/s      1      9B      27/s    251B/s
sql_approx_count_distinct_double    37.198ms   1.5M  12.9MB   40.3M/s   346MB/s      1      9B      26/s    241B/s

美团如何使用Presto

选择presto的原因

2013年我们也用过一段时间的impala,当时impala不支持线上1.x的hadoop社区版,所以搭了一个CDH的小集群,每天将大集群的热点数据导入小集群。但是hadoop集群年前完成升级2.2之后,当时的impala还不支持2.2 hadoop版本。而Presto刚好开始支持2.x hadoop社区版,并且Presto在Facebook 300PB大数据量的环境下可以成功的得到大量使用,我们相信它在美团也可以很好的支撑我们实时分析的需求,于是决定先上线测试使用一段时间。

部署和使用形式

考虑到两个原因:1、由于Hadoop集群主要是夜间完成昨天的计算任务,白天除了日志写入外,集群的计算负载较低。2、Presto Worker节点与DataNode节点布置在一台机器上可以本地计算。因此我们将Presto部署到了所有的DataNode机器上,并且夜间停止Presto服务,避免占用集群资源,夜间基本也不会有用户查询数据。

Presto二次开发和BUG修复

年后才正式上线Presto查询引擎,0.60版本,使用的时间不长,但是也遇到了一些问题:

  1. 美团的Hadoop使用的是2.2版本,并且开启了Security模式,但是Presto不支持Kerberos认证,我们修改了Presto代码,增加了Kerberos认证的功能。
  2. Presto还不支持SQL的隐式类型转换,而Hive支持,很多自助查询的用户习惯了Hive,导致使用Presto时都会出现表达式中左右变量类型不匹配的问题,我们增加了隐式类型转换的功能,大大减小了用户SQL出错的概率。
  3. Presto不支持查询lzo压缩的数据,需要修改hadoop-lzo的代码。
  4. 解决了一个having子句中有distinct字段时查询失败的BUG,并反馈了Presto团队 https://github.com/facebook/presto/pull/1104

所有代码的修改可以参考我们在github上的仓库 https://github.com/MTDATA/presto/commits/mt-0.60

实际使用效果

这里给出一个公司内部开放给分析师、PM、工程师进行自助查询的查询中心的一个测试报告。这里选取了平时的5000个Hive查询,通过Presto查询的对比见下面的表格。

自助查询sql数

hive

presto

presto/hive

1424

154427s

27708s

0.179424582489

 

presto查询hive presto查询原理_执行计划_25

presto查询hive presto查询原理_数据_26

OLAP 引擎分类

OLAP 引擎的一些常见分类大概有这两种:

  • ROLAP,叫关系型 OLAP。它的特点是基于关系性模型,计算的时候,根据原始数据去做聚合运算。常见的小数据量可以利用 MySQL、Oracle 这种传统数据库,而大数据量可以利用 Spark SQL、Presto 这些项目。
  • MOLAP,叫多维 OLAP。它的特点就是它会基于一个预定义的模型,我需要知道,要根据什么维度,要去算哪些指标,我提前就把这些结果弄好,存储在引擎上。当查询的时候,根据结果简单地做下汇总就可以得出来。

 

presto查询hive presto查询原理_presto查询hive_27

 

图1

如图 1,是简单的 ROLAP 模型,具体操作流程刚才已经提到了,它的优势就是,它其实就是个数据库,所以任何的 SQL 都可以在里面执行。数据是没有冗余的。

缺点就是数据量很大的时候,计算速度会下降很多,所以并发会比较差。它的场景就是不知道要查什么数据,灵活性非常高时,一般会选 ROLAP。

数据模型
Presto使用Catalog、Schema和Table这3层结构来管理数据。

---- Catalog:就是数据源。Hive是数据源,Mysql也是数据源,Hive 和Mysql都是数据源类型,可以连接多个Hive和多个Mysql,每个连接都有一个名字。一个Catalog可以包含多个Schema,大家可以通过show catalogs 命令看到Presto连接的所有数据源。
---- Schema:相当于一个数据库实例,一个Schema包含多张数据表。show schemas from 'catalog_name'可列出catalog_name下的所有schema。
---- Table:数据表,与一般意义上的数据库表相同。show tables from 'catalog_name.schema_name'可查看'catalog_name.schema_name'下的所有表。

在Presto中定位一张表,一般是catalog为根,例如:一张表的全称为 hive.test_data.test,标识 hive(catalog)下的 test_data(schema)中test表。

可以简理解为:数据源的大类.数据库.数据表。

 

High-level Components of Presto

As a distributed system, Presto has two types of servers: Coordinator and Worker. The community version is supposed to have one coordinator and one or more workers. Some companies have their version of Highly-Available coordinators and there is a GitHub issue tracking the HA deployment of community version. However, the HA scenario is out of the scope of this article. Therefore we will continue with the assumption that there is always a single coordinator and one or more workers. Of course, the same server can act as a coordinator and a worker but it is not preferred in a production environment.

 

 

presto查询hive presto查询原理_数据_28

 

Presto CLI

The command-line tool is available to download from the official website or if you build from the source code, you can find it in presto/presto-cli/target folder. You can run the presto-cli-xxx-executable.jar from the terminal and there are a whole bunch of command line parameters available to use. By default. presto-cli connects to the coordinator running at localhost:8080. If the Presto server you want to connect to is running at a different location, use the --server parameter to specify the target.

Presto JDBC Driver

Presto also provides a JDBC driver to connect to the cluster. Similar to the CLI, you can either download it from the website or get it from the built source code (presto/presto-jdbc/target).

 

Both Presto CLI and Presto JDBC driver are using RESTful APIs under the hood. Therefore there is always an opportunity to use a REST client to connect to the Presto server.

Presto Dashboard

Presto Dashboard is a tool to get statistics about submitted queries and the system. You can see how many workers are available in the cluster and some statistics about the query including the execution time, input size, output size, and some advanced details on how the query is compiled into a physical plan and how it is scheduled on available workers.

Unlike the CLI and JDBC driver, the dashboard is a read-only platform. There are some third-party tools like Apache Superset or Airpal that allows you to run SQL from a GUI. You can find the list of available tools on the official website.

Coordinator

The coordinator is a Presto server that receives SQL queries, compiles them, optimizes them, and schedules them on workers. Besides, it also maintains the cluster of workers and runs the dashboard. A cluster without a coordinator is not usable. In the community version of Presto, a discovery service is running along with the coordinator to which workers register themselves to form the cluster.

Worker

As the name suggests, workers execute the query. The coordinator compiles a SQL query into logical plans and sends fragmented logical plans combined with the data source information to workers (known as tasks). Workers execute those tasks and produce the output.

In-House Terms
I am working on an article completely about the internals of Presto. However, some terms are introduced here to get yourself familiar with them. You need to know nothing about these if you use Presto just to execute some SQL queries. However if you are a database engineer working with internals of database, these keywords should be already in your dictionary.

Split
The smallest unit of data a Presto operator can process. Connectors generate splits according to their standards. For example, MySQL connector may generate one split representing a full table whereas the Hive connector may create 100 splits representing one ORC file. A split object itself does not carry the data. Instead it has the information to the data location. For example, a Hive split may have the ORC file name, length and the offset to read the piece of information defined by that split.

presto查询hive presto查询原理_presto查询hive_29

Logical Plan
A Directed Acyclic Graph generated by compiling the SQL query. Presto uses this tree representation to optimize and analyze the SQL query.

presto查询hive presto查询原理_SQL_30

Stage
A sub-tree (not necessary to have branches) of a logical plan after fragmentation is called a stage.

presto查询hive presto查询原理_presto查询hive_31

Task
A stage bundled with a split and ready for scheduling is called a task. A task informs the worker what to do and from where to get the data.

Physical Plan
A physical plan is created by workers by converting the given stage into an operator pipeline. Usually the nodes in the logical plan will be replaced by operators and some additional operators will be injected in between to connect them.

presto查询hive presto查询原理_presto查询hive_32

Fore more details about the internals of Presto, read the Presto: SQL on Everything paper.

Presto服务进程

Presto集群中一共有两种服务器进程:Coordinator服务进程和Worker服务进程,其中Coordinator服务进程的主要作用是:接收查询请求、解析查询语句、生成查询执行计划、任务调度和Worker管理。而Worker服务进程则执行被分解后的查询执行任务:Task。

1.Coordinator
Coordinator服务进程部署于集群中一个单独的节点上,是整个Presto集群的管理节点。Coordinator服务进程主要用于接收客户端提交的查询,查询语句解析,生成查询执行计划、Stage和Task并对生成的Task进行调度。除此之外,Coordinator还对集群中的所有Worker进行管理。Coordinator进程是整个Presto集群的Master进程,该进程即与Worker进程通信从而获得最新的Worker信息,又与Client进行通信,从而接受查询请求,而所有的这些工作都是通过Coordinator上的StatementResource类提供的RESTful服务来完成的。

2.Worker
在一个Prestor集群中,存在一个Coordinator节点和多个Worker节点。Coordinator节点是管理节点,而Worker节点就是工作节点。在每个Worker节点上都会存在一个Worker服务进程,该服务进程主要进行数据的处理以及Task的执行。Worker服务进程每隔一定的时间都会向Coordinator上的RESTful服务发关心跳,从而告知Coordinator:我还活着,并接收你的调度。当客户端提交一个查询的时候,Coordinator则会从当前存活的Worker列表中选择出合适的Worker节点去运行Task。而Worker在执行每个Task的时候又会进一步对当前Task读入的每个Spli进行一系列的操作和处理。

Presto模型

Presto可以通过多种不同类型的Connector访问多种数据源,目前支持的Connector包括:Hive、JMX、MySQL、Cassandra、PostgreSQL以及Kafka。下面介绍Presto是如何访问不同类型的数据源的,并对Presto中的模型和概念进行描述。

1.Connector
Presto是通过多种多样的Connector来访问多种不同的数据源的。你可以将Connector当作Presto访问各种不同数据源的驱动程序。一般情况下,Presto针对每种数据源都有与之对应的Connector。每种Connector都实现了Presto中标准的SPI接口,因此只要你实现Presto中的标准的SPI接口,就可以轻易地实现使用适合自己特定需求的Connector来访问特定的数据源。Presto目前支持的Connector 有Hive、JMX、MySQL、Cassandra、PostgreSQL等,都有其对应的Build-In Connector(内置的Connector)。
当你需要使用某种Connector访问特定的数据源时,需要在$PRESTO_HOME/etc/catalog、中创建一个配置文件:example.properties)(文件名字限制,但是其后缀名必须为.properties),在该配置文件中必须要设置一个属性:connector.name,该属性是必须设置的。Presto中Connector Manager就是通过该配置属性来决定使用哪个Connector去访问相应的数据源的。例如,你现在需要访问一个hive数据源,那么你在配置文件中就需要将属性connect.name设置为Hive-cdh5或者Hive-cdh4,这样Presto就会使用内置的Hive connector去访问Hive数据仓库中相应的数据。

2.Catalog
Presto中的Catalog类型于Mysql中的数据库实例。而Schema就类似于Mysql中的一个Database。通过使用特定的Connector访问Catalog中指定的数据源,一个Catalog中可以包含多个Schema。那么怎么定义一个Catalog呢?其实你不需要特意去指定Catalog。正如之前说的,假设你想访问Hive中的数据,则需要在$PRESTO_HOME/etc/catalog中创建一个配置文件:example.properties。该配置文件中定义了诸如Hive store的URI等访问Hive中的数据所需要的所有配置项,并且配置文件的名字就是Catalog名字:example。从这里可以看出Presto中配置文件的名字(不带.properties)就是Catalog的名字。
当你访问Catalog中的某个表时,该表的全名总是以Catalog的名字开始。例如名字为example.schema1.tables1的表,指的是表table1位于名为schema1的schema中,而schema1又位于名为example的Catalog中。

3.Schema
Presto中的Schema就类似于Mysql中的Database。一个Catalog名称和一个Schema名称唯一确定了可以查询的一系列表的集合。当通过Presto去查询Hive或者Mysql中的数据时,你会发现Presto中的Schema与Hive或者Mysql中的Database是相对应的。

4.Table
Presto中的Table与传统数据库中的Table的含义是一样的。

Preto查询执行模型

Preto在执行SQL语句时,将这些SQL语句解析为相应的查询,并在分布式集群中执行这些查询。

1.Statement
Statement语句。其实就是指我们输入的SQL语句。Presto支持需要ANSI标准的SQL语句。这种语句由子句(Clause)、表达式(Expression)和断言(Predicate)组成。

Presto为什么将语句(Statement)和查询(Query)的概念分开呢?
因为在Presto中,语句和查询本身就是不同的概念。语句指的是终端用户输入的用文字表示的SQL语句;当Presto执行输入的SQL语句时,会根据SQL语句生成查询执行计划,进而生成可以执行的查询(Query),而查询代表的是分布到所有的Worker之间执行的实际查询操作。

2.Query
Query即查询执行。当Presto接收一个SQL语句并执行时,会解析该SQL语句,将其转变成一个查询执行和相关的查询执行计划。一个查询执行代表可以在Presto集群中运行的查询,是由运行在各个Worker上且各自之间相互关联的阶段(Stage)组成的。

那么SQL语句与查询执行之间有什么不同呢?
其实很简单,你可以认为SQL语句就是提交给Presto的用文字表示的SQL执行语句。而查询执行则是为了完成SQL语句所表述的查询而实例化的配置信息、组件、查询执行计划和优化信息等。一个查询执行由Stage、Task、Driver、Split、Operator和DataSource组成。这些组件之间通过内部联系共同组成了一个查询执行,从而得到SQL语句表述的查询,并得到相应的结果集。

3.Stage
Stage即查询执行阶段。当Presto运行Query时,Presto会将一个Query拆分成具有层级关系的多个Stage,一个Stage就代表查询执计划的一部分。例如,当我们执行一个查询,从Hive的一张具有1亿条记录的表中查询数据并进行聚合操作时,Presto会创建一个Root Stage(后面会介绍,该Stage就是Single Stage),该Stage聚合其上游Stage的输出数据,然后将结果输出给Coordinator,并由Coordinator将结果输出给终端用户。
通常情况下Stage之间是树状的层次结构。每个Query都有一个Root Stage。该Stage用于聚集所有其他Stage的输出数据,并将最终的数据反馈给终端用户。需要注意的是,Stage并不会在集群中实际执行,它只是Coordinator用于对查询计划进行管理和建模的逻辑概念。每个Stage(除了Single Stage和Source Stage)都会有输入和输出,都会从上游Stage读取数据,然后将产生结果输出给下游Stage。需要注意的是;Source Stage没有上游,它从Connector获取数据,Single Stage没有下游,它的结果直接输出给Coordinator,它由Coordinator输出给终端用户。
Presto中的Stage共有4种,具体介绍如下:
Coordinator_Only:这种类型的Stage用于执行DDL或者DML语句中最终的表结构创建或者更改。
Single:这种类型的Stage用于聚合子Stage的输出,并将结果数据输出给终端用户。
Fixed:这种类型的Stage用于接受其子Stage产生的数据并在集群中对这些数据进行分布式的聚合或者分组计算。
Source:这种类型的Stage用于直接连接数据源,从数据源读取数据,在读取数据的同时,该阶段也会根据Presto对查询执行计划的优化完成相关的断言下发(Predicate PushDown)
和条件过滤等。

说明:一个SQL查询可以被分解 多个前后关联的Stage,在这里我们约定:按照数据的流向,越靠近数据源的Stage越处于上游,越远离数据源的Stage越处于下游。

4.Exchange
Exchange的字面意思就是“交换”。Presto的Stage是通过Exchange来连接另一个Stage的。Exchange用于完成有上下游关系的Stage之间的数据交换。在Presto中有两种Exchange:Output Buffer和Exchange Client。生产数据的Stage通过名为Output Buffer的Exchange将数据传送给其下游的Stage。消费数据的Stage通过名为Exchange从上游Stage读取数据。
如果当前Stage是Surce类型的Stage,那么该Stage则是直接通过相应的Connector从数据源读取数据的。而该Stage则是通过名为Source Operator的Operator与Connector进行交互的,例如,一个Source Stage直接从HDFS获取数据,那么这种操作不是通过Exchange Client来完成的,而是通过运行于Driver中的Source Operator来完成的。

5.Task
从前面的介绍中可以知道,Stage并不会在Presto集群中实际运行,它仅代表针对于一个SQL语句查询执行计划中的一部分查询的执行过程,只是用来对查询执行计划进行管理和建模。Stage在逻辑上又被分为一系列的Task,这些Task则是需要实际运行在Presto的各个Worker节点上的。
在Presto集群中,一个查询执行被分解成具有层次关系的一系列的Stage,一个Stage又被拆分为一系列的Task。每个Task处理一个或者多个Split。每个Task都有对应的输入和输入。一个Stage被分解为多个Task,从而可以并行地执行一个Stage。Task也采用了相应的机制:一个Task也可以被分解为一个或者多个Driver,从而并行地执行一个Task。

6.Driver
一个Task包含一个或者多个Driver。一个Driver其实就是作用于一个Split的一系列Operator的集合。因此一个Driver用于处理一个Split,并且生成相应的输出,这些输出由Task收集并且传送给下游Stage中的Task。一个Driver拥有一个输入和一个输出。

7.Operator
一个Operator代表对一个Spit的一种操作,例如过滤、加权、转换等。一个Operator依次读取一个Split中的数据,将Operator所代表的计算和操作作用于Split的数据上,并产生输出。每个Operator均会以Page为最小处理单元分别读取输入数据和产生输出数据。Operator每次只会读取一个Page对象,相应地,每次也只会产生一个Page对象。

8.Split
Split即分片,一个分片其实就是一个大的数据集中的一个小的子集。而Driver则是作用于一个分片上的一系列操作的集合,而每个节点上运行的Task,又包含多个Driver,从而一个Task可以处理多个Split。其中每一种操作均由一个Operator表示。分布式查询执行计划的源Stage(Source Stage)通过Connector从数据源获取多个分片。Source Stage对Split处理完毕之后,会将输出传递给其下游Stage(通常其下游Stage的类型为Fixed或者Single)。
当Presto执行一个查询的时候,首先会从Coordinator得到一个表对应的所有Split。然后Presto就会根据查询执行计划,选择合适的节点运行相应的Task处理Split。

9.Page
Page是Presto中处理的最小数据单元。一个Page对象包含多个Block对象,而每个Block对象是一个字节数组,存储一个字段的若干行。多个Block横切的一行是真实的一行数据。一个Page最大为1MB,最多16*1024行数据