前言
1 了解计算引擎的发展史
- 第一代计算引擎:基于磁盘的分布式计算
- MapReduce
- 优点:对硬件要求不高,开发简单,成本较低
- 缺点:灵活性差,性能较差
- 第二代计算引擎:基于DAG的分布式计算 Hive => 默认引擎调整为Tez
- Tez
- 优点:基于MR引入DAG设计,灵活性高,提升了MR的性能 => 减少了落盘次数
- 缺点:性能不足以满足需求,底层还是MR
- 第三代计算引擎:基于内存的分布式计算 => 离线计算
- Spark、Impala、Presto => 写SQL(Impala > Presto > Spark)
- 优点:基于内存式计算,性能较高,支持SQL接口
- 缺点:内存消耗较高,成本较高
- 第四代计算引擎:基于实时数据流的分布式计算 => 实时计算
- Storm、Flink
- 优点:实时数据处理,性能最高,数据价值可以实现最大化
- 缺点:目前技术功能和接口不是非常成熟,还需要不断完善
2 Presto
1 功能及应用常场景
Presto是一个Facebook技术团队开源的分布式SQL查询引擎,用于代替Hive,适用于交互式分析查询,数据量支持GB到PB
功能:实现基于内存的分布式数据计算引擎
特点:性能高,接口丰富
高性能的数据查询,数据处理全部基于内存来实现
清晰的架构,是一个能够独立运行的系统,不依赖于任何其他外部系统
简单的数据结构,列式存储,逻辑行,大部分数据都可以轻易的转化成presto所需要的这种数据结构
丰富的插件接口,完美对接外部存储系统,或者添加自定义的函数
适合的场景:大数据的交互式分析查询:秒级的查询时延
AWS上Athena作为一个大数据商业服务提供给商业付费客户,底层就是Presto服务
京东精准营销平台中JD-Presto作为大数据Ad-Hoc计算平台,极大提升了采销部门进行精准营销活动的效果和效率
美团在2014年在Hadoop集群上搭建了Presto来服务于公司内部的分析师、PM、工程师
不适合的场景
OLTP:Presto不是数据库,没有存储结构,只是一个基于存储之上的一个分布式查询工具
数据分析引擎:Presto的数据都在内存中做计算,复杂的分析会导致OOM【out of memory】
小结
Presto是第三代计算引擎,Facebook在2012年研发,免费开源
Presto内存计算适合海量数据分析(秒级查询),不适合做数据库以及超复杂数据分析
优点:内存计算提高性能,可以连接多方数据库
2 分布式架构
内存型 + 分布式计算
3 Presto的常用语法
语法:https://prestodb.io/docs/current/
Presto是通用型计算引擎:数据源是多样化(支持MySQL、支持Hive、支持Oracle)
法设计满足大多数数据库都支持的语法
数据类型:
- tinyint、smallint、integer、 bigint、double、decimal
- varchar、 char、varbinary、json(注意:Presto中没有string类型)
- date、time、 timestamp
- array, map
- boolean
Presto有些数据类型无法兼容Hive!!!
虽然Presto可以替代Hive,但是在实际工作中,两者一般都要配合在一起使用:
Hive负责数据库、数据表构建(DDL、DML)
Presto(DQL)
DDL语法:支持,但是一般都不在Presto中使用
数据库操作
-- 建库
CREATE SCHEMA [ IF NOT EXISTS ] schema_name
CREATE SCHEMA IF NOT EXISTS db_test01;
-- 列举
SHOW SCHEMAS
-- 删库
DROP SCHEMA [ IF EXISTS ] schema_name
DROP SCHEMA IF EXISTS db_test01;
数据表操作
-- 建表
CREATE TABLE [ IF NOT EXISTS ] table_name (
colName1 type1 comment,
colName2 type2 comment,
colName3 type3 comment,
……
colNameN typeN comment
)
[ COMMENT table_comment ]
[ WITH ( property_name = expression [, ...] ) ]
CREATE TABLE default.orders (
orderkey bigint,
orderstatus varchar,
totalprice double,
orderdate date
)
COMMENT 'A table to keep track of orders.'
WITH (format = 'ORC')
-- 列举
SHOW TABLES
-- 查看
DESC table_name
-- 删表
DROP TABLE [ IF EXISTS ] table_name
DML语法
INSERT:会用到,将一条SQL语句的结果保存到表中
-- 语法
INSERT INTO table_name [ ( column [, ... ] ) ] query
-- 示例
INSERT INTO orders SELECT * FROM new_orders;
INSERT INTO cities VALUES (2, 'San Jose'), (3, 'Oakland');
INSERT INTO nation (nationkey, name, regionkey) VALUES (26, 'POLAND', 3);
注意:Presto不支持insert overwrite,如果想覆盖必须在Hive端先truncate table清空,然后再Presto中写入
DELETE
-- 语法
DELETE FROM table_name [ WHERE condition ]
-- 示例
DELETE FROM lineitem WHERE shipmode = 'AIR';
DELETE FROM lineitem WHERE orderkey IN (SELECT orderkey FROM orders WHERE priority = 'LOW');
注意:以上delete from…where语句在MySQL连接器支持,在Hive中不支持,但是在Hive端,支持delete from清空数据表操作!!!
DQL语法:主要在Presto中使用的语法
-- 基础语法
[ WITH with_query [, ...] ]
SELECT [ ALL | DISTINCT ] select_expr [, ...]
[ FROM from_item [, ...] ]
[ WHERE condition ]
[ GROUP BY [ ALL | DISTINCT ] grouping_element [, ...] ]
[ HAVING condition]
[ ORDER BY expression [ ASC | DESC ] [, ...] ]
[ { LIMIT [ count | ALL ] } ]
-- 子查询语法
SELECT a, b
FROM (
SELECT a, MAX(b) AS b FROM t GROUP BY a
) AS x;
WITH x AS (SELECT a, MAX(b) AS b FROM t GROUP BY a)
SELECT a, b FROM x;
-- JOIN语法
[ INNER ] JOIN
LEFT [ OUTER ] JOIN
RIGHT [ OUTER ] JOIN
FULL [ OUTER ] JOIN
CROSS JOIN
-- 示例1:基础查询
SELECT
count(*),
mktsegment,
nationkey,
CAST(sum(acctbal) AS bigint) AS totalbal
FROM customer
GROUP BY mktsegment, nationkey
HAVING sum(acctbal) > 5700000
ORDER BY totalbal DESC;
-- 示例2:子查询
SELECT
name
FROM nation
WHERE regionkey IN (SELECT regionkey FROM region)
-- 示例3:JOIN
SELECT
*
FROM table_1
JOIN table_2
ON table_1.key_A = table_2.key_A AND table_1.key_B = table_2.key_B
函数
数学函数:https://prestodb.io/docs/current/functions/math.html
聚合函数:https://prestodb.io/docs/current/functions/aggregate.html
字符函数:https://prestodb.io/docs/current/functions/string.html
日期函数:https://prestodb.io/docs/current/functions/datetime.html
窗口函数:https://prestodb.io/docs/current/functions/window.html
工作中使用
DDL:一般在数据源【Hive、MySQL】中执行建库建表
DML:Presto中通过insert将Select语句的结果进行保存
DQL:Presto中通过select语句查询处理数据
Presto基本语法与MySQL和Hive类似,但是有所不同
工作中:Hive负责建库、建表以及导入数据,Presto一般负责复杂业务的查询
4 Presto的使用优化
比较 Impala、Presto、Spark
Impala:性能最快、集成HDFS的数据接口、SQL语法支持非常差
用起来不方便,接口比较少
场景:使用Impala对Hive或者Hbase中的数据进行即席查询或者计算
Presto:性能第二快、集成多种数据源接口
支持大数据接口的语法不统一,单一SQL内存计算功能
场景:高性能多数据源集成数据查询、处理
Spark:性能最慢,全场景的数据处理平台【离线计算、实时计算、机器学习、图计算:Python、Java、Scala、SQL、R】
架构的设计导致实时计算性能不是特别完善 => Flink
场景:用一个工具解决整套数据处理的方案
优化方法:
优化一:分区裁剪
适当对数据表进行分区,当我们筛选数据时,可以通过where指定要查询的分区(分区裁剪)
Hive类似,Presto会根据元信息读取分区数据,合理的分区能减少Presto数据读取量,提升查询数据性能。
优化二:ORC列式存储 => Presto => ORC
Presto对ORC文件读取做了特定优化,因此在Hive中创建Presto使用的表时,建议采用ORC格式存储。相对于Parquet,Presto对ORC支持更好。
Parquet和ORC一样都支持列式存储,但是Presto对ORC支持更好,而Impala对Parquet支持更好。在数仓设计时,要根据后续可能的查询引擎合理设置数据存储格式。
优化三:数据压缩
数据压缩可以减少节点间数据传输对IO带宽压力,对于即席查询需要快速解压,建议采用Snappy压缩。
优化四:预先排序
对于已经排序的数据,在查询的数据过滤阶段,ORC格式支持跳过读取不必要的数据。
比如对于经常需要过滤的字段可以预先排序。
-- 写入数据时排序
INSERT INTO table nation_orc partition(p)
SELECT * FROM nation SORT BY n_name;
-- 如果需要过滤n_name字段,则性能将提升。
SELECT count(*) FROM nation_orc WHERE n_name=’AUSTRALIA’;
5 Presto的内存分配
内存分类:
user memory:用户内存
跟用户数据相关的,比如读取用户输入数据会占据相应的内存,这种内存的占用量跟用户底层数据量大小是强相关的
system memory:系统内存
执行过程中衍生出的副产品,比如tablescan表扫描,write buffers写入缓冲区,跟查询输入的数据本身不强相关的内存。
内存池:内存池中来实现分配user memory和system memory
GENERAL_POOL:主要使用的内存
一般情况下,一个查询执行所需要的user/system内存都是从general pool中分配的,reserved pool在一般情况下是空闲不用的
RESERVED_POOL:备份内存
大部分时间里是不参与计算的,但是当集群中某个Worker节点的general pool消耗殆尽之后,coordinator会选择集群中内存占用最多的查询,把这个查询分配到reserved pool,这样这个大查询自己可以继续执行,而腾出来的内存也使得其它的查询可以继续执行,从而避免整个系统阻塞。
注意:reserved pool的设计问题
- 空间大小:大小上限就是集群允许的最大的查询的大小(query.total-max-memory-per-node)
- 设计缺点:普通模式下这块内存用不到的时候就是浪费的资源,对于较大的查询一般不建议使用Presto来实现
- 禁用功能:experimental.reserved-pool-enabled设置为false
- 内存耗尽:物理内存耗尽时,有一个OOM Killer的机制,对于超出内存限制的大查询SQL将会被系统Kill掉
- 注意:如果在使用Presto进行较为复杂或较大数据分析时,如果Presto worker节点突然断开,很大一部分原因就是因为OOM内存溢出,系统主动杀死了最占用内存的进程。
相关参数
user memory用户内存参数
query.max-memory-per-node:单个query操作在单个worker上user memory能用的最大值
query.max-memory:单个query在整个集群中允许占用的最大user memory
user+system总内存参数
query.max-total-memory-per-node:单个query操作可在单个worker上使用的最大(user + system)内存
query.max-total-memory:单个query在整个集群中允许占用的最大(user + system) memory
协助阻止机制:在高内存压力下保持系统稳定
当general pool常规内存池已满时,操作会被置为blocked阻塞状态,直到通用池中的内存可用为止。
此机制可防止激进的查询填满JVM堆并引起可靠性问题。
其他参数
memory.heap-headroom-per-node:这个内存是JVM堆中预留给第三方库的内存分配,presto无法跟踪统计,默认值是-Xmx * 0.3
-Xmx3G
预留第三方库内存大小 = 3G * 0.3 = 0.9G
结构
GeneralPool = 服务器总内存 - ReservedPool - memory.heap-headroom-per-node - Linux系统内存
常规内存池内存大小 = 服务器物理总内存 - 服务器linux操作系统内存- 预留内存池大小 - 预留给第三方库内存
常见的报错解决
1、
Query exceeded per-node total memory limit of xx
适当增加query.max-total-memory-per-node。
2、Query exceeded distributed user memory limit of xx
适当增加query.max-memory。
3、Could not communicate with the remote task. The node may have crashed or be under too much load
内存不够,导致节点crash,可以查看/var/log/message。
建议参数设置
1、query.max-memory-per-node和query.max-total-memory-per-node是query操作使用的主要内存配置,因此这两个配置可以适当加大。
2、memory.heap-headroom-per-node是三方库的内存,默认值是JVM-Xmx * 0.3,可以手动改小一些。
3、 各节点JVM内存推荐大小: 当前节点剩余内存*80% => free -h
4、 对于heap-headroom-pre-node第三方库的内存配置: 建议jvm内存的15%左右
5、 在配置的时候, 不要正正好, 建议多预留一点点, 以免出现问题
注意:
1、query.max-memory-per-node小于query.max-total-memory-per-node。
2、query.max-memory小于query.max-total-memory。
3、query.max-total-memory-per-node 与memory.heap-headroom-per-node 之和必须小于 jvm max memory,也就是jvm.config 中配置的-Xmx。
3G + -Xmx5G * 0.3 = 3G + 1.5G = 4.5G
-Xmx5G