万字长文揭秘:阿里如何实现海量数据实时分析?

640?wx_fmt=jpeg

阿里妹导读:随着数据量的快速增加,愈来愈多的企业迎来业务数据化时代,数据成为了最重要的生产资料和业务升级依据。本文由阿里AnalyticDB团队出品,近万字长文,首次深度解读阿里在海量数据实时分析领域的多项核心技术。java


数字经济时代已经来临,但愿能和业界同行共同探索,加速行业数字化升级,服务更多中小企业和消费者。算法

欢迎转发、收藏此文


挑战


随着数据量的快速增加,愈来愈多的企业迎来业务数据化时代,数据成为了最重要的生产资料和业务升级依据。伴随着业务对海量数据实时分析的需求愈来愈多,数据分析技术这两年也迎来了一些新的挑战和变革:
数据库


  • 在线化和高可用,离线和在线的边界愈来愈模糊,一切数据皆服务化、一切分析皆在线化。数组

  • 高并发低延时,愈来愈多的数据系统直接服务终端客户,对系统的并发和处理延时提出了新的交互性挑战。缓存

  • 混合负载, 一套实时分析系统既要支持数据加工处理,又要支持高并发低延时的交互式查询。性能优化

  • 融合分析, 随着对数据新的使用方式探索,须要解决结构化与非结构化数据融合场景下的数据检索和分析问题。网络


阿里巴巴最初经过单节点Oracle进行准实时分析, 后来转到Oracle RAC,随着业务的飞速发展, 集中式的Shared Storage架构须要快速转向分布式,迁移到了Greenplum,但不到一年时间便遇到扩展性和并发的严重瓶颈。为了迎接更大数据集、更高并发、更高可用、更实时的数据应用发展趋势,从2011年开始,在线分析这个技术领域,阿里实时数仓坚决的走上了自研之路。多线程

 

640?wx_fmt=png


分析型数据库AnalyticDB


AnalyticDB是阿里巴巴自主研发、惟一通过超大规模以及核心业务验证的PB级实时数据仓库。自2012年第一次在集团发布上线以来,至今已累计迭代发布近百个版本,支撑起集团内的电商、广告、菜鸟、文娱、飞猪等众多在线分析业务。架构


AnalyticDB于2014年在阿里云开始正式对外输出,支撑行业既包括传统的大中型企业和政府机构,也包括众多的互联网公司,覆盖外部十几个行业。AnalyticDB承接着阿里巴巴广告营销、商家数据服务、菜鸟物流、盒马新零售等众多核心业务的高并发分析处理, 每一年双十一上述众多实时分析业务高峰驱动着AnalyticDB不断的架构演进和技术创新。并发


通过这2年的演进和创新,AnalyticDB已经成长为兼容MySQL 5.x系列、并在此基础上加强支持ANSI SQL:2003的OLAP标准(如window function)的通用实时数仓,跻身为实时数仓领域极具行业竞争力的产品。近期,AnalyticDB成功入选了全球权威IT咨询机构Forrester发布"The Forrester Wave™: CloudData Warehouse,Q4 2018"研究报告的Contenders象限,以及Gartner发布的分析型数据管理平台报告 (Magic Quadrant forData Management Solutions for Analytics),开始进入全球分析市场。AnalyticDB旨在帮客户将整个数据分析和价值化从传统的离线分析带到下一代的在线实时分析模式。


总体架构


通过过去2年的架构演进和功能迭代,AnalyticDB当前总体架构以下图。


AnalyticDB是一个支持多租户的Cloud Native Realtime Data Warehouse平台,每一个租户DB的资源隔离,每一个DB都有相应独立的模块(图中的Front Node, Compute Node, Buffer Node),在处理实时写入和查询时,这些模块都是资源(CPU, Memory)使用密集型的服务,须要进行DB间隔离保证服务质量。同时从功能完整性和成本优化层面考虑,又有一系列集群级别服务(图中绿色部分模块)。


640?wx_fmt=png


下面是对每一个模块的具体描述:


DB级别服务组件:


  • Front Node:负责JDBC, ODBC协议层接入,认证和鉴权,SQL解析、重写;分区地址路由和版本管理;同时优化器,执行计划和MPP计算的调度模块也在Front Node。

  • Compute Node: 包含MPP计算Worker模块,和存储模块(行列混存,元数据,索引)。

  • Buffer Node: 负责实时写入,并根据实时数据大小触发索引构建和合并。


集群级别服务组件:


  • Management Console: 管理控制台。

  • Admin Service:集群管控服务,负责计量计费,实例生命周期管理等商业化功能,同时提供OpenAPI和InnerAPI给Management Console和第三方调用。

  • Global Meta Service:全局元数据管理,提供每一个DB的元数据管理服务,同时提供分区分配,副本管理,版本管理,分布式DDL等能力。

  • Job Service:做业服务,提供异步做业调度能力。异步做业包括索引构建、扩容、无缝升级、删库删表的后台异步数据清理等。

  • Connector Service:数据源链接服务,负责外部各数据源(图中右侧部分)接入到AnalyticDB。目前该服务开发基本完成,即将上线提供云服务。

  • Monitoring & Alerting Service:监控告警诊断服务,既提供面向内部人员的运维监控告警诊断平台,又做为数据源经过Management Console面向用户侧提供数据库监控服务。

  • Resource Management Service:资源管理服务,负责集群级别和DB级别服务的建立、删除、DNS/SLB挂载/卸载、扩缩容、升降配,无缝升级、服务发现、服务健康检查与恢复。


