暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

分布式数据库产品总结

分享录 2021-06-09
870

这是来自我微x公众号(ID:fenxianglu,每天分享各种有用信息。浏览器输入“xubingtao.cn”访问我个人网站、苹-果App Store搜索“分享录”、微x小程序搜索“分享录”、头-条/抖-音小程序搜索“分享录”有更多原创内容)的一篇文章,以下文章来源于CSDN,作者是杰特JET,个人觉得写得不错分享给大家,文章底部有原文链接,如有侵权请联系删除。

Pivotal

Greenplum Database(GPDB)

**Greenplum Database(GPDB)**是一款基于开源 PostgreSQL 扩展的 MPP
(massively parallel processing),可支持大规模水平扩展的分布式数据库。GPDB 采用的是 master-worker
 模式,每个 worker process 运行在不同的机器上,拥有各自的存储和运算资源。**客户端通过 master 把查询语句分发到各个机器上,以达到并行计算来处理海量数据。**集群节点(无论是master还是segemnt)上的每个实例都是一个物理上独立的PostgrepSQL数据库。

Greenplum数据库是一种shared nothing的分析型MPP数据库。这种模型与高度规范化的/事务型的SMP数据库有显著区别。Greenplum数据库使用非规范化的模式设计会工作得最好,非规范化的模式适合于MPP分析型处理,例如带有大型事实表和较小维度表的星形模式或者雪花模式。

Greenplum 在 PostgreSQL 之上还添加了大量其他功能,例如 Append-Optimized 表、列存表、外部表、多级分区表、细粒度资源管理器、ORCA 查询优化器、备份恢复、高可用、故障检测和故障恢复、集群数据迁移、扩容、MADlib 机器学习算法库、容器化执行 UDF、PostGIS 扩展、GPText 套件、监控管理、集成 Kubernetes 等。

架构

  • master:保存元数据而不保存用户数据,有用户表信息,优化器使用这些信息进行查询优化和计划生成

  • segment:每个segment保存用户数据表的一部分。在 Greenplum 中,用户数据按照某种策略分散到不同节点的不同 segment 实例中。

使用标准的 INSERT
 SQL 语句可以将数据自动按照用户定义的策略分布到合适的节点,然而 INSERT 性能较低,仅适合插入少量数据。Greenplum 提供了专门的并行化数据加载工具以实现高效数据导入,详情可以参考 gpfdist 和 gpload 的官方文档。

在数据分布方面,Greenplum 在这方面不单单做到了基本的分布式数据存储,还提供了很多更高级灵活的特性,譬如多级分区、多态存储。Greenplum 6 进一步增强了这一领域,实现了一致性哈希和复制表,并允许用户根据应用干预数据分布方法。有这么多种手段,可见Greenplum用户肯定时遇到了很多数据倾斜的问题

Greenplum支持的分区方法有:

