即席查询
开源的分布式SQL查询引擎,开源的,并不是阿帕奇的。用它就是写SQL
GB->PB 查询比较快的,查询延迟比较低的,秒级查询的场景
Presto不是一个标准的数据库,查询需要对接其他数据源,查询引擎 分析引擎
如果想要计算的数据分散在Hdfs、Hive、ES、Hbase、MySql、Kafka中,应该怎么做?
Facebook科学家们发现目前并没有一款合适的计算引擎,最终决定开发一款MPP交互式计算引擎
2012年秋天进行研发,2013年开源出来并成功用其对300PB的数据进行运算,奠定了Presto的地位
master/slave架构
优点:
缺点:
解决:
PipeLine模式:
Worker节点将小任务封装成一个PrioritizedSplitRunner对象,放入pending split优先级队列中
Worker节点启动一定数目的线程进行计算,线程数task.shard.max-threads=availableProcessors() * 4
每个空闲线程从队列中取出PrioritizedSplitRunner对象执行,每隔1秒,判断任务是否执行完成,如果完成,从allSplits队列中删除,如果没有,则放回pendingSplits队列
每个任务的执行流程如下图右侧,依次遍历所有Operator,尝试从上一个Operator取一个Page对象,如果取得的Page不为空,交给下一个Operator执行
节点间流水线计算
ExchangeOperator的执行流程图,ExchangeOperator为每一个Split启动一个HttpPageBufferClient对象,主动向上一个Stage的Worker节点拉数据,拉取的最小单位也是一个Page对象,取到数据后放入Pages队列
presto采取三层表结构:
presto的存储单元包括:
ORC文件格式 ORC文件格式是一种Hadoop生态圈中的列式存储格式
文件footer里存储了每个条带(script)的最大值最小值,可以用来过滤
snappy压缩
Snappy 是一个 C++ 的用来压缩和解压缩的开发包。其目标不是最大限度压缩或者兼容其他压缩格式,而是旨在提供高速压缩速度和合理的压缩率
列式存储 行式存储各有各自的优缺点,在OLAP场景下,我们需要的就是列式存储。
一个查询是由Stage、Task、Driver、Split、Operator和DataSource组成
每个查询分解为多个stage
每个 stage拆分多个task(每个PlanFragment都是一个Stage)
每个task处理一个或多个split(Driver)
每个Driver处理一个split,每个Driver都是作用于一个split的一系列Operator集合
每个split对应一个或多个page对象 :一个大的数据集中的一个小的子集
每个page包含多个block对象,每个block对象是个字节数据 (presto中最小处理单位)
Operator:一个operator代表对一个split的一种操作(如过滤、转化等)
operator每次只会读取一个page对象 并且每次也只会产生一个page对象
Cli —> parser—>analysis —> 优化 —> 拆分为子plan —> 调度
一个简单查询,最终构造为一个QueryPlan。对于较复杂的查询,是多个QueryPlan的组合。
Task是Stage的具体分布式查询计划,由Coordinator进行划分
Task需要通过Http接口发送到具体的Node上去,然后生成对应的本地执行计划
一个Stage可以分解为多个同构的Task
Task是发送到本地的执行计划
Task被分为多种Driver去并发的执行
一个Driver内包含多个Operator
真正操作Page数据的是Operator
一个Operator代表一种操作,比如Scan->读取数据, Filter -> 过滤数据
优化过程
谓词下推:基本思想即:将过滤表达式尽可能移动至靠近数据源的位置,以使真正执行时能直接跳过无关的数据。(将过滤表达式下推到存储层直接过滤数据,减少传输到计算层的数据量)
Presto是一款内存计算型的引擎,所以对于内存管理必须做到精细
使用Slice进行内存操作,Slice使用Unsafe#copyMemory实现了高效的内存拷贝
Presto采用逻辑的内存池,来管理不同类型的内存需求。
Presto把整个内存划分成三个内存池,分别是System Pool ,Reserved Pool, General Pool。
为什么要使用内存池
System Pool用于系统使用的内存,例如机器之间传递数据
为什么需要保留区内存呢?并且保留区内存正好等于query在机器上使用的最大内存?
如果没有Reserved Pool, 那么当query非常多,并且把内存空间几乎快要占完的时候,某一个内存消耗比较大的query开始运行。但是这时候已经没有内存空间可供这个query运行了,这个query一直处于挂起状态,等待可用的内存。 但是其他的小内存query跑完后,又有新的小内存query加进来。由于小内存query占用内存小,很容易找到可用内存。 这种情况下,大内存query就一直挂起直到饿死。
所以为了防止出现这种饿死的情况,必须预留出来一块空间,共大内存query运行。 预留的空间大小等于query允许使用的最大内存。Presto每秒钟,挑出来一个内存占用最大的query,允许它使用reserved pool,避免一直没有可用内存供该query运行。
Presto内存管理,分两部分:
query内存管理
机器内存管理
当query内存和机器内存汇总之后,coordinator会挑选出一个内存使用最大的query,分配给Reserved Pool。
内存管理是由coordinator来管理的, coordinator每秒钟做一次判断,指定某个query在所有的机器上都能使用reserved 内存。那么问题来了,如果某台机器上,,没有运行该query,那岂不是该机器预留的内存浪费了?为什么不在单台机器上挑出来一个最大的task执行。原因还是死锁,假如query,在其他机器上享有reserved内存,很快执行结束。但是在某一台机器上不是最大的task,一直得不到运行,导致该query无法结束。
Presto
在数据进行shuffle
的时候,是Pull
模式。
在两端负责分发和交换数据的类分别是ExchangClient
和OutputBuffer
。
Source Stage
把数据从Connector
中拉取出来,这时候需要给下一个FixedStage
进行处理。
他会先把数据放在OutputBuffer
中,等待上游把数据请求过去,而上游请求数据的类就是ExchangeClient
。
@Path("{taskId}/results/{bufferId}/{token}")
ExchangeClient
ExchangOperator
@Override
public Page getOutput()
{
SerializedPage page = exchangeClient.pollPage();
if (page == null) {
return null;
}
}
ExchangOperator接收的HttpPageBufferClient
这在一定的程度上缓解了请求的压力,同时为节约了下游的cpu资源。因为如果那台服务器挂了,那么一直无意义的http请求是毫无意义的,还会一直浪费cpu资源。
BroadcastOutputBuffer和PartitionedOutputBuffer
每来一个Page,把大小加进去,每出一个Page把大小减去,如果当前攒着的大小超过了阈值,那么就返回Blocked,把整个Driver给Block掉,不去执行了。
partition的情形,因为每一个Page进来,只会分到指定的一个ClientBuffer中,移除的时候直接减去就行了。
对于广播 定怎么知道已经被所有的Task
取走了呢
包装了一个PageReference
类,传递进去原先的Page
和一个回调,这个回调就是把当前的BufferSize
减去CurPageSize
。
引用计数的实现,每add
到Buffer
中一次,计数就加一,每从buffer
中移除一次,计数就减一,当为0的时候,就调用回调把size
减去。
由于采用列式存储,选择需要的字段读取、减少数据量,避免*
能以分区做过滤条件尽量使用分区
group by语句优化,合理安排语句顺序count(distinct)数据 降序排序 id gender
order by要使用limit 排序需求大部分不会全局的排序集。最终要合并到一个节点,压力过大。没个worker的前100.
join 大表放前边,小表放后边 hive不需要考虑这个问题。为什么:
字段名饮用,票号,用的是双引号。
时间类型,
目前仅支持parquet格式 列式存储格式的读取,不支持插入
PrestoServer类 启动类,启动一个server线程
加载框架 相比Spring的IOC,Google的Injector更轻量级一些
加载插件 plugins目录下,将其注册到PluginManager中
加载数据源
StaticCatalogStore类
加载资源管理
加载权限管控
启动服务
启动客户端
./prestocli --server 127.0.0.1:8080 --catalog mysql --schema default
show shemas;
should tables;