数据模型


AnalyticDB中表组(Table Group)分为两类:事实表组和维度表组。


  • 事实表组(Fact Table Group),表组在AnalyticDB里是一个逻辑概念,用户能够将业务上关联性比较多的事实表放在同一个事实表组下,主要是为了方便客户作众多数据业务表的管理,同时还能够加速Co-location Join计算。

  • 维度表组(Dimension Table Group),用于存放维度表,目前有且仅有一个,在数据库创建时会自动建立,维度表特征上是一种数据量较小可是须要和事实表进行潜在关联的表。


AnalyticDB中表分为事实表(Fact Table)和维度表(Dimension Table)。


事实表建立时至少要指定Hash分区列和相关分区信息,而且指定存放在一个表组中,同时支持List二级分区。


  • Hash Partition将数据按照分区列进行hash分区,hash分区被分布到多个Compute Node中。

  • List Partition(若是指定List分区列的话)对一个hash分区进行再分区,通常按照时间(如天天一个list分区)。

  • 一个Hash Partition的全部List Partition默认存放于同一个Compute Node中。每一个Hash Partition配有多个副本(一般为双副本),分布在不一样的Compute      Node中,作到高可用和高并发。


维度表能够和任意表组的任意表进行关联,而且建立时不须要配置分区信息,可是对单表数据量大小有所限制,而且须要消耗更多的存储资源,会被存储在每一个属于该DB的Compute Node中。


下图描述了从Database到List分区到数据模型:


640?wx_fmt=png


对于Compute Node 来讲,事实表的每一个List分区是一个物理存储单元(若是没有指定List分区列,可认为该Hash分区只有一个List分区)。一个分区物理存储单元采用行列混存模式,配合元数据和索引,提供高效查询。


海量数据


基于上述数据模型,AnalyticDB提供了单库PB级数据实时分析能力。如下是生产环境的真实数据:


  • 阿里巴巴集团某营销应用单DB表数超过20000张

  • 云上某企业客户单DB数据量近3PB,单日分析查询次数超过1亿

  • 阿里巴巴集团内某单个AnalyticDB集群超过2000台节点规模

  • 云上某业务实时写入压力高达1000w TPS

  • 菜鸟网络某数据业务极度复杂分析场景,查询QPS 100+


导入导出


灵活的数据导入导出能力对一个实时数仓来讲相当重要,AnalyticDB当前既支持经过阿里云数据传输服务DTS、DataWorks数据集成从各类外部数据源导入入库,同时也在不断完善自身的数据导入能力。总体导入导出能力以下图(其中导入部分数据源当前已支持,部分在开发中,即将发布)。

 

640?wx_fmt=png


★ 数据导入


首先,因为AnalyticDB兼容MySQL5.x系列,支持经过MySQL JDBC方式把数据insert入库。为了得到最佳写入性能,AnalyticDB提供了Client SDK,实现分区聚合写的优化,相比经过JDBC单条insert,写入性能有10倍以上提高。对于应用端业务逻辑须要直接写入AnalyticDB的场景,推荐使用AnalyticDB Client SDK。


同时,对于快速上传本地结构化的文本文件,可使用基于AnalyticDB Client SDK开发的Uploader工具。对于特别大的文件,能够拆分后使用uploader工具进行并行导入。


另外,对于OSS,MaxCompute这样的外部数据源,AnalyticDB经过分布式的Connector Service数据导入服务并发读取并写入到相应DB中。Connector Service还将支持订阅模式,从Kafka,MQ,RDS等动态数据源把数据导入到相应DB中。AnalyticDB对大数据生态的Logstash,Fluentd,Flume等日志收集端、ETL工具等经过相应插件支持,可以快速把数据写入相应DB。


今天在阿里巴巴集团内,天天有数万张表从MaxCompute导入到AnalyticDB中进行在线分析,其中大量导入任务单表数据大小在TB级、数据量近千亿。


★ 数据导出


AnalyticDB目前支持数据导出到OSS和MaxCompute,业务场景主要是把相应查询结果在外部存储进行保存归档,实现原理相似insert from select操做。insert from select是把查询结果写入到内部表,而导出操做则是写入外部存储, 经过改进实现机制,能够方便地支持更多的导出数据源。


核心技术


高性能SQL Parser