范围分区:根据某个列的时间范围或者数值范围对数据分区。譬如以下 SQL 将创建一个分区表,该表按天分区,从 2016-01-01 到 2017-01-01 把全部一年的数据按天分成了 366 个分区:

    CREATE TABLE sales (id int, date date, amt decimal(10,2))
    DISTRIBUTED BY (id)
    PARTITION BY RANGE (date)
    ( START (date '2016-01-01') INCLUSIVE
    END (date '2017-01-01') EXCLUSIVE
    EVERY (INTERVAL '1 day') );

    列表分区:按照某个列的数据值列表,将数据分不到不同的分区。譬如以下 SQL 根据性别创建一个分区表,该表有三个分区:一个分区存储女士数据,一个分区存储男士数据,对于其他值譬如 NULL,则存储在单独 other 分区。

      CREATE TABLE rank (id int, rank int, year int, gender char(1), count int ) 
      DISTRIBUTED BY (id)
      PARTITION BY LIST (gender)
      ( PARTITION girls VALUES ('F'),
      PARTITION boys VALUES ('M'),
      DEFAULT PARTITION other );

      Greenplum 支持多态存储,即单张用户表,可以根据访问模式的不同使用不同的存储方式存储不同的分区。通常不同年龄的数据具有不同的访问模式,不同的访问模式有不同的优化方案。多态存储以用户透明的方式为不同数据选择最佳存储方式,提供最佳性能。Greenplum 提供以下存储方式:

      • 堆表(Heap Table):堆表是 Greenplum 的默认存储方式,也是 PostgreSQL 的存储方式。支持高效的更新和删除操作,访问多列时速度快,通常用于 OLTP 型查询。

      • Append-Optimized 表:为追加而专门优化的表存储模式,通常用于存储数据仓库中的事实表。不适合频繁的更新操作。

      • AOCO (Append-Optimized, Column Oriented) 表:AOCO 表为列表,具有较好的压缩比,支持不同的压缩算法,适合访问较少的列的查询场景。

      • 外部表:外部表的数据存储在外部(数据不被 Greenplum 管理),Greenplum 中只有外部表的元数据信息。Greenplum 支持很多外部数据源譬如 S3、HDFS、文件、Gemfire、各种关系数据库等和多种数据格式譬如 Text、CSV、Avro、Parquet 等。

      存储方式和分区方式相组合,可以对一张表不同的数据区域有不同的存储方式。

      数据分布是任何 MPP 数据库的基础也是 MPP 数据库是否高效的关键之一。通过把海量数据分散到多个节点上,一方面大大降低了单个节点处理的数据量,另一方面也为处理并行化奠定了基础,两者结合起来可以极大的提高整个系统的性能。譬如在一百个节点的集群上,每个节点仅保存总数据量的百分之一,一百个节点同时并行处理,性能会是单个配置更强节点的几十倍。如果数据分布不均匀出现数据倾斜,受短板效应制约整个系统的性能将会和最慢的节点相同。因而数据分布是否合理对 Greenplum 整体性能影响很大

      Greenplum 6 提供了以下数据分布策略。

      1. 哈希分布:数据使用哈希分布,每个分布键可以包含多个字段,分布的时候对整个分布键下的tuple算哈希,然后放入对应的segment

      2. 随机分布:如果不能确定一张表的哈希分布键或者不存在合理的避免数据倾斜的分布键,则可以使用随机分布。随机分布会采用循环的方式将一次插入的数据存储到不同的节点上。随机性只在单个 SQL 中有效,不考虑跨 SQL 的情况。譬如如果每次插入一行数据到随机分布表中,最终的数据会全部保存在第一个节点上。

      3. 复制表(Replicated Table):整张表在每个节点上都有一个完整的拷贝

      查询计划并执行

      PostgreSQL 生成的查询计划只能在单节点上执行,Greenplum 需要将查询计划并行化以充分发挥集群的优势

      Greenplum 引入 Motion 算子(操作符)实现查询计划的并行化。Motion 算子实现数据在不同节点间的传输,它为其他算子隐藏了 MPP 架构和单机的不同,使得其他大多数算子不用关心是在集群上执行还是在单机上执行。每个 Motion 算子都有发送方和接收方。此外 Greenplum 还对某些算子进行了分布式优化,譬如聚集。

      执行器

      • QD(Query Dispatcher、查询调度器):Master 节点上负责处理用户查询请求的进程称为 QD(PostgreSQL 中称之为 Backend 进程)。QD 收到用户发来的 SQL 请求后,进行解析、重写和优化,将优化后的并行计划分发给每个 segment 上执行,并将最终结果返回给用户。此外还负责整个 SQL 语句涉及到的所有的 QE 进程间的通讯控制和协调,譬如某个 QE 执行时出现错误时,QD 负责收集错误详细信息,并取消所有其他 QEs;如果 LIMIT n 语句已经满足,则中止所有 QE 的执行等。QD 的入口是 exec_simple_query()。主要是火山模型

      • QE(Query Executor、查询执行器):Segment 上负责执行 QD 分发来的查询任务的进程称为 QE。Segment 实例运行的也是一个 PostgreSQL,所以对于 QE 而言,QD 是一个 PostgreSQL 的客户端,它们之间通过 PostgreSQL 标准的 libpq 协议进行通讯。对于 QD 而言,QE 是负责执行其查询请求的 PostgreSQL Backend 进程。通常 QE 执行整个查询的一部分(称为 Slice)。QE 的入口是 exec_mpp_query()。

      • Slice:为了提高查询执行并行度和效率,Greenplum 把一个完整的分布式查询计划从下到上分成多个 Slice,每个 Slice 负责计划的一部分。划分 slice 的边界为 Motion,每遇到 Motion 则一刀将 Motion 切成发送方和接收方,得到两颗子树。每个 slice 由一个 QE 进程处理。上面例子中一共有三个 slice。

      • Gang:在不同 segments 上执行同一个 slice 的所有 QEs 进程称为 Gang。****

      数据shuffle

      相邻 Gang 之间的数据传输称为数据洗牌(Data Shuffling)。数据洗牌和 Slice 的层次相吻合,从下到上一层一层通过网络进行数据传输,不能跨层传输数据。根据 Motion 类型的不同有不同的实现方式,譬如广播和重分布。

      Greenplum 实现数据洗牌的技术称为 interconnect,它为 QEs 提供高速并行的数据传输服务,不需要磁盘 IO 操作,是 Greenplum 实现高性能查询执行的重要技术之一。interconnect 只用来传输数据(表单的元组),调度、控制和错误处理等信息通过 QD 和 QE 之间的 libpq 连接传输。

      Interconnect 有 TCP 和 UDP 两种实现方式,TCP interconnect 在大规模集群中会占用大量端口资源,因而扩展性较低。Greenplum 默认使用 UDP 方式。UDP interconnect 支持流量控制、网络包重发和确认等特性。

      分布式事务

      Greenplum 使用两阶段提交(2PC)协议实现分布式事务。2PC 是数据库经典算法,此处不再赘述。本节概要介绍两个 Greenplum 分布式事务的实现细节:

      • 分布式事务快照:实现 master 和不同 segment 间一致性

      • 共享本地快照:实现 segment 内不同 QEs

      在 QD 开始一个新的事务(StartTransaction)时,它会创建一个新的分布式事务 id、设置时间戳及相应的状态信息;在获取快照(GetSnapshotData)时,QD 创建分布式快照并保存在当前快照中。和单节点的快照类似,分布式快照记录了 xmin/xmax/xip 等信息。这些信息被用于确定元组的可见性(HeapTupleSatisfiesMVCC)。

      和 PostgreSQL 的提交日志 clog 类似,Greenplum 需要保存全局事务的提交日志,以判断某个事务是否已经提交。这些信息保存在共享内存中并持久化存储在 distributedlog 目录下。

      为了提高判断本地 xid 可见性的效率,避免每次访问全局事务提交日志,Greenplum 引入了本地事务-分布式事务提交缓存

      HAWQ

      针对 Hadoop 存储的 SQL 执行引擎。HAWQ 通过数据接口可以直接读取 Hive 表里的数据(也支持原生存储格式),然后用 SQL 执行引擎来计算得到查询结果。与 HiveQL 通过把 SQL 解析成一连串的 MapReduce job 的执行模式相比,速度要快好几个量级。HAWQ 虽然在开发执行引擎过程中借鉴了很多 GPDB 的东西,但毕竟是一款不同的数据库引擎,Pivotal 因此希望有一款兼容的优化器能够服务于它。由此,研发了开源优化器 ORCA。

      Snowflake Elastic Data Warehouse

      除了使用了 vec-exec(毕竟,联合创始人 Marcin 的博士毕业论文就是关于 vec-exec 的),Snowflake 也是一款 100%计算和存储分离,面向云原生的数据仓库系统。本文内容主要参考他们发表于 SIGMOD-16 的 paper: The Snowflake Elastic Data Warehouse

      Snowlake 是 2012 年成立的,2015 年正式推出商用版本。2012 年,正是云服务起步不久,大数据热火朝天的时候。当时,数据仓库的主流趋势是 SQL On Hadoop。Cloudera, Hontornworks, MapR, Greenplum HAWQ, Facebook 的 Presto,算是百花齐放。但主创团队认为,RDBMS 不会消失,用户们会因为上云的趋势,想要一款完全适配云端的数据仓库。

      文章简单介绍了市面上通常的 on-prem 分布式数据仓库的一些缺点。首先就是计算和存储硬件是耦合的,即每个服务器同时负责存储数据,并且执行 SQL 语句得到结果。耦合的劣势在于,不能针对不同的 workloads 做优化。二就是服务器的 node membership 改变(无论是因为服务器损坏,或者是因为数据量提升需要扩容)对用户来说都不友善。一,就是要进行大量数据的 reshuffle。二是,为了做到高可用,可能会保留一部分 node 作为 stand-by replica,当主节点有问题时,马上接替主节点,这相当于变相提高了数据成本。总结来说,on-prem 的数据仓库要做到同时保持可伸缩性(elasticity)和高可用性(availability)并兼顾成本,是很难鱼与熊掌兼得的。三就是对服务进行升级比较麻烦。

      由于云服务的出现,很多上述的问题,变得不再是问题了。一就是,云服务通常会提供多种类型的服务器来针对特定的 usecase;二,服务器的下线,上线,扩容在云服务上都属于基本操作;三是,云上有高可用,低成本的存储系统;四是,服务更新非常方便。基于这些原因,Snowflake 选择了完完全全的计算和存储分离的架构设计。整个架构分成三个大模块:

      1. 数据存储:完全交给 AWS 的 S3 来存储数据。

      2. Virtual Warehouse(VW) 虚拟数据仓库实例(下面简称 VW):由多个 Virtual Node(AWS 中的 EC2 instance)组成的一个 Virtual Cluster,负责执行各种 SQL 语句,因此称为 Virtual Warehouse。数据库的执行引擎是也是自己构建的分布式引擎。

      3. Cloud Services:整个 Snowflake 的大脑:负责管理数据存储和 VW,以及其他一系列的操作,比如安全,登陆,事物管理,用户隔离,等等。值得注意的是,你可以大致认为整个 AWS,所有的用户,共享这一个大脑实例(当然,这个实例本身是多中心复制,高可用加高备份的),但每个用户只能管理属于自己的数据和 VW。

      数据存储

      在设计存储系统的时候,Snowflake 有纠结过,是应该使用 AWS 的 S3,还是自行设计类似于 HDFS 的存储系统。最终,在经过了各种比较,利弊权衡后,决定使用 S3。虽然,S3 的性能并不是最快;并且,由于是网络接入,也不是最稳定。但是,胜在高可用性和高可靠性上。团队决定基于 S3 打造数据存储系统,同时,可以把精力放在优化 local caching 和数据倾斜(skew resilience)上。

      相对于本地文件系统,S3 的 access latency 会更高,并且,由于是网络接入(尤其是用 https),CPU 使用率也更高。而且,S3 本身就是一个简单的 blob 存储,支持的主要创建,删除和读取文件,即,不能对现有文件进行更新,更新相当于重新创建一个更新过的文件。但是,S3 的读取有一大好处在于,可以读取部分文件。

      S3 的这些属性,对于整个 Snowflake 的数据存储和并行控制设计有重大的影响。首先,表数据被水平(horizontally partitioned)地切分成多个不可变的 blob 文件;每个文件通过列存(column-store)的形式保存数据,Snowflake 具体使用的存储格式是 PAX 的 Hybrid-column store(挖个坑,可以单独讲一期这个)。每个数据文件包含数据头用来存储元数据。基于 S3 的下载部分文件的 API,对于运行的 SQL 语句,优化器会选择只下载必须用到的数据 block 即可。这也就意味着所有snowflake的事务都是基于快照隔离Snapshot Isolation(SI)

      值得一提的是,Snowflake 不单单使用 S3 来存储表数据文件,也用 S3 来存储临时生成的 intermediate result(语句执行中,某个 operator 产生的临时结果集)。一旦这些结果集的大小超过了本地磁盘空间,spill 到磁盘上的文件就会以 S3 的形式存储。这样的好处在于,可以让 Snowflake 真正可以处理巨大的数据而不用担心内存或者本地磁盘空间吃紧。另一个好处在于,这些临时结果集也可能被利用作为 cache 使用。

      最后文中还提到了数据库的其他元数据存储,包括有哪些 caching 文件,每个表存在了哪些 S3 文件中,等等,都是存储在一个 transactional 的 key-value store 中,并不在 S3 里。

      虚拟数据仓库实例(Virtual Warehouse)

      执行 SQL 语句:每个语句 instance 都只会运行在一个 VW 上;每个 VW 有多个 WN;每个 WN 只隶属于一个 VW,不会被共享。(这边有注解说,WN 变成共享的会是一个未来的工作,因为可以更好地提升使用率并且会进一步降低用户成本)。当一个语句被运行时,所有的 WN 在这个 VW 上,(或者也可能是一部分 WN,如果优化器认为这是一个非常轻量级的语句),都会起一个 worker process,这个进程的生命周期就是这句语句的执行周期。worker process ,在执行的过程中,不会对外部资源造成任何变化,换言之,no side effect,即使是 update 语句。为什么这么说呢,因为所有的表数据文件都是 immutable 的。这样带来的好处就是,如果 worker process 由于各种原因崩溃了, 通常只是需要 retry 即可,没有其他善后事宜要做。现在 VW 里还不支持 partial retry,这也在未来计划的工作中。

      由于 VW 的可伸缩性(elasticity),通常情况下,可以通过起一个更大 size 的 VW 来提升语句的性能,但保持一样的使用成本。例如,**一个复杂的分析语句在一个 4 节点 VW 上需要运行 15 个小时,但在一个 32 节点 VW 上只需要 2 小时。**因为是云原生,用户只需要支付运行 VW 时的费用即可。因此,在价格不变的情况下,用户体验和查询速度却大幅度提升。这也是 Snowflake 云原生数据仓库的一大卖点。

      • 本地缓存: 每个 WN 都会用本地文件为表数据做本地缓存,即已经被从 S3 那读取的数据文件。这些文件是包含元数据信息和要用到的 column 的数据。这些缓存的数据文件可以被多个 worker process 共享(如果需要读取一样的数据),文中提到维护了一个简单的 LRU 的 cache replacement 策略,效果非常不错。为了进一步提升 hit rate,同一份数据文件被多个 WN 节点保存,优化器会用 consistent hashing 算法,来分配哪些节点保存哪些数据。同时,对于后续要读取对应数据的语句,优化器也会根据这个分配发送到对应节点。

      • 数据倾斜处理:一些节点可能相对于其他节点,运行更慢,比如硬件问题或者是单纯网络问题。Snowflake 的优化是,每个 WN 在读取了相应的数据文件后,当它发现其他 WN 还在读取,他会发送请求给其他 WN 要求分担更多的数据,而且这些数据直接从 S3 读取。从而来确保不要把过多的数据处理放在速度慢的 WN 上。

      • 执行引擎:虽说可以通过增加节点来提升性能,但是 Snowflake 依然希望每一个节点的单体性能都能做到极致。因此,Snowflake 构建了自己的,基于列存向量执行(vec-exec),并且是 push-based(推模式)的执行引擎。 

        • Columnar: 没啥争议,对于 OLAP 语句来说,Columnar-store 无论从存储,读取效率和执行效率来说,都优于 row-store。

        • Vec-exec:也没有争议,Marcin 肯定把 Vec-Exec 这套运行优化放到执行器上。

        • push-based: 相对于 Volcano 的拉模式,是下方的 operator,当处理完数据后,把数据 push 到上方的 operator(从执行计划角度来看上下),类似于 code-gen,这样的好处是提高了 cache 的利用率,因为可以避免不必要的循环控制语句。

        • 另一点就是,一些其他传统数据库系统在执行语句时需要考虑的麻烦,对于 Snowflake 来说没有。比如,不用 transaction management,因为所有的语句都是没有 side effect 的。(原因是S3中的文件不可以更改)

      AnalyticDB

      数据库带来的新挑战:

      1. 在线化和高可用:离线和在线的边界越来越模糊,一切数据皆服务化、一切分析皆在线化;

      2. 高并发低延时:越来越多的数据系统直接服务终端客户,对系统的pp和处理延时提出了新的交互性挑战;

      3. 混合负载:一套实时分析系统既要支持数据加工处理,又要支持高并发低延时的交互式查询;

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

      Oracle RAC --> Greenplum --> HBase --> AnalyticDB

      ADB主要是OLAP系统,同时要顾及各种点查询、优化的速度。底层采用盘古,所以数据库主要的创新点在数据格式、优化器、执行器等等

      系统架构

      AnalyticDB主要分为以下几个部分:

      1. Coordinator(协调节点):协调节点负责接收JDBC/ODBC连接发过来的请求,并将请求分发给读节点或者写节点

      2. Write Node(写节点):只处理写请求(如INSERT、DELETE、UPDATE)的节点。 

        1. 某个写节点会被选为主节点,其他写节点选为从节点,主节点和从节点之间通过ZooKeeper来进行通信。每个节点会独立负责某些一级分区的数据,主节点的任务就是决定每个节点负责哪些一级分区。协调节点会将写请求分发到对应的写节点上,写节点收到请求后,会将写SQL语句放到内存buffer中,这些buffer中的SQL语句称为log数据。

        2. 写节点会将buffer中的log数据刷到盘古上,当刷盘古成功后,写节点会返回一个版本号(即LSN)给协调节点,表示写完成了。每个一级分区在其对应的写节点上,都会独立地对应一个版本号,每次写节点将某个一级分区的log数据刷到盘古后,都会增大这个版本号,并将最新版本号返回给协调节点。

        3. 当盘古上的log数据达到一定规模时,AnalyticDB会在伏羲上启动MapReduce任务,以将log数据转换成真实存储数据+索引

      3. Read Node(读节点):只处理读请求(如SELECT)的节点。 

        1. 每个读节点也独立负责某些一级分区的数据。在每个读节点初始化时,它会从盘古上读取最新版本数据(包括索引)。之后,基于这份数据,读节点会从写节点的内存buffer中将写请求log周期性地拉取过来,并在本地进行replay,replay之后的数据不会再存储到盘古中(而是存到本地ssd中?)。读节点根据replay之后的数据,服务到来的读请求。

        2. 由于读节点需要去从写节点上拉取写请求数据,因此读节点为用户提供了两种可见性级别:实时(real-time)可见和延时(bounded-staleness)可见。实时可见允许读节点立即读到写节点写入的数据,延时可见允许读节点在一段时间后才读到写节点上写入的数据。AnalyticDB默认使用的可见性级别为延时可见。(我猜延时可见就是用某种方式读polarFS,这个架构有点像Aurora的一写14读了)

      4. Pangu(盘古):高可靠分布式存储系统,是AnalyticDB依赖的基础模块。写节点会将写请求的数据刷到盘古上进行持久化。

      5. Fuxi(伏羲):资源管理与任务调度系统,是AnalyticDB依赖的基础模块。伏羲合理使用集群机器的空闲资源,以进行相关计算任务的异步调度执行。

      为便于大规模分析处理,AnalyticDB对数据表进行分区。AnalyticDB数据表有两个分区级别:一级分区和二级分区。

      选择具有较高基数(cardinality)的列作为一级分区键,以保证数据行能均匀地分布到每个一级分区,最大化并行。用户还可以根据需要定义二级分区,以便进行数据的自动管理。二级分区拥有最大分区数,当二级分区的实际数目超过了这个最大分区数后,最老的二级分区会被自动删除。通常,选择时间列(天、周或月)作为二级分区列,这样,包含相同时间序列的数据行,会被划分到同一个二级分区中。

      传统OLAP系统在同一个链路上同时处理读写请求,因此,所有的并发读写请求都共享同一个资源池,也会互相影响。但是当读写并发同时非常大时,这种设计会由于过度的资源竞争而导致不好的性能。如图5所示,为了解决这个问题,同时确保读和写的高性能,AnalyticDB采用的架构为读写分离架构,即AnalyticDB有独立的读写节点各自处理读写请求,且写节点和读节点完全互相隔离。

      保证

      1. 可靠性:写节点自己选主,并且负责负载均衡,用户可以指定每个读节点的副本个数。既保证了可靠性,又保证了读写带宽

      2. 扩展性:当有新写节点加入时,自动负责负载均衡

      3. 多租户:使用CGroup负责多租户的隔离(CPU/内存/网络带宽)(一个AnalyticDB实例会根据对应的资源创建上面提到的各种节点)

      存储引擎

      AnalyticDB存储层采用Lambda架构,读节点上的数据包括基线数据增量数据两部分。增量数据又分为Incremental Data和Deleted bitset,按照行列混存的架构存放在读节点的SSD上。真正读取是,basline数据要和增量数据做UNION和MINUS之后,才能输出有效数据。

      对于每张表,每k行的数据组成一个Row Group。Row Group中的数据连续存放在磁盘中。整个Row Group中,又将数据按照列(聚集列)分别顺序存放。AnalyticDB会对每列构建一份元数据,用于维护列数据的统计信息(包括Cardinality、Sum和Min/Max等)、字典数据(采用字典编码)以及物理映射等。AnalyticDB默认会对每一列数据建立索引,索引中的Key是列的值,Value是值出现的所有行号集合,采用后台异步构建模式。由于增量数据部分没有索引,随着数据的不断实时写入,增量数据的查询性能会越来越慢。AnalyticDB采用后台任务来合并基线数据和增量数据形成一个新的基线数据,并基于新的基线数据构建全量索引。

      读写过程

      使用copy on write技术(OLAP读多写少)来支持MVCC,delete数据被转化在Deleted bitset上,而update操作则被分为Incremental Data和Deleted bitset分别存放。每个写操作都会分配独立的LSN,从而达到MVCC

      由于建立了全列倒排索引,所以执行引擎处理返回结果的时候用到了多路归并

      数据合并

      由于没有全局索引,随着数据的不断实时写入,增量数据的查询性能会越来越慢。因此ADB会在后台通过伏羲启动一个MapReduce 任务来合并基线数据和增量数据(同时去掉标记为删除的数据)形成一个新的基线数据,并基于新的基线数据构建全量索引。

      在合并任务开始时,一部分增量数据会标记为immutable,并执行合并,合并完成之后,之前的baseline data和immutable会被删除

      行列混存

      在海量数据分析场景下,数据分析业务主要有以下三类workload:

      1. OLAP场景下的大规模多维分析:海量数据的统计分析和多表关联,比较适合列存格式

      2. 高并发的点查:通常需要捞取出一整行的明细数据,比较适合行存。

      3. 高写入吞吐:每秒千万的高吞吐实时写入,比较适合行存。

      在ADB的实现中,每K行数据实现了Row Group,每个row group中的每个列存放在自己的block中,Row group按照索引排列

      inverted index

      为了应对ad-hoc,ADB对每列建立了倒排索引,从而提高复杂数据的查询效率。(每列都建立索引,不就是倒排索引了)

      元数据

      为了加速查询,AnalyticDB对每列构建一份元数据,并保存在一个叫detail_meta的单独文件中。detail_meta文件通常较小(小于1MB),首次查询时被加载在内存中。如图8左边所示,元数据主要包括4部分:

      • Header。包括版本号,文件长度以及一些统计信息。

      • 列统计信息。包括行数,NULL值数,cardinality,SUM,MAX和MIN 值。优化器根据这些信息来生成最佳执行计划。

      • 字典。对于cardinality较少(小于1024)的列,AnalyticDB采用字典编码,数据文件里保存字典号码。字典保存在该字段中。

      • 块地址信息。保存块号到数据文件起始地址和长度的映射关系。(我猜测是每次合并的时候更新)

      索引管理

      AnalyticDB设计和实现了一个新的索引引擎,在不影响写入性能的情况下,支持结构化和非结构化数据类型索引。它将构建过程从写入链路中移除,采用后台异步构建模式,支持对所有列构建索引,从而解决了OLAP任意查询的性能问题

      AnalyticDB默认对所有列构建索引,并保存在一个单独的文件中。与传统的数据库不同,AnalyticDB索引中的key是列的值,value是该值出现的所有行号集合,并支持所有的条件同时走索引查询。多个列的操作去做union或者intersect

      AnalyticDB在索引引擎是实现上也做了大量的优化,包括:多路流式归并、索引选择CBO和索引结果缓存。

      • 多路流式归并:传统数据库大多采用2路归并策略,在条件数特别多的场景下,会导致大量中间结果,计算效率很低。AanlyticDB采用K路流式归并算法,可以支持多个集合并行归并,避免产生大量中间结果集合,提升了整个归并的速度。

      • 索引选择CBO:当where条件中包括多个条件,并不是所有的条件走索引扫描能取得最佳的性能。利用索引中的统计信息,提前估算出各个条件可能的选择率,对于选择率很高的条件走索引查询,其他条件直接在上层进行过滤操作。例如对于where id = 1 and 0 < x < 1000000
        的情况下,id = 1
        这个条件的选择率已经很高,则0<x<1000000
        条件不走索引查询效率会更高。

      • 索引结果缓存:在OLAP分析场景中,多个查询条件中,可能会出现部分条件固定不变或重复多次出现。针对这种场景AnalyticDB 实现了一个高效的无锁缓存,缓存的的key为等值或range条件,value为行号集合。这样在出现重复查询情况下,可以直接读取缓存,避免索引IO扫描开销。

      索引构建

      为了支持每秒千万的实时数据写入,避免同步构建索引影响实时写入的性能,AnalyticDB并没有采用同步构建索引的策略,而是采用异步后台进程构建索引的方式。索引引擎会根据时间或增量数据的大小来决定是否启动后台进程来构建索引。该后台进程读取Pangu上的历史全量数据和新写入的增量日志数据,完成数据合并形成新的全量数据,并对该全量数据重新构建索引。该过程通过伏羲的MapReduce任务执行,选择负载较低的机器执行,对用户完全透明。

      优化器

      创新性引入了两个关键功能:存储感知的优化和高效实时采样。因为ADB独特的索引结构和分布式的数据存储

      执行引擎

      在优化器之下,AnalyticDB在MPP架构基础上,采用流水线执行的DAG架构,构建了一个适用于低延迟和高吞吐量工作负载的执行器。AnalyticDB的列式执行引擎能够充分利用底层的行列混合存储。与行式执行引擎相比,当前的向量化执行引擎更加缓存友好,能避免将不必要的数据加载到内存中。

      与许多 OLAP 系统一样,AnalyticDB在运行时利用代码生成器(CodeGen) 来提高 CPU 密集型计算的性能。AnalyticDB的CodeGen基于 ANTLR ASM来动态生成表达式的代码树。同时此 CodeGen 引擎还将运行时因素纳入考虑,让AnalyticDB能在Task级别利用异构新硬件的能力。例如,如果集群中CPU支持 AVX-512指令集,我们通过生成字节码使用SIMD来提高性能。在此之外,通过整合内部数据表示形式,在存储层和执行引擎之间,AnalyticDB是能够直接对序列化二进制数据进行操作,而不是Java 对象。这有助于消除序列化和去序列化的开销,这在大数据量shuffle时可能会节约20%以上的时间。

      总结

      得益于流水线处理、全列索引、行列混存、运行时索引路径选择、K路归并、向量化执行引擎、CodeGen等优化机制,AnalyticDB获得了最优的TCP-H测试运行时间,并比Greenplum快了近2倍。

      PolarDB/PolarFS

      使用共享存储解决MySQL主从结构遇到的一系列问题

      系统结构:

      • libpfs是一个用户空间文件系统库,负责数据库的I/O接入。

      • PolarSwitch运行在计算节点上,用于转发数据库的I/O请求。每个请求包含了数据库实例所在的Volume ID、起始偏移和长度。PolarSwitch将其划分为对应的一到多个Chunk,并将请求发往Chunk所属的ChunkServer完成访问。

      • ChunkServer部署在存储节点上,用于处理I/O请求和节点内的存储资源分布。ChunkServer之间通过所谓的ParallelRaft同步数据

      • PolarCtrl是系统的控制平面,它包含了一组实现为微服务的管理者,相应地Agent代理被部署到所有的计算和存储节点上。主要职责: 

        • 监控ChunkServer的健康状况,确定哪些ChunkServer有权属于PolarFS集群;

        • Volume创建及Chunk的布局管理(即Chunk分配到哪些ChunkServer);

        • Volume至Chunk的元数据信息维护;

        • 向PolarSwitch推送元信息缓存更新;

        • 监控Volume和Chunk的I/O性能;

        • 周期性地发起副本内和副本间的CRC数据校验。

      存储资源管理单元:

      • Volume:是为每个数据库提供的独立逻辑存储空间,其上建立了具体文件系统供此数据库使用,其大小为10GB至100TB,可充分适用于典型云数据库实例的容量要求。

      • Chunk:每个Volume内部被划分为多个Chunk,Chunk是数据分布的最小粒度,每个Chunk只存放于存储节点的单个NVMe SSD盘上,其目的是利于数据高可靠和高可用的管理。典型的Chunk大小为10GB,这远大于其他类似的系统,例如GFS的64MB。虽然chunk很大,但是chunk可以通过在线迁移维持负载均衡(chunk存储在固态盘上、还要在线迁移,这个服务不可用时间有多长???)

      • Block:在ChunkServer内,Chunk会被进一步划分为多个Block,其典型大小为64KB。Blocks动态映射到Chunk 中来实现按需分配。Chunk至Block的映射信息由ChunkServer自行管理和保存,除数据Block之外,每个Chunk还包含一些额外Block用来实现Write Ahead Log(写到optane)。

      读写流程

      1. POLARDB通过libpfs发送一个写请求,经由ring buffer发送到PolarSwitch。

      2. PolarSwitch根据本地缓存的元数据,将该请求发送至对应Chunk的主节点。

      3. 新写请求到达后,主节点上的RDMA NIC将写请求放到一个提前分好的buffer中,并将该请求项加到请求队列。一个I/O轮询线程不断轮询这个请求队列,一旦发现新请求到来,它就立即开始处理。

      4. 请求通过SPDK写到硬盘的日志block,并通过RDMA发向副本节点。这些操作都是异步调用,数据传输是并发进行的。

      5. 当副本请求到达副本节点,副本节点的RDMA NIC同样会将其放到预分buffer中并加入到复制队列。

      6. 副本节点上的I/O轮询线程被触发,请求通过SPDK异步地写入Chunk的日志。

      7. 当副本节点的写请求成功回调后,会通过RDMA向主节点发送一个应答响应。

      8. 主节点收到一个复制组中大多数节点的成功返回后,主节点通过SPDK将写请求应用到数据块上。就这一步比较重要,其他不用看

      9. 随后,主节点通过RDMA向PolarSwitch返回。

      10. PolarSwitch标记请求成功并通知上层的POLARDB。

      ParallelRaft

      ParallelRaft与Raft最根本的不同在于,当某个entry提交成功时,并不意味着之前的所有entry都已成功提交。因此我们需要保证:

      1. 在这种情况下,单个存储的状态不会违反存储语义的正确性;

      2. 所有已提交的entry在各种边界情况下均不会丢失;

      有了这两点,结合数据库或其他应用普遍存在的对存储I/O乱序完成的默认容忍能力,就可以保证它们在PolarFS上的正常运转,并获得PolarFS提供的数据可靠性。

      ParallelRaft的乱序执行遵循如下原则:

      1. 当写入的Log项彼此的存储范围没有交叠,那么就认为Log项无冲突可以乱序执行;

      2. 否则,冲突的Log项将按照写入次序依次完成。

      容易知道,依照此原则完成的I/O不会违反传统存储语义的正确性。

      后面说了一大堆,反正就是paxos,因为同一个raft上面,可能会有多个并行的事务,所以一定要乱序提交,乱序确认

      PolarFS

      PolarFS设计中采用了如下技术以充分发挥I/O性能:

      • PolarFS采用了绑定CPU的单线程有限状态机的方式处理I/O,避免了多线程I/O pipeline方式的上下文切换开销。

      • PolarFS优化了内存的分配,采用MemoryPool减少内存对象构造和析构的开销,采用巨页来降低分页和TLB更新的开销。

      • PolarFS通过中心加局部自治的结构,所有元数据均缓存在系统各部件的内存中,基本完全避免了额外的元数据I/O。

      • PolarFS采用了全用户空间I/O栈,包括RDMA和SPDK,避免了内核网络栈和存储栈的开销。

      PolarFS是共享访问的分布式文件系统,每个文件系统实例都有相应的Journal文件和与之对应的Paxos文件。Journal文件记录了metadata的修改历史,是共享实例之间元数据同步的中心。Journal文件逻辑上是一个固定大小的循环buffer。PolarFS会根据水位来回收journal。Paxos文件基于Disk Paxos实现了分布式互斥锁(文件锁,文件系统里的悲观锁,性能如何?)。

      由于journal对于PolarFS非常关键,它们的修改必需被Paxos互斥锁保护。如果一个节点希望在journal中追加项,其必需使用DiskPaxos算法来获取Paxos文件中的锁。通常,锁的使用者会在记录持久化后马上释放锁。但是一些故障情况下使用者不释放锁。为此在Paxos互斥锁上分配有一个租约lease。其他竞争者可以重启竞争过程。当PolarFS当节点开始同步其他节点修改的元数据时,它从上次扫描的位置扫描到journal末尾,将新entry更新到memory cache中。

      PolarFS的上述共享机制非常适合POLARDB一写多读的典型应用扩展模式。一写多读模式下没有锁争用开销,只读实例可以通过原子I/O无锁获取Journal信息,从而使得POLARDB可以提供近线性的QPS性能扩展。

      由于PolarFS支持了基本的多写一致性保障,当可写实例出现故障时,POLARDB能够方便地将只读实例升级为可写实例,而不必担心底层存储产生不一致问题,因而方便地提供了数据库实例Failover的功能。(DBFS,单机高可用)

      感觉这个系统从db到libpfs、到后端存储chunkserver,都有WAL…所以最底层做快照,libpfs可以恢复,然后上层的PolarDB也可以恢复。

      对底层盘做快照而不是对上层db做快照有一个问题,就是对盘做快照的时候,当时正在执行的IO,其是否真正落盘了是UB的。PolarDB管这种快照叫做disk outage consistency snapshot
      ,在具体的实现上,如果做快照,PolarCtrl会通知PolarSwitch,在某个时间点的IO上打Tag,chunkserver收到对应的tag之后,说明这个tag时间的时间位点就是一个快照点。所以会先做快照,然后再处理打上tag的IO。这样,做快照的时间就和上层对应的某个事务的LSN联系起来了。

      事务的数据可见性问题

      一、MySQL/InnoDB
      通过Undo日志来实现事务的MVCC,由于只读节点跟读写节点属于不同的mysqld进程,读写节点在进行Undo日志Purge的时候并不会考虑此时在只读节点上是否还有事务要访问即将被删除的Undo Page,这就会导致记录旧版本被删除后,只读节点上事务读取到的数据是错误的。

      针对该问题,PolarDB提供两种解决方式:

      • 所有ReadOnly定期向Primary汇报自己的最大能删除的Undo数据页,Primary节点统筹安排;

      • 当Primary节点删除Undo数据页时候,ReadOnly接收到日志后,判断即将被删除的Page是否还在被使用,如果在使用则等待,超过一个时间后还未有结束则直接给客户端报错。

      二、还有个问题,由于InnoDB BP刷脏页有多种方式,其并不是严格按照oldest modification来的,这就会导致有些事务未提交的页已经写入共享存储,只读节点读到该页后需要通过Undo Page来重建可见的版本,但可能此时Undo Page还未刷盘,这就会出现只读上事务读取数据的另一种错误。

      针对该问题,PolarDB解决方法是:

      • 限制读写节点刷脏页机制,如果脏页的redo还没有被只读节点回放,那么该页不能被刷回到存储上。这就确保只读节点读取到的数据,它之前的数据链是完整的,或者说只读节点已经知道其之前的所有redo日志。这样即使该数据的记录版本当前的事务不可见,也可以通过undo构造出来。即使undo对应的page是旧的,可以通过redo构造出所需的undo page。

      • replica需要缓存所有未刷盘的数据变更(即RedoLog),只有primary节点把脏页刷入盘后,replica缓存的日志才能被释放。这是因为,如果数据未刷盘,那么只读读到的数据就可能是旧的,需要通过redo来重建出来,参考第一点。另外,虽然buffer pool中可能已经缓存了未刷盘的page的数据,但该page可能会被LRU替换出去,当其再次载入所以只读节点必须缓存这些redo。

      DDL问题

      如果读写节点把一个表删了,反映到存储上就是把文件删了。对于mysqld进程来说,它会确保删除期间和删除后不再有事务访问该表。但是在只读节点上,可能此时还有事务在访问,PolarFS在完成文件系统元数据同步后,就会导致只读节点的事务访问存储出错。

      PolarDB目前的解决办法是:如果主库对一个表进行了表结构变更操作(需要拷表),在操作返回成功前,必须通知到所有的ReadOnly节点(有一个最大的超时时间),告诉他们,这个表已经被删除了,后续的请求都失败。当然这种强同步操作会给性能带来极大的影响,有进一步的优化的空间。

      Change Buffer问题

      Change Buffer本质上是为了减少二级索引带来的IO开销而产生的一种特殊缓存机制。当对应的二级索引页没有被读入内存时,暂时缓存起来,当数据页后续被读进内存时,再进行应用,这个特性也带来的一些问题,该问题仅存在于StandBy中。例如Primary节点可能因为数据页还未读入内存,相应的操作还缓存在Change Buffer中,但是StandBy节点则因为不同的查询请求导致这个数据页已经读入内存,可以直接将二级索引修改合并到数据页上,无需经过Change Buffer了。但由于复制的是Primary节点的redo,且需要保证StandBy和Primary在存储层的一致性,所以StandBy节点还是会有Change Buffer的数据页和其对应的redo日志,如果该脏页回刷到存储上,就会导致数据不一致。

      为了解决这个问题,PolarDB引入shadow page的概念,把未修改的数据页保存到其中,将Change Buffer记录合并到原来的数据页上,同时关闭该Mtr的redo,这样修改后的Page就不会放到Flush List上。也就是StandBy实例的存储层数据跟Primary节点保持一致。

      Polar-X

      ClickHouse

      ClickHouse拥有多种表引擎类型,在这众多的表引擎中,MergeTree是比较有代表性的引擎之一,被广泛使用。

      MergeTree采用列式存储,类似LSM Tree的架构组织数据。数据导入时被划分为多个Part,每个Part对应一个目录。Part中包含各个列的数据,每个列都有独立的文件。后台会调度合并任务,将多个小的Part合并成更大的Part,类似LSM Tree的合并过程。Part中包含几类文件:

      • 数据文件(.bin),每一列的数据都分别存储在数据文件,一般以主键排序。数据文件中划分为若干个Block,Block是列存文件的压缩单元。每个Block又会包含若干个索引Granularity,用于索引定位。

      • 索引文件(.idx),索引文件又分为主键索引和二级索引: 

        • MergeTree的主键索引与传统数据库的主键索引有所不同,MergeTree的主键索引只负责排序,但是不会去重。主键索引文件中,存储的是每一个Granularity中起始行的主键值,可以在扫描过程中过滤部分Granularity。

        • MergeTree的二级索引文件中可以存储Granularity的minmax、set、bloom_filter、ngrambf_v1等信息。

      • Mark文件(.mrk),由于索引文件是对Granularity进行索引,类似于逻辑索引。Mark文件记录Granularity在数据文件中的物理偏移,类似于将逻辑索引转换成物理索引。

      MergeTree对于批量导入支持较好,对OLTP级事务更新仅有限支持。MergeTree存储引擎对数据实时可见要求非常高的场景是不太友好的。


      TiFlashAnalyticDBClickHouseSqlServer
      存储结构Delta Tree,磁盘行列混存增量 + 基线,磁盘行列混存MergeTree,磁盘列存Hekaton列存索引,内存行列混存
      索引结构主键索引全列倒排索引主键索引 + 二级索引本身是行存的索引,可以利用行存的其他索引
      数据更新方式MVCC事务隔离,支持TP型事务和批量导入MVCC事务隔离,支持TP型事务批量导入友好,有限支持更新与行存保持一致
      数据压缩通用压缩字典压缩通用压缩RLE等专用压缩

      TiDB

      shared-nothing,raft,很多mysql实现的功能还没实现。底层KV存储,TiBD主要负责和client对接,然后做优化,很多执行计划会下推到TiKV

      我在想TiDB还是有很多问题的,首先TiDB的底座不是云原生的基础组件(类比snowflake polarDB ADB),很多问题上云之后就没法解决了

      TiDB目前有两种存储节点,分别是 TiKV 和 TiFlash。TiKV 采用了行式存储,更适合 TP 类型的业务;而 TiFlash 采用列式存储,擅长 AP 类型的业务。TiFlash 通过 raft 协议从 TiKV 节点实时同步数据,拥有毫秒级别的延迟,以及非常优秀的数据分析性能。它支持实时同步 TiKV 的数据更新,以及支持在线 DDL。我们把 TiFlash 作为 Raft Learner 融合进 TiDB 的 raft 体系,将两种节点整合在一个数据库集群中,上层统一通过 TiDB 节点查询,使得 TiDB 成为一款真正的 HTAP 数据库。

      TiFlash

      TiFlash的列式存储引擎Delta Tree参考了B+ TreeLSM Tree的设计思想。

      • Delta Tree将数据按照主键划分为Range分区,每个分区称为Segment。

      • Segment通过B+ Tree作为索引。也就是说,B+ Tree索引的叶子节点为Segment。

      • 在Segment内部采用类似LSM Tree的分层存储方式,不过采用固定两层的LSM Tree,分别为Delta层和Stable层。 

        • Delta层保存增量数据部分,其中,新写入的数据写入Delta Cache中,与LSM Tree的MemTable类似。当Delta Cache写满后,其中的数据刷入Delta层的Pack中,类似LSM Tree的L0层。

        • Stable层类似于LSM Tree的L1层,其中的数据以主键和版本号排序。

        • Delta层的Pack和Stable层需要做全量合并,得到新的Stable层数据。

      • 当Segment中的数据量超过阈值,就会做类似B+ Tree叶子节点的分裂操作,分裂成两个Segment。同时,如果相邻的Segment中的数据量都比较小,也会将相邻的Segment合并成一个Segment。

      C-Store(2005)/Vertica

      大多数DBMS都是为写优化,C-store是第一个为读优化的OLTP数据库。C-Store 的主要贡献有以下几点:通过精心设计的 projection 同时实现列数据的多副本和多种索引方式;用读写分层的方式兼顾了(少量)写入的性能。此外,C-Store 可能是第一个现代的列式存储数据库实现,其的设计启发了无数后来的商业或开源数据库,就比如 Vertica。

      在 C-Store 内部,逻辑表被纵向拆分成 projections每个 projection 可以包含一个或多个列,甚至可以包含来自其他逻辑表的列(构成索引)。当然,每个列至少会存在于一个 projections 上。

      Projection 内是以列式存储的:里面的每个列分别用一个数据结构存放。为了避免列太长引起问题,也支持每个 projection 以 sort key 的值做横向切分。

      Projection 是有冗余性的,常常 1 个列会出现在多个 projection 中,但是它们的顺序也就是 sort key 并不相同,因此 C-Store 在查询时可以选用最优的一组 projections,使得查询执行的代价最小。

      Apache ORC

      Apache ORC 最初是为支持 Hive 上的 OLAP 查询开发的一种文件格式,如今在 Hadoop 生态系统中有广泛的应用。ORC 支持各种格式的字段,包括常见的 int、string 等,也包括 struct、list、map 等组合字段;字段的 meta 信息就放在 ORC 文件的尾部(这被称为自描述的)。

      ORC 里的 Stripe 就像传统数据库的页,它是 ORC 文件批量读写的基本单位。这是由于分布式储存系统的读写延迟较大,一次 IO 操作只有批量读取一定量的数据才划算。这和按页读写磁盘的思路也有共通之处。

      Apache ORC 提供有限的 ACID 事务支持。受限于分布式文件系统的特点,文件不能随机写,那如何把修改保存下来呢?

      类似于 LSM-Tree 中的 MVCC 那样,writer 并不是直接修改数据,而是为每个事务生成一个 delta 文件,文件中的修改被叠加在原始数据之上。当 delta 文件越来越多时,通过 minor compaction 把连续多个 delta 文件合成一个;当 delta 变得很大时,再执行 major compaction 将 delta 和原始数据合并。这种保持基线数据不变、分层叠加 delta 数据的优化方式在列式存储系统中十分常见,是一种通用的解决思路

      Dremel (2010) / Apache Parquet

      Dremel 是 Google 研发的用于大规模只读数据的查询系统,用于进行快速的 ad-hoc 查询,弥补 MapReduce 交互式查询能力的不足。为了避免对数据的二次拷贝,Dremel 的数据就放在原处,通常是 GFS 这样的分布式文件系统,为此需要设计一种通用的文件格式。

      Dremel 的系统设计和大多 OLAP 的列式数据库并无太多创新点,但是其精巧的存储格式却变得流行起来,Apache Parquet 就是它的开源复刻版。注意 Parquet 和 ORC 一样都是一种存储格式,而非完整的系统。

      Impala

      Impala是Cloudera公司主导开发的新型查询系统,它提供SQL语义,能查询存储在Hadoop的HDFS和HBase中的PB级大数据。已有的Hive系统虽然也提供了SQL语义,但由于Hive底层执行使用的是MapReduce引擎,仍然是一个批处理过程,难以满足查询的交互性。相比之下,Impala的最大特点也是最大卖点就是它的快速。Impala完全抛弃了MapReduce这个不太适合做SQL查询的范式,而是像Dremel一样借鉴了MPP并行数据库的思想另起炉灶,因此可做更多的查询优化,从而省掉不必要的shuffle、sort等开销。

      Impala与Hive类似不是数据库而是数据分析工具,集群有以下几类节点

      1. Impalad,Impala的核⼼组件,负责读写数据,执行查询任务,并将结果返回协调者 

        1. Impalad服务由三个模块组成:Query Planner、Query Coordinator和Query Executor,前两个模块组成前端,负责接收SQL查询请求,解析SQL并转换成执⾏计划,交由后端执⾏。

      2. statestored,负责监控集群中Impalad的健康状况,并将集群健康信息同步给Impalad。

      3. catalogd,Impala执⾏的SQL语句引发元数据发⽣变化时,catalog服务负责把这些元数据的变化同步给其它Impalad进程

      查询流程

      1. Client提交任务 

        • Client发送⼀个SQL查询请求到任意⼀个Impalad节点,会返回⼀个queryId⽤于之后的客户端操作。

      2. 生成查询计划(单机计划、分布式执行计划) 

        • Fragment :sql⽣成的分布式执⾏计划的⼀个⼦任务;

        • DataStreamSink:传输当前的Fragment输出数据到不同的节点;

        • SQL提交到Impalad节点之后,Analyser依次执⾏SQL的词法分析、语法分析、语义分析等操作;从MySQL元数据库中获取元数据,从HDFS的名称节点中获取数据地址,以得到存储这个查询相关数据的所有数据节点。

        • 单机执行计划:根据上⼀步对SQL语句的分析,由Planner先⽣成单机的执⾏计划,该执⾏计划是有PlanNode组成的⼀棵树,这个过程中也会执⾏⼀些SQL化,例如Join顺序改变、谓词下推等。

        • 分布式并⾏物理计划:将单机执⾏计划转换成分布式并⾏物理执⾏计划,物理执⾏计划由⼀个个的Fragment组成,Fragment之间有数据依赖关系,处理过程中要在原有的执⾏计划之上加⼊⼀些ExchangeNode和DataStreamSink信息等。 

      3. 任务调度和分发 

        • Coordinator将Fragment(⼦任务)根据数据分区信息发配到不同的Impalad节点上执⾏。Impalad节点接收到执⾏Fragment请求交由Executor执⾏。

      4. Fragment之间的数据依赖 

        • 每⼀个Fragment的执⾏输出通过DataStreamSink发送到下⼀个Fragment,Fragment运⾏过程中不断向coordinator节点汇报当前运⾏状态。

      5. 结果汇总 

        • 查询的SQL通常情况下需要有⼀个单独的Fragment⽤于结果的汇总,它只在Coordinator节点运⾏,将多个节点的最终执⾏结果汇总,转换成ResultSet信息。

      6. 获取结果 

        • 客户端调⽤获取ResultSet的接⼝,读取查询结果。

      Druid

      Druid可以对多列数据构建倒排索引(bitmap-based inverted indexes)

      Pinot

      ------------END-----------

      更多原创文章请扫描上面(微信内长按可识别)二维码访问我的个人网站(https://www.xubingtao.cn),或者打开我的微信小程序:分享录可以评论以及在线客服反馈问题,其他平台小程序和APP请访问:https://www.xubingtao.cn/?p=1675。祝大家生活愉快!

      文章转载自分享录,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

      评论