在架构方面,Doris只有两类进程:一类是FE,可以理解为Doris的管理节点,主要负责用户请求的接入、查询计划的解析、元数据的存储和集群管理相关工作;另一类是BE,主要负责数据存储、查询计划的执行。这两类进程都是可以横向扩展的。除此之外,Doris不依赖任何第三方系统(如HDFS、Zookeeper等)。这种高度集成的架构设计极大地降低了运维成本。
FE节点包含Leader、Follower和Observer三种角色。默认一个集群只能有一个Leader,可以有多个Follower和Observer。其中,Leader和Follower组成一个Paxos选择组,如果Leader宕机,剩下的Follower会自动选出新的Leader,保证写入高可用。Observer同步Leader的数据,但是不参加选举。如果只部署一个FE,则FE默认就是Leader。
FE节点主要包括存储管理(Store Manager)模块、状态管理(State Store)模块、协调(Coordinator)模块、元数据(StoreMeta)模块和元数据缓存(StoreMeta Cache)模块。存储管理模块负责管理所有的元数据信息,包括数据库、表信息、tablet信息、tablet的副本信息等。存储管理模块还负责管理用户的权限信息(即用户的认证信息和授权信息)和数据的导入任务等。状态管理模块负责管理所有BE进程的存活状态信息、查询负载信息等非持久化信息,并提供发布订阅接口。协调模块负责接收用户发来的请求,然后进行语句解析、生成执行规划,根据当前集群的状态,对执行规划进行调度。元数据模块负责对元数据的读写,只有FE Leader拥有此权限。元数据缓存模块负责同步元数据,以供语句解析、生成执行规划,主要是Follower和Observer角色的权限。
BE节点可以无限扩展,并且所有BE节点的角色都是对等的。在集群足够大的情况下,部分BE下线不影响集群提供服务。BE节点主要由存储引擎(Store Engine)和查询执行器(Query Executor)组成。存储引擎负责管理节点本地的tablet数据,发送或者接收数据形成副本,定期合并更新多个版本的数据减少存储占用。存储引擎还负责接收来自查询执行器的数据读取需求和批量数据导入需求。一个查询在MPP集群中执行时,会拆成一个树状的执行树,这棵树的执行由Coordinator来协调执行,树的叶子节点也叫计划片断(PlanFragment),每一个PlanFragment分配给一个BE节点的查询执行器来执行,这就是查询执行器模块的作用。
在数据建模方面,Doris支持Aggregate、Unique和Duplicate三种模型,可以满足OLAP领域的各种应用场景。同时,Doris建表语句,相对于MySQL只增加了一些分布式系统所具有的特性,比如分布键、分桶数等。有过分布式数据库使用经验的用户非常容易理解和上手操作。
在数据导入方面,Doris提供多种数据导入方案(如图2所示),可以针对不同的数据源进行选择,同时在数据导入过程中提供原子性保证。不论是使用Broker Load进行批量导入,还是使用INSERT语句进行单条导入,都是一个完整的事务操作。导入事务可以保证一批次内的数据原子生效,不会出现部分数据写入的情况。
数据导入流程如下图
在SQL开发方面,Doris支持标准的SQL语言,在方言方面向MySQL兼容。不论是简单的单表聚合、排序过滤操作,还是复杂的多表关联、子查询、窗口函数等,Doris都可以通过SQL轻松完成,极大地降低了用户的迁移和使用成本。Adhoc这类高吞吐的即席查询和库内ETL场景也是Doris的强项。Doris还能够支持复杂SQL语法,包括Grouping Set等高级语法功能,同时还可以通过UDF或UDAF来自定义拓展功能。在TB级别数据上,Doris可以部分代替Hive等离线系统的功能,使得用户在一套数据库中满足所有需求。
在工具方面,Doris在FE模块中实现了兼容MySQL协议,方便用户使用标准的MySQL客户端或各种语言的类库进行连接,对各种工具的支持都非常好。
在集群可靠性方面,Doris元数据使用内存存储+检查点+镜像日志文件的模式,Doris部署无外部依赖,只需要部署BE和FE模块即可搭建一个集群。Doris支持在线更改表模式(加减列、创建Rollup ),不会影响当前服务,不会阻塞读、写等操作,因为这种操作是异步执行的。
Doris的三个数据模型:
明细模型 Duplicate
主键模型 Unique
聚合模型 Aggregate
一张表包含行和列,行表示用户的一行数据,列则表示一行数据中不同的列
列则分为两类:Key 和 Value,Key列是建表语句中通过关键字unique key
或aggregate key
或duplicate key
指定的列,除了Key列,其它的列都是Value列
明细模型
明细模型适用于既没有主键又没有聚合需求的数据。
明细模型的特点:
-
默认模型,即在建表时没有指定Unique、Aggregate或Duplicate时,就会默认创建一个明细模型的表
-
Duplicate Key用来指明数据存储时按照哪些列进行排序,如果不指定则会自动按照一定规则选择排序列,排序列一般选择2-4列即可
-
如果没有排序需求,也可以通过
"enable_duplicate_without_keys_by_default" = "true"
配置表属性来取消排序 -
不会对数据进行聚合处理,即使两行数据完全一样也会全部保留
CREATE TABLE IF NOT EXISTS example_tbl_duplicate_without_keys_by_default
(
`timestamp` DATETIME NOT NULL COMMENT "日志时间",
`type` INT NOT NULL COMMENT "日志类型",
`error_code` INT COMMENT "错误码",
`error_msg` VARCHAR(1024) COMMENT "错误详细信息",
`op_id` BIGINT COMMENT "负责人id",
`op_time` DATETIME COMMENT "处理时间"
)
# 可以通过DUPLICATE KEY来指定排序列
# DUPLICATE KEY(`timestamp`, `type`, `error_code`)
DISTRIBUTED BY HASH(`type`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1",
# 可以通过以下配置来取消排序列
# "enable_duplicate_without_keys_by_default" = "true"
);
主键模型
主键模型适用于有更新需求的数据。主键模型能够保证Key(主键)的唯一性,当用户更新一条数据时,新写入的数据会覆盖具有相同Key(主键)的旧数据。
-
使用UNIQUE KEY指定主键,可以有多个
-
有两种方式:读时合并(merge-on-read)和写时合并(merge-on-write)。读时合并是在写入数据时不会触发任何数据去重的操作,而是在查询或compacton时进行。写时合并是在数据写入阶段就完成所有数据去重的工作。读时合并具有更好的写入性能,而写入合并具有更好的查询性能。新版本默认使用写时合并。
-
默认整行更新,也可以配置成部分列更新。
CREATE TABLE IF NOT EXISTS example_tbl_unique_merge_on_write
(
`user_id` LARGEINT NOT NULL COMMENT "用户id",
`username` VARCHAR(50) NOT NULL COMMENT "用户昵称",
`city` VARCHAR(20) COMMENT "用户所在城市",
`age` SMALLINT COMMENT "用户年龄",
`sex` TINYINT COMMENT "用户性别",
`phone` LARGEINT COMMENT "用户电话",
`address` VARCHAR(500) COMMENT "用户地址",
`register_time` DATETIME COMMENT "用户注册时间"
)
# 指定主键
UNIQUE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1",
# 新版本默认为写时合并,可以通过此配置改为读时合并
"enable_unique_key_merge_on_write" = "false"
);
聚合模型
聚合模型适用于有聚合需求的数据。
-
通过AGGREGATE KEY指定Key列(维度列),而设置了AggregationType的列则为Value列(指标列)。
-
写入数据时,对于Key列相同的行会聚合成一行,而Value列会按照设置的 AggregationType进行聚合
聚合方式和agg_state:
-
SUM
求和,多行的 Value 进行累加。
-
REPLACE
替代,下一批数据中的 Value 会替换之前导入过的行中的 Value。
-
MAX
保留最大值。
-
MIN
保留最小值。
-
REPLACE_IF_NOT_NULL
非空值替换。和 REPLACE 的区别在于对于 null 值,不做替换。
-
HLL_UNION
HLL 类型的列的聚合方式,通过 HyperLogLog 算法聚合。
-
BITMAP_UNION
BIMTAP 类型的列的聚合方式,进行位图的并集聚合。
CREATE TABLE IF NOT EXISTS example_tbl_agg1
(
`user_id` LARGEINT NOT NULL COMMENT "用户id",
`date` DATE NOT NULL COMMENT "数据灌入日期时间",
`city` VARCHAR(20) COMMENT "用户所在城市",
`age` SMALLINT COMMENT "用户年龄",
`sex` TINYINT COMMENT "用户性别",
`last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间",
`cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费",
`max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间",
`min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用户最小停留时间"
)
# 指定维度列
AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
Aggregate Key:Key列相同的记录,Value列按照指定的聚合类型进行聚合,要求所有未定义聚合类型的列都写入键清单。
Unique Key:Key列相同的记录,Value列按导入顺序进行覆盖,键清单为该表的主键字段(支持联合主键)。
Duplicate Key:Key列相同的记录,键清单一般为排序字段或者查询字段,主要用于索引优化。在不指定的情况下,Doris表默认为Duplicate Key类型,Key列为列定义中前36 B,如果前36 B的列数小于3,将使用前三列。
再次强调,在建表时,除Aggregate Key外,Value列不需要指定聚合类型。