AnalyticDB通过数年的发展,语法解析器也经历了屡次更新迭代。曾经使用过业界主流的 Antlr(http://www.antlr.org),JavaCC(https://javacc.org)等Parser生成器做为SQL 语法解析器,可是二者在长期、大规模、复杂查询场景下,Parser的性能、语法兼容、API设计等方面不知足要求,因而咱们引入了自研的SQL Parser组件FastSQL。


★ 领先业界的Parser性能


AnalyticDB主打的场景是高并发、低延时的在线化分析,对SQL Parser性能要求很高,批量实时写入等场景要求更加苛刻。FastSQL经过多种技术优化提高Parser性能,例如:


  • 快速对比:使用64位hash算法加速关键字匹配,使用fnv_1a_64 hash算法,在读取identifier的同时计算好hash值,并利用hash64低碰撞几率的特色,使用64位hash code直接比较,比常规Lexer先读取identifier,在查找SymbolTable速度更快。

  • 高性能的数值Parser:Java自带的Integer.parseInt()/Float.parseFloat()须要构造字符串再作parse,FastSQL改进后能够直接在原文本上边读取边计算数值。

  • 分支预测:在insert values中,出现常量字面值的几率比出现其余的token要高得多,经过分支预测能够减小判断提高性能。


以TPC-DS99个Query对比来看,FastSQL比Antlr Parser(使用Antlr生成)平均快20倍,比JSQLParser(使用JavaCC生成)平均快30倍,在批量Insert场景、多列查询场景下,使用FastSQL后速度提高30~50倍。


640?wx_fmt=png


★ 无缝结合优化器


在结合AnalyticDB的优化器的SQL优化实践中,FastSQL不断将SQL Rewrite的优化能力前置化到SQL Parser中实现,经过与优化器的SQL优化能力协商,将尽量多的表达式级别优化前置化到SQL Parser中,使得优化器能更加专一于基于代价和成本的优化(CBO,Cost-Based Optimization)上,让优化器能更多的集中在理解计算执行计划优化上。FastSQL在AST Tree上实现了许多SQL Rewrite的能力,例如:


  • 常量折叠


 
 
SELECT * FROM t1 t
WHERE comm_week 
BETWEEN((('day',-('20180605'),   CASTdate_formatdate_addday_of_week
date('20180605')),'%Y%m%d') AS bigint)                             
AND((('day',-('20180605')         CASTdate_formatdate_addday_of_week
,date('20180605')),'%Y%m%d') AS bigint)                            
------>
SELECT * FROM t1 t
WHEREBETWEEN20180602AND20180602 comm_week 


  • 函数变换:


 
 
SELECT * FROM
t1 t
WHERE(."pay_time",'%Y%m%d')>='20180529' DATE_FORMATt
AND(."pay_time",'%Y%m%d')<='20180529'     DATE_FORMATt
------>
SELECT * FROM t1 t
WHERE."pay_time">= TIMESTAMP'2018-05-29 00:00:00' t
AND."pay_time"< TIMESTAMP'2018-05-30 00:00:00' t


  • 表达式转换:


 
 
SELECT,FROM a b  t1
WHERE+1=10; b 
------>
SELECT,FROM a b  t1
WHERE=9; b 


  • 函数类型推断:


 
 
-- f3类型是TIMESTAMP类型
SELECT(,1) concatf3
FROM; nation
------>
SELECT((AS CHAR),'1') concatCASTf3 
FROM; nation


  • 常量推断:

 
 

SELECT
* FROM t
WHERE<AND=AND=5 a  b  b  c  a 
------>
SELECT * FROM t
WHERE>5AND=5AND= b  a  b  c


  • 语义去重:


 
 
SELECT * FROM
t1
WHERE>'2017-05-01' max_adate 
AND!='2017-04-01'     max_adate 
------>
SELECT * FROM t1
WHERE> DATE '2017-05-01' max_adate 


玄武存储引擎


为保证大吞吐写入,以及高并发低时延响应,AnalyticDB自研存储引擎玄武,采用多项创新的技术架构。玄武存储引擎采用读/写实例分离架构,读节点和写节点可分别独立扩展,提供写入吞吐或者查询计算能力。在此架构下大吞吐数据写入不影响查询分析性能。同时玄武存储引擎构筑了智能全索引体系,保证绝大部分计算基于索引完成,保证任意组合条件查询的毫秒级响应。


 读写分离架构支持大吞吐写入


传统数据仓库并无将读和写分开处理,即这些数据库进程/线程处理请求的时候,无论读写都会在同一个实例的处理链路上进行。所以全部的请求都共享同一份资源(内存资源、锁资源、IO资源),并相互影响。在查询请求和写入吞吐都很高的时候,会存在严重的资源竞争,致使查询性能和写入吞吐都降低。


为了解决这个问题,玄武存储引擎设计了读写分离的架构。以下图所示,玄武存储引擎有两类关键的节点:Buffer Node和Compute Node。Buffer Node专门负责处理写请求,Compute Node专门负责查询请求,Buffer Node和Compute Node彻底独立并互相不影响,所以,读写请求会在两个彻底不相同的链路中处理。上层的Front Node会把读写请求分别路由给Buffer Node和Compute Node。


640?wx_fmt=png


实时写入链路:


  • 业务实时数据经过JDBC/ODBC协议写入到Front Node。

  • Front Node根据实时数据的hash分区列值,路由到相应Buffer Node。

  • Buffer Node将该实时数据的内容(相似于WAL)提交到盘古分布式文件系统,同时更新实时数据版本,并返回Front  Node,Front Node返回写入成功响应到客户端。

  • Buffer Node同时会异步地把实时数据内容推送到Compute Node,Compute Node消费该实时数据并构建实时数据轻量级索引。

  • 当实时数据积攒到必定量时,Buffer Node触发后台Merge Baseline做业,对实时数据构建彻底索引并与基线数据合并。


实时查询链路:


  • 业务实时查询请求经过JDBC/ODBC协议发送到Front Node。

  • Front Node首先从Buffer Node拿到当前最新的实时数据版本,并把该版本随执行计划一块儿下发到Compute Node。

  • Compute Node检查本地实时数据版本是否知足实时查询要求,若知足,则直接执行并返回数据。若不知足,需先到Buffer Node把指定版本的实时数据拖到本地,再执行查询,以保证查询的实时性(强一致)。


AnalyticDB提供强实时和弱实时两种模式,强实时模式执行逻辑描述如上。弱实时模式下,Front Node查询请求则不带版本下发,返回结果的实时取决于Compute Node对实时数据的处理速度,通常有秒极延迟。因此强实时在保证数据一致性的前提下,当实时数据写入量比较大时对查询性能会有必定的影响。


高可靠性


玄武存储引擎为Buffer Node和Compute Node提供了高可靠机制。用户能够定义Buffer Node和Compute Node的副本数目(默认为2),玄武保证同一个数据分区的不一样副本必定是存放在不一样的物理机器上。Compute Node的组成采用了对等的热副本服务机制,全部Compute Node节点均可以参与计算。另外,Computed Node的正常运行并不会受到Buffer Node节点异常的影响。若是Buffer Node节点异常致使Compute Node没法正常拉取最新版本的数据,Compute Node会直接从盘古上获取数据(即使这样须要忍受更高的延迟)来保证查询的正常执行。数据在Compute Node上也是备份存储。以下图所示,数据是经过分区存放在不一样的ComputeNode上,具备相同hash值的分区会存储在同一个Compute Node上。数据分区的副本会存储在其余不一样的Compute Node上,以提供高可靠性。


640?wx_fmt=png

 

高扩展性


玄武的两个重要特性设计保证了其高可扩展性:1)Compute Node和Buffer Node都是无状态的,他们能够根据业务负载需求进行任意的增减;2)玄武并不实际存储数据,而是将数据存到底层的盘古系统中,这样,当Compute Node和Buffer Node的数量进行改变时,并不须要进行实际的数据迁移工做。


 为计算而生的存储


