目录:
1、背景
2、Doris的架构
3、Doris的核心特性
4、数据的导入和输出
1、背景
大数据的时代,数据的处理能力大大增强,但在最后一个环节,即数据应用服务环节依然存在较大的瓶颈。
原来业务数据库时代,高并发、高灵活性是一个矛盾体,如何让一线在灵活定制分析SQL时候,虽然拖拉拽生成了不忍直视的SQL,但希望查询引擎依旧可以保持强劲的性能指标,不管是并发度还是查询时间都能让客户满意,是蛮有挑战的一件事情。
目前的查询分析非常多,并且还在不断的涌现出来,引擎层出不穷,各有优势也有其缺点,比如ADB、Hologres、Presto、Kylin、Hbase、Doris,这些产品本质上都是用资源换时间,或者空间换时间,本质上就是计算机制重构(比如MPP)、硬件提速(比如SSD磁盘)、索引提速(比如位图)、空间转换(比如预计算)等维度是提升性能。不断涌现且更新的技术产品也从侧面印证了查询引擎的问题依然很多,痛点依旧没有解决,同时也大有可为。
Doris最近也是比较火的一款产品,脱胎于百度的广告业务,适应于实时分析场景,确实解决了查询引擎的一些场景下的痛点问题,接下来就将个人对Doris的一些理解简单介绍一下。
2、Doris架构
在介绍Doris的特性之前,先让我们了解一下Doris的整体架构。具体包括几个核心维度:
Doris由FrontEnd DorisDB前端节点和BackEnd DorisDB后端节点核心组件组成;前端节点负责管理元数据、管理客户端的连接、进行查询规划和调度等工作;后端节点负责数据存储、计算执行、副本管理等;另外还包括DorisManager和Broker,DorisManager管理工具,负责提供集群管理、在线查询、故障查询、监控报警的可视化工具;Broker负责和外部存储(HDFS或对象存储)进行数据的导出导入等辅助功能;Doris可以通过MySQL客户端直接访问。
Doris核心组件及运行模式如下(以下FrontEnd DorisDB简称FE,BackEnd DorisDB简称BE):
首先是FE:
其次是BE:
以查询为例,通过FE的组织、协调、控制,对提交的SQL进行解析, 分析, 改写, 优化和规划, 生成分布式执行计划,然后由若干BE执行,并在若干BE中选定一个协作者coordinator,由协作者协调n个BE进行本地计算,然后返回给协作者,协作者汇总后返回给FE最终结果,最后由FE将最终结果提供给最终用户。
3、Doris的核心特性
Doris具体具备什么样的核心特性,让我们看看他能做什么。
1)表是如何设计?列式存储;稀疏索引Shortkey Index;加速数据处理(预先聚合、分区分桶、RollUp表物化索引、列级别的索引技术布隆过滤器和Bitmap索引);
2)数据模型有哪些?明细模型DUPLICATE、聚合模型AGGREGATE、更新模型UNIQUE;每个模型后都需要指定排序键;对于不同的应用场景,采取不同的数据模型,满足高性能的要求;
3)数据分布有几种?Round-Robin轮转范围、Round指定区分范围、离散List、哈希Hash;同时还支持动态分区分布;分区分桶是为了MPP最大化利用资源,为防止数据倾斜,需要选择合理的分布策略;
4)物化视图:相较于聚合模型的汇总分析的数据不一致性,物化视图天然的数据一致性是它最大的优点。
物化视图的组织形成与基表、RollUp表相同,创建后,基本的数据会自动以异步的方式填充分到物化视图中;数据导入时,基表和物化视图保持原子型保证数据的一致性。物化视图创建后,不能通过命令直接查询,还是查基表,是否用物化视图,需要由执行计划自动选择,可以查询是否使用了物化视图。todo基表、Rollup物化索引、物化视图都是使用前缀索引。
可以使用bitmap_union创建物化视图来处理精确去重;也可以使用hll_union创建物化视图来处理近亿去重;也可以匹配更丰富的前缀索引,比如用户的基表tableA有(k1, k2, k3) 三列。其中 k1, k2 为排序键。这时候如果用户查询条件中包含 where k1=1 and k2=2 就能通过shortkey索引加速查询。但是用户查询语句中使用条件k3=3, 则无法通过shortkey索引加速. 此时, 可创建以k3作为第一列的物化视图。
当然也有一些限制,分区列必须在Groupby中;不支持Key列聚合只支持Value列聚合;不支持指定物化视图查询;只支持单例聚合,不支持表达式(比如sum(a+b)) ;过多的物化视图,会影响导入数据的效率,比如有20张物化视图,则相当于导入20张表,但不影响查询性能,在有物化索引或物化视图的情况下,性能会更好;相同列,不同聚合函数,不能同时出现在一张物化视图中;物化视图不支持Join和where,不支持Groupby的Having子句;不能并行只能串行创建物化视图;
5)Bitmap索引:Bitmap索引的原理就是将RawData进行Dictionary的转提炼,然后基于Dictionary(Value、ID)的ID进行BitmapIndex(ID、Bitmap)的存储和查询。一般为0和1,如果是多值列,则需要转化为某值为0,其他值为1;
当然也有一些限制,对于聚合模型,只支持Key列Bitmap索引;适用于大量重复、较低基数的场景;不支持Float、Double、Decimal类型列建Bitmap索引;通过查询的Profile信息查看是否使用了索引;
6)Bitmap精确去重:重有2种,一个传统的是count distinct,优点是保留明细数据,缺点是消耗极大的计算和存储资源;一个是基于预计算,在用户不关心明细数据的情况下,采取预计算的方式去重,用空间换时间,效果不错,MOLAP的核心思路也是如此。
Doris使用Bitmap去重,原理即给定1个数组,其取值范围为[0,n](不包括n),对该数组去重,可采用(n+7)/8的字节长度的Bitmap,初始化为0;逐个处理数组元素,以数组中元素取值作为Bitmap的下标,将该下标的bit置为1;最后统计Bitmap中1的个数即为数组的count distinct的结果。
使用Bitmap去重的优势:空间优势,对于Int32的去重,只需要1/32空间,在Doris中,使用Roaring Bitmap存储空间会进一步降低。时间优势:基于Bitmap的操作比基于Sort和基于Hash的去重效率都要高,复杂度只有O(1)~O(n),并且无条件依赖和数据依赖,可向量化执行。
当然以有些限制,包括只能用于聚合表,明细表和更新表不支持Bitmap列;数据类型只能是BITMAP,聚合函数为BITMAP_UNION;在Bitmap列上使用count distinct,自动转换为BITMAP_UNION_COUNT计算。
7)Bloomfilter索引:用于判断某个元素是否在一个集合中的数据结构,优点是空间效率和时间效率都比较高,缺点是有一定的误判率,但不存集合中时,一定会报不存在。布隆过滤器由Bit数组和N个哈希函数构成,Bit数据组实始全为0,当一个元素插入时,则通过N个哈希函数进行计算n个Slot,然后将Bit数组中的n个Slot的Bit置1。当判断某一个值是否存在时,就通过N个哈希函数计算n个Slot,如果n个Slot对应的Bit位都为1,则集合存在,只要有一个Bit为0,则不存在。由于Bit数组位数有限,所以Bit位通过不同的哈希函数计算完成后,Bit是相同或冲突的,所以全1的情形也不一定真存在;与Bitmap的适用场景相反,适用用于高基数的场景,一般用于in条件的比较多,=条件也适用;
当然也有一些限制:不支持Tinyint、Float、Double 类型的列建Bloom Filter索引;只支持in和=过滤查询;通过查询的Profile信息查看是否使用了索引;
8)外部表:DorisDB支持以外部表的形式,接入其他数据源。外部表指的是保存在其他数据源中的数据表。目前DorisDB已支持的第三方数据源包括MySQL、HDFS、ElasticSearch,Hive。对这几种种数据源,现阶段只支持读取,还不支持写入。
9)Doris还支持数组、窗口函数、HyperLogLog去重、broadcast join、 Lateral Join等一些应用,在这里就不一一介绍了,可以看看官方资料。
4、数据的导入和输出
导入:
根据不同的数据来源可以选择不同的导入方式:
1)离线数据导入,如果数据源是Hive/HDFS,推荐采用Broker Load导入, 如果数据表很多导入比较麻烦可以考虑使用Hive外表直连查询,性能会比Broker load导入效果差,但是可以避免数据搬迁,如果单表的数据量特别大,或者需要做全局数据字典来精确去重可以考虑Spark Load导入。
2)实时数据导入,日志数据和业务数据库的binlog同步到Kafka以后,优先推荐通过Routine load 导入DorisDB,如果导入过程中有复杂的多表关联和ETL预处理可以使用Flink处理以后用stream load写入DorisDB,我们有标准的Flink-connector 可以方便Flink任务使用
3)程序写入DorisDB,推荐使用Stream Load,可以参考例子中有java python shell的demo,
4)文本文件导入推荐使用 Stream load
5)Mysql数据导入,推荐使用Mysql外表,insert into new_table select * from external_table 的方式导入
6)其他数据源导入,推荐使用DataX导入,我们提供了DataX-dorisdb-writer
7)DorisDB内部导入,可以在DorisDB内部使用insert into tablename select 的方式导入,可以跟外部调度器配合实现简单的ETL处理。
一个导入作业主要分为5个阶段:
1)PENDING
非必须。该阶段是指用户提交导入作业后,等待FE调度执行。
Broker Load和将来的Spark Load包括该步骤。
2)ETL
非必须。该阶段执行数据的预处理,包括清洗、分区、排序、聚合等。
Spark Load包括该步骤,它使用外部计算资源Spark完成ETL。
3)LOADING
该阶段先对数据进行清洗和转换,然后将数据发送给BE处理。当数据全部导入后,进入等待生效过程,此时导入作业状态依旧是LOADING。
4)FINISHED
在导入作业涉及的所有数据均生效后,作业的状态变成 FINISHED,FINISHED后导入的数据均可查询。FINISHED是导入作业的最终状态。
5)CANCELLED
在导入作业状态变为FINISHED之前,作业随时可能被取消并进入CANCELLED状态,如用户手动取消或导入出现错误等。CANCELLED也是导入作业的一种最终状态。
适用场景:
1)HDFS导入
源数据存储在HDFS中,数据量为几十GB到上百GB时,可采用Broker Load方法向DorisDB导入数据。此时要求部署的Broker进程可以访问HDFS数据源。导入数据的作业异步执行,用户可通过SHOW LOAD命令查看导入结果。
源数据存储在HDSF中,数据量达到TB级别时,可采用Spark Load方法向DorisDB导入数据。此时要求部署的Spark进程可以访问HDFS数据源。导入数据的作业异步执行,用户可通过SHOW LOAD命令查看导入结果。
对于其它外部数据源,只要Broker或Spark进程能读取对应数据源,也可采用Broker Load或Spark Load方法导入数据。
2)本地文件导入
数据存储在本地文件中,数据量小于10GB,可采用Stream Load方法将数据快速导入DorisDB系统。采用HTTP协议创建导入作业,作业同步执行,用户可通过HTTP请求的返回值判断导入是否成功。
3)Kafka导入
数据来自于Kafka等流式数据源,需要向DorisDB系统导入实时数据时,可采用Routine Load方法。用户通过MySQL协议创建例行导入作业,DorisDB持续不断地从Kafka中读取并导入数据。
4)Insert Into导入
手工测试及临时数据处理时可以使用Insert Into方法向DorisDB表中写入数据。其中,INSERT INTO tbl SELECT ...;语句是从 DorisDB 的表中读取数据并导入到另一张表;INSERT INTO tbl VALUES(...);语句向指定表里插入单条数据。
5)同时,还有其他一些方式Json数据导入(对于一些半结构化的比如Json类型的数据,我们可以用stream load 或者 routine load的方式进行导入。Stream Load: 对于文本文件存储的Json数据,我们可以使用 stream load进行导入。Routine Load:对于Kafka中的json格式数据,可以使用Routine load的方式导入)、flink-connector-dorisdb(内部实现是通过缓存并批量由stream load导入)、DataX-dorisdb-writer(DorisWriter 插件实现了写入数据到 DorisDB 的目的表的功能。在底层实现上, DorisWriter 通过Stream load以csv或 json 格式导入数据至DorisDB。内部将reader读取的数据进行缓存后批量导入至DorisDB,以提高写入性能。总体数据流是 source -> Reader -> DataX channel -> Writer -> DorisDB。)
输出:
数据导出(Export)是 DorisDB 提供的一种将数据导出并存储到其他介质上的功能。该功能可以将用户指定的表或分区的数据,以文本的格式,通过 Broker 进程导出到远端存储上,如 HDFS/阿里云OSS/AWS S3(或者兼容S3协议的对象存储) 等。
用户提交一个导出作业后,DorisDB 会统计这个作业涉及的所有 Tablet,然后对这些 Tablet 进行分组,每组生成一个特殊的查询计划。该查询计划会读取所包含的 Tablet 上的数据,然后通过 Broker 将数据写到远端存储指定的路径中。
处理流程主要包括:
1)用户提交一个 Export 作业到 FE。
2)FE 的导出调度器会通过两阶段来执行一个导出作业:
3)PENDING:FE 生成一个 ExportPendingTask,向 BE 发送 snapshot 命令,对所有涉及到的 Tablet 做一个快照,并生成多个查询计划。
4) EXPORTING:FE 生成一个 ExportExportingTask,开始执行一个个的查询计划。
Spark DorisDB Connector 可以支持通过 Spark 读取 DorisDB 中存储的数据。
1)当前版本只支持从DorisDB中读取数据。
2)可以将DorisDB表映射为DataFrame或者RDD,推荐使用DataFrame。
3)支持在DorisDB端完成数据过滤,减少数据传输量。
以上内容,大部分上是官方资料,中间增加了我的个人理解。总的来说,从存储机制、索引机制、运行机制、支撑场景等维度进行了简要说明,确实也能解决一部分的应用问题。
后续应用深入后,再分享实践相关的内容。