数据存储格式


传统关系型数据库通常采用行存储(Row-oriented Storage)加B-tree索引,优点在于其读取多列或全部列(SELECT *)场景下的性能,典型的例子如MySQL的InnoDB引擎。可是在读取单列、少数列而且行数不少的场景下,行存储会存在严重的读放大问题。

数据仓库系统通常采用列存储(Column-oriented Storage),优点在于其单列或少数列查询场景下的性能、更高的压缩率(不少时候一个列的数据具备类似性,而且根据不一样列的值类型能够采用不一样的压缩算法)、列聚合计算(SUM, AVG, MAX, etc.)场景下的性能。可是若是用户想要读取整行的数据,列存储会带来大量的随机IO,影响系统性能。

为了发挥行存储和列存储各自的优点,同时避免二者的缺点,AnalyticDB设计并实现了全新的行列混存模式。以下图所示:


640?wx_fmt=png


  • 对于一张表,每k行数据组成一个Row Group。在每一个Row Group中,每列数据连续的存放在单独的block中,每Row Group在磁盘上连续存放。

  • Row Group内列block的数据可按指定列(汇集列)排序存放,好处是在按该列查询时显著减小磁盘随机IO次数。

  • 每一个列block可开启压缩。


行列混存存储相应的元数据包括:分区元数据,列元数据,列block元数据。其中分区元数据包含该分区总行数,单个block中的列行数等信息;列元数据包括该列值类型、整列的MAX/MIN值、NULL值数目、直方图信息等,用于加速查询;列block元数据包含该列在单个Row Group中对应的MAX/MIN/SUM、总条目数(COUNT)等信息,一样用于加速查询。


全索引计算


用户的复杂查询可能会涉及到各类不一样的列,为了保证用户的复杂查询可以获得秒级响应,玄武存储引擎在行列混合存储的基础上,为基线数据(即历史数据)全部列都构建了索引。玄武会根据列的数据特征和空间消耗状况自动选择构建倒排索引、位图索引或区间树索引等,而用的最多的是倒排索引。


640?wx_fmt=png


如上图所示,在倒排索引中,每列的数值对应索引的key,该数值对应的行号对应索引的value,同时全部索引的key都会进行排序。依靠全列索引,交集、并集、差集等数据库基础操做能够高性能地完成。以下图所示,用户的一个复杂查询包含着对任意列的条件筛选。玄武会根据每一个列的条件,去索引中筛选知足条件的行号,而后再将每列筛选出的行号,进行交、并、差操做,筛选出最终知足全部条件的行号。玄武会依据这些行号去访问实际的数据,并返回给用户。一般通过筛选后,知足条件的行数可能只占总行数的万分之一到十万分之一。所以,全列索引帮助玄武在执行查询请求的时候,大大减少须要实际遍历的行数,进而大幅提高查询性能,知足任意复杂查询秒级响应的需求。


640?wx_fmt=png


使用全列索引给设计带来了一个很大挑战:须要对大量数据构建索引,这会是一个很是耗时的过程。若是像传统数据库那样在数据写入的路径上进行索引构建,那么这会严重影响写入的吞吐,并且会严重拖慢查询的性能,影响用户体验。为了解决这个挑战,玄武采用了异步构建索引的方式。当写入请求到达后,玄武把写SQL持久化到盘古,而后直接返回,并不进行索引的构建。


当这些未构建索引的数据(称为实时数据)积累到必定数量时,玄武会开启多个MapReduce任务,来对这些实时数据进行索引的构建,并将实时数据及其索引,同当前版本的基线数据(历史数据)及其索引进行多版本归并,造成新版本的基线数据和索引。这些MapReduce任务经过伏羲进行分布式调度和执行,异步地完成索引的构建。这种异步构建索引的方式,既不影响AnalyticDB的高吞吐写入,也不影响AnalyticDB的高性能查询。


异步构建索引的机制还会引入一个新问题:在进行MapReduce构建索引的任务以前,新写入的实时数据是没有索引的,若是用户的查询会涉及到实时数据,查询性能有可能会受到影响。玄武采用为实时数据构建排序索引(Sorted Index)的机制来解决这个问题。


以下图所示,玄武在将实时数据以block形式刷到磁盘以前,会根据每一列的实时数据生成对应的排序索引。排序索引实际是一个行号数组,对于升序排序索引来讲,行号数组的第一个数值是实时数据最小值对应的行号,第二个数值是实时数据第二小值对应的行号,以此类推。这种状况下,对实时数据的搜索复杂度会从O(N)下降为O(lgN)。排序索引大小一般很小(60KB左右),所以,排序索引能够缓存在内存中,以加速查询。


640?wx_fmt=png

羲和计算引擎


针对低延迟高并发的在线分析场景需求,AnalyticDB自研了羲和大规模分析引擎,其中包括了基于流水线模型的分布式并行计算引擎,以及基于规则 (Rule-Based Optimizer,RBO) 和代价(Cost-Based Optimizer,CBO)的智能查询优化器。

 

★   优化器


优化规则的丰富程度是可否产生最优计划的一个重要指标。由于只有可选方案足够多时,才有可能选到最优的执行计划。AnalyticDB提供了丰富的关系代数转换规则,用来确保不会遗漏最优计划。


基础优化规则:


  • 裁剪规则:列裁剪、分区裁剪、子查询裁剪

  • 下推/合并规则:谓词下推、函数下推、聚合下推、Limit下推

  • 去重规则:Project去重、Exchange去重、Sort去重

  • 常量折叠/谓词推导


探测优化规则:


  • Joins:BroadcastHashJoin、RedistributedHashJoin、NestLoopIndexJoin

  • Aggregate:HashAggregate、SingleAggregate

  • JoinReordering

  • GroupBy下推、Exchange下推、Sort下推


高级优化规则:CTE

 

例以下图中,CTE的优化规则的实现将两部分相同的执行逻辑合为一个。经过相似于最长公共子序列的算法,对整个执行计划进行遍历,并对一些能够忽略的算子进行特殊处理,如Projection,最终达到减小计算的目的。


640?wx_fmt=png


单纯基于规则的优化器每每过于依赖规则的顺序,一样的规则不一样的顺序会致使生成的计划彻底不一样,结合基于代价的优化器则能够经过尝试各类可能的执行计划,达到全局最优。

 

AnalyticDB的代价优化器基于Cascade模型,执行计划通过Transform模块进行了等价关系代数变换,对可能的等价执行计划,估算出按Cost Model量化的计划代价,并从中最终选择出代价最小的执行计划经过Plan Generation模块输出,存入Plan Cache(计划缓存),以下降下一次相同查询的优化时间。

 

640?wx_fmt=png

 

在线分析的场景对优化器有很高的要求,AnalyticDB为此开发了三个关键特性:存储感知优化、动态统计信息收集和计划缓存。


存储层感知优化


生成分布式执行计划时,AnalyticDB优化器能够充分利用底层存储的特性,特别是在Join策略选择,Join Reorder和谓词下推方面。


  • 底层数据的哈希分布策略将会影响Join策略的选择。基于规则的优化器,在生成Join的执行计划时,若是对数据物理分布特性的不感知,会强制增长一个数据重分布的算子来保证其执行语义的正确。 数据重分布带来的物理开销很是大,涉及到数据的序列化、反序列化、网络开销等等,所以避免屡次数据重分布对于分布式计算是很是重要的。除此以外,优化器也会考虑对数据库索引的使用,进一步减小Join过程当中构建哈希的开销。

  • 调整Join顺序时,若是大多数Join是在分区列,优化器将避免生成Bushy Tree,而更偏向使用Left Deep Tree,并尽可能使用现有索引进行查找。

640?wx_fmt=png

  • 优化器更近一步下推了谓词和聚合。聚合函数,好比count(),和查询过滤能够直接基于索引计算。

 

全部这些组合下降了查询延迟,同时提升集群利用率,从而使得AnalyticDB能轻松支持高并发。


动态统计信息收集


统计信息是优化器在作基于代价查询优化所需的基本信息,一般包括有关表、列和索引等的统计信息。传统数据仓库仅收集有限的统计信息,例如列上典型的最常值(MFV)。商业数据库为用户提供了收集统计信息的工具,但这一般取决于DBA的经验,依赖DBA来决定收集哪些统计数据,并依赖于服务或工具供应商。


上述方法收集的统计数据一般都是静态的,它可能须要在一段时间后,或者当数据更改达到必定程度,来从新收集。可是,随着业务应用程序变得愈来愈复杂和动态,预约义的统计信息收集可能没法以更有针对性的方式帮助查询。例如,用户能够选择不一样的聚合列和列数,其组合可能会有很大差别。可是,在查询生成以前很难预测这样的组合。所以,很难在统计收集时决定正确统计方案。可是,此类统计信息可帮助优化器作出正确决定。


咱们设计了一个查询驱动的动态统计信息收集机制来解决此问题。守护程序动态监视传入的查询工做负载和特色以提取其查询模式,并基于查询模式,分析缺失和有益的统计数据。在此分析和预测之上,异步统计信息收集任务在后台执行。这项工做旨在减小收集没必要要的统计数据,同时使大多数即将到来的查询受益。对于前面提到的聚合示例,收集多列统计信息一般很昂贵,尤为是当用户表有大量列的时候。根据咱们的动态工做负载分析和预测,能够作到仅收集必要的多列统计信息,同时,优化器可以利用这些统计数据来估计聚合中不一样选项的成本并作出正确的决策。


计划缓存


从在线应用案件看,大多数客户都有一个共同的特色,他们常常反复提交相似的查询。在这种状况下,计划缓存变得相当重要。为了提升缓存命中率,AnalyticDB不使用原始SQL文本做为搜索键来缓存。相反,SQL语句首先经过重写并参数化来提取模式。例如,查询 “SELECT * FROM t1 WHERE a = 5 + 5”将转化为“SELECT * FROM t1 WHERE a =?”。参数化的SQL模版将被做为计划缓存的关键字,若是缓存命中,AnalyticDB将根据新查询进行参数绑定。因为这个改动,即便使用有限的缓存大小,优化器在生产环境也能够保持高达90%以上的命中率,而以前只能达到40%的命中率。


这种方法仍然有一个问题。假设咱们在列a上有索引,“SELECT * FROM t1 WHERE a = 5”的优化计划能够将索引扫描做为其最佳访问路径。可是,若是新查询是“SELECT * FROM t1 WHERE a = 0”而且直方图告诉咱们数值0在表t1占大多数,那么索引扫描可能不如全表扫描有效。在这种状况下,使用缓存中的计划并非一个好的决定。为了不这类问题,AnalyticDB提供了一个功能Literal Classification,使用列的直方图对该列的值进行分类,仅当与模式相关联的常量“5”的数据分布与新查询中常量“0”的数据分布相似时,才实际使用高速缓存的计划。不然,仍会对新查询执行常规优化。

 

★ 执行引擎


在优化器之下,AnalyticDB在MPP架构基础上,采用流水线执行的DAG架构,构建了一个适用于低延迟和高吞吐量工做负载的执行器。以下图所示,当涉及到多个表之间非分区列JOIN时,CN(MPP Worker)会先进行data exchange (shuffling)而后再本地JOIN (SourceTask),aggregate后发送到上一个stage(MiddleTask),最后汇总到Output Task。因为绝大多状况都是in-memory计算(除复杂ETL类查询,尽可能无中间Stage 落盘)且各个stage之间都是pipeline方式协做,性能上要比MapReduce方式快一个数量级。

640?wx_fmt=png


在接下来的几节中,将介绍其中三种特性,包括混合工做负载管理,CodeGen和矢量化执行。


混合工做负载管理


做为一套完备的实时数仓解决方案,AnalyticDB中既有须要较低响应时间的高并发查询,也有相似ETL的批处理,二者争用相同资源。传统数仓体系每每在这两个方面的兼顾性上作的不够好。

 

AnalyticDB worker接收coordinator下发的任务, 负责该任务的物理执行计划的实际执行。这项任务能够来自不一样的查询, worker会将任务中的物理执行计划按照既定的转换规则转换成对应的operator,物理执行计划中的每个Stage会被转换成一个或多个operator。

640?wx_fmt=png


执行引擎已经能够作到stage/operator级别中断和Page级别换入换出,同时线程池在全部同时运行的查询间共享。可是,这之上仍然须要确保高优先级查询能够得到更多计算资源。


640?wx_fmt=png


根据经验,客户老是指望他们的短查询即便当系统负载很重的时候也能快速完成。为了知足这些要求,基于以上场景,经过时间片的分配比例来体现不一样查询的优先级,AnalyticDB实现了一个简单版本的类Linux kernel 的调度算法。系统记录了每个查询的总执行耗时,查询总耗时又是经过每个Task耗时来进行加权统计的,最终在查询层面造成了一颗红黑树,每次老是挑选最左侧节点进行调度,每次取出或者加入(被唤醒以及从新入队)都会从新更新这棵树,一样的,在Task被唤醒加入这颗树的时候,执行引擎考虑了补偿机制,即时间片耗时若是远远低于其余Task的耗时,确保其在整个树里面的位置,同时也避免了由于长时间的阻塞形成的饥饿,相似于CFS 调度算法中的vruntime补偿机制。

640?wx_fmt=png


这个设计虽然有效解决了慢查询占满资源,致使其余查询得不到执行的问题,却没法保障快查询的请求延迟。这是因为软件层面的多线程执行机制,线程个数大于了实际的CPU个数。在实际的应用中,计算线程的个数每每是可用Core的2倍。这也就是说,即便快查询的算子获得了计算线程资源进行计算,也会在CPU层面与慢查询的算子造成竞争。所下图所示,快查询的算子计算线程被调度到VCore1上,该算子在VCore1上会与慢查询的计算线程造成竞争。另外在物理Core0上,也会与VCore0上的慢查询的计算线程造成竞争。


640?wx_fmt=png


在Kernel sched模块中,对于不一样优先级的线程之间的抢占机制,已经比较完善,且时效性比较高。于是,经过引入kernel层面的控制能够有效解决快查询低延迟的问题,且无需对算子的实现进行任何的改造。执行引擎让高优先级的线程来执行快查询的算子,低优先级的线程来执行慢查询的算子。因为高优先级线程抢占低优先级线程的机制,快查询算子天然会抢占慢查询的算子。此外,因为高优先级线程在Kernel sched模块调度中,具备较高的优先级,也避免了快慢查询算子在vcore层面的CPU竞争。

 

640?wx_fmt=png


一样的在实际应用中是很难要求用户来辨别快慢查询,由于用户的业务自己可能就没有快慢业务之分。另外对于在线查询,查询的计算量也是不可预知的。为此,计算引擎在Runtime层面引入了快慢查询的识别机制,参考Linux kernel中vruntime的方式,对算子的执行时间、调度次数等信息进行统计,当算子的计算量达到给定的慢查询的阈值后,会把算子从高优先级的线程转移到低优先级的线程中。这有效提升了在压力测试下快查询的响应时间。

 

代码生成器


Dynamic code generation(CodeGen)广泛出如今业界的各大计算引擎设计实现中。它不只可以提供灵活的实现,减小代码开发量,一样在性能优化方面也有着较多的应用。可是同时基于ANTLR ASM的AnalyticDB代码生成器也引入了数十毫秒编译等待时间,这在实时分析场景中是不可接受的。为了进一步减小这种延迟,分析引擎使用了缓存来重用生成的Java字节码。可是,它并不是能对全部状况都起很好做用。


随着业务的普遍使用以及对性能的进一步追求,系统针对具体的状况对CodeGen作了进一步的优化。使用了Loading Cache对已经生成的动态代码进行缓存,可是SQL表达式中每每会出现常量(例如,substr(col1,1, 3),col1 like‘demo%’等),在原始的生成逻辑中会直接生成常量使用。这致使不少相同的方法在遇到不一样的常量值时须要生成一整套新的逻辑。这样在高并发场景下,cache命中率很低,而且致使JDK的meta区增加速度较快,更频繁地触发GC,从而致使查询延迟抖动。


substr(col1,  1, 3)

=>  cacheKey<CallExpression(substr), inputReferenceExpression(col1),  constantExpression(1), constantExpression(3)>cacheValue bytecode;

 

经过对表达式的常量在生成bytecode阶段进行rewrite,对出现的每一个常量在Class级别生成对应的成员变量来存储,去掉了Cachekey中的常量影响因素,使得能够在不一样常量下使用相同的生成代码。命中的CodeGen将在plan阶段instance级别的进行常量赋值。


substr(col1,  1, 3)

=>  cacheKey<CallExpression(substr),  inputReferenceExpression(col1)>cacheValue bytecode;

 

在测试与线上场景中,通过优化不少高并发的场景再也不出现meta区的GC,这显著增长了缓存命中率,总体运行稳定性以及平均延迟均有必定的提高。

 

AnalyticDB CodeGen不只实现了谓词评估,还支持了算子级别运算。例如,在复杂SQL且数据量较大的场景下,数据会屡次shuffle拷贝,在partitioned shuffle进行数据拷贝的时候很容易出现CPU瓶颈。用于链接和聚合操做的数据Shuffle一般会复制从源数据块到目标数据块的行,伪代码以下所示:


foreach row

   foreach column

      type.append(blockSrc, position, blockDest);


从生产环境,大部分SQL每次shuffle的数据量较大,可是列不多。那么首先想到的就是forloop的展开。那么上面的伪代码就能够转换成


foreach  row

   type(1).append(blockSrc(1), position,  blockDest(1));

   type(2).append(blockSrc(2), position,  blockDest(2));

   type(3).append(blockSrc(3), position,  blockDest(3));

 

上面的优化经过直接编码是没法完成的,须要根据SQL具体的column状况动态的生成对应的代码实现。在测试中1000w的数据量级拷贝延时能够提高24%。

 

矢量化引擎和二进制数据处理


相对于行式计算,AnalyticDB的矢量化计算因为对缓存更加友好,并避免了没必要要的数据加载,从而拥有了更高的效率。在这之上,AnalyticDB CodeGen也将运行态因素考虑在内,可以轻松利用异构硬件的强大功能。例如,在CPU支持AVX-512指令集的集群,AnalyticDB能够生成使用SIMD的字节码。同时AnalyticDB内部全部计算都是基于二进制数据,而不是Java Object,有效避免了序列化和反序列化开销。


极致弹性


在多租户基础上,AnalyticDB对每一个租户的DB支持在线升降配,扩缩容,操做过程当中无需停服,对业务几乎透明。如下图为例:


640?wx_fmt=png


  • 用户开始能够在云上开通包含两个C4资源的DB进行业务试用和上线(图中的P1, P2...表明表的数据分区)

  • 随着业务的增加,当两个C4的存储或计算资源没法知足时,用户可自主对该DB发起升配或扩容操做,升配+扩容可同时进行。该过程会按副本交替进行,保证整个过程当中始终有一个副本提供服务。另外,扩容增长节点后,数据会自动在新老节点间进行重分布。

  • 对于临时性的业务增加(如电商大促),升配扩容操做都可逆,在大促事后,可自主进行降配缩容操做,作到灵活地成本控制。


在线升降配,平滑扩缩容能力,对今年双十一阿里巴巴集团内和公共云上和电商物流相关的业务库起到了相当重要的保障做用。


GPU加速


★ 客户业务痛点


某客户数据业务的数据量在半年时间内由不到200TB增长到1PB,而且还在快速翻番,截止到发稿时为止已经超过1PB。该业务计算复杂,查询时间跨度周期长,需按照任意选择属性过滤,单个查询计算涉及到的算子包括20个以上同时交并差、多表join、多值列(相似array)group by等以及上述算子的各类复杂组合。传统的MapReduce离线分析方案时效性差,极大限制了用户快速分析、快速锁定人群并即时投放广告的诉求,业务发展面临新的瓶颈。


★ AnalyticDB加速方案


GPU加速AnalyticDB的作法是在Compute Node中新增GPU Engine对查询进行加速。GPU Engine主要包括: Plan Rewriter、Task Manager、Code Generator、CUDA Manager、Data Manager和VRAM Manager。


640?wx_fmt=png


SQL查询从Front Node发送到Compute Node,通过解析和逻辑计划生成之后,Task Manager先根据计算的数据量以及查询特征选择由CPU Engine仍是GPU Engine来处理,而后根据逻辑计划生成适合GPU执行的物理计划。


GPU Engine收到物理计划后先对执行计划进行重写。若是计划符合融合特征,其中多个算子会被融合成单个复合算子,从而大量减小算子间临时数据的Buffer传输。


Rewriting以后物理计划进入Code Generator,该模块主功能是将物理计划编译成PTX代码。Code Generator第一步借助LLVM JIT先将物理计划编译成LLVM IR,IR通过优化之后经过LLVMNVPTX Target转换成PTX代码。CUDA运行时库会根据指定的GPU架构型号将PTX转换成本地可执行代码,并启动其中的GPU kernel。Code Generator能够支持不一样的Nvidia GPU。


CUDA Manager经过jCUDA调用CUDA API,用于管理和配置GPU设备、GPU kernel的启动接口封装。该模块做为Java和GPU之间的桥梁,使得JVM能够很方便地调用GPU资源。


Data Manager主要负责数据加载,将数据从磁盘或文件系统缓存加载到指定堆外内存,从堆外内存加载到显存。CPU Engine的执行模型是数据库经典的火山模型,即表数据需逐行被拉取再计算。这种模型明显会极大闲置GPU上万行的高吞吐能力。目前Data Manager可以批量加载列式数据块,每次加载的数据块大小为256M,而后经过PCIe总线传至显存。


VRAM Manager用于管理各GPU的显存。显存是GPU中最稀缺的资源,须要合理管理和高效复用,有别于如今市面上其余GPU数据库系统使用GPU的方式,即每一个SQL任务独占全部的GPU及其计算和显存资源。为了提高显存的利用率、提高并发能力,结合AnalyticDB多分区、多线程的特色,咱们设计基于Slab的VRAM Manager统一管理全部显存申请:Compute Node启动时,VRAM Manager先申请所需空间并切分红固定大小的Slab,这样能够避免运行时申请带来的时间开销,也下降经过显卡驱动频繁分配显存的DoS风险。


在须要显存时,VRAM Manager会从空闲的Slab中查找空闲区域划分显存,用完后返还Slab并作Buddy合并以减小显存空洞。性能测试显示分配时间平均为1ms,对于总体运行时间而言可忽略不计,明显快于DDR内存分配的700ms耗时,也利于提升系统总体并发度。在GPU和CPU数据交互时,自维护的JVM堆外内存会做为JVM内部数据对象(如ByteBuffer)和显存数据的同步缓冲区,也必定程度减小了Full GC的工做量。


GPU Engine采用即时代码生成技术主要有以下优势:


  • 相对传统火山模型,减小计划执行中的函数调用等,尤为是分支判断,GPU中分支跳转会下降执行性能

  • 灵活支持各类复杂表达式,例如projection和having中的复杂表达式。例如HAVING     SUM(double_field_foo) > 1这种表达式的GPU代码是即时生成的

  • 灵活支持各类数据类型和UDF查询时追加

  • 利于算子融合,如group-by聚合、join再加聚合的融合,便可减小中间结果(特别是Join的链接结果)的拷贝和显存的占用 


根据逻辑执行计划动态生成GPU执行码的整个过程以下所示:


640?wx_fmt=png


★ GPU 加速实际效果


该客户数据业务使用了GPU实时加速后,将计算复杂、响应时间要求高、并发需求高的查询从离线分析系统切换至AnalyticDB进行在线分析运行稳定,MapReduce离线分析的平均响应时间为5到10分钟,高峰时可能须要30分钟以上。无缝升级到GPU加速版AnalyticDB以后,全部查询彻底实时处理并保证秒级返回,其中80%的查询的响应时间在2秒之内(以下图),而节点规模降至原CPU集群的三分之一左右。 业务目前能够随时尝试各类圈人标签组合快速对人群画像,即时锁定广告投放目标。据客户方反馈,此加速技术已经帮助其在竞争中构建起高壁垒,使该业务成为同类业务的核心能力,预计明年用户量有望翻番近一个数量级。


640?wx_fmt=png


总结


简单对本文作个总结,AnalyticDB作到让数据价值在线化的核心技术可概括为:


  • 高性能SQL Parser:自研Parser组件FastSQL,极致的解析性能,无缝集合优化器

  • 玄武存储引擎:数据更新实时可见,行列混存,粗糙集过滤,聚簇列,索引优化

  • 羲和计算引擎:MPP+DAG融合计算,CBO优化,向量化执行,GPU加速

  • 极致弹性:业务透明的在线升降配,扩缩容,灵活控制成本。

  • GPU加速:利用GPU硬件加速OLAP分析,大幅度下降查询延时。


分析型数据AnalyticDB, 做为阿里巴巴自研的下一代PB级实时数据仓库, 承载着整个集团内和云上客户的数据价值实时化分析的使命。 AnalyticDB为数据价值在线化而生,做为实时云数据仓库平台,接下来会在体验和周边生态建设上继续加快建设,但愿能将最领先的下一代实时分析技术能力普惠给全部企业,帮助企业转型加速数据价值探索和在线化。



640?wx_fmt=gif

你可能还喜欢

点击下方图片便可阅读


640?wx_fmt=jpeg

牛!阿里数据库掌门人李飞飞获选ACM杰出科学家


640?wx_fmt=jpeg

聊一聊 | IPv6 来了,哪些行业会发生美好的改变?


640?wx_fmt=jpeg

阿里玄难:面向不肯定性的软件设计几点思考


640?wx_fmt=jpeg

关注「阿里技术」

把握前沿技术脉搏