一、doris是什么

Apache Doris是一个现代化的MPP(大规模并行分析)分析型数据库产品。仅需亚秒级响应时间即可获得查询结果,有效地支持实时数据分析。Apache Doris的分布式架构非常简洁,易于运维,并且可以支持10PB以上的超大数据集。
Apache Doris可以满足多种数据分析需求,例如固定历史报表,实时数据分析,交互式数据分析和探索式数据分析等。令数据分析工作更加简单高效!

二、数据模型

Aggregate(聚合模型,提前聚合数据, 适合报表和多维分析业务)


  • SUM:求和,多行的 Value 进行累加。
  • REPLACE:替代,下一批数据中的 Value 会替换之前导入过的行中的 Value。
  • MAX:保留最大值。
  • MIN:保留最小值。


    CREATE TABLE IF NOT EXISTS example_db.expamle_tbl
    (
        `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`, `timestamp`, `city`, `age`, `sex`)
    ... /* 省略 Partition 和 Distribution 信息 */
    ;


     

     

    user_id

    date

    city

    age

    sex

    last_visit_date

    cost

    max_dwell_time

    min_dwell_time

    10000

    2017-10-01

    北京

    20

    0

    2017-10-01 06:00:00

    20

    10

    10

    10000

    2017-10-01

    北京

    20

    0

    2017-10-01 07:00:00

    15

    2

    2

    在导入上述数据时,最终表里面就只有一行数据:

    user_id

    date

    city

    age

    sex

    last_visit_date

    cost

    max_dwell_time

    min_dwell_time

    10000

    2017-10-01

    北京

    20

    0

    2017-10-01 07:00:00

    35

    10

    2

    其中user_id, date, city age,sex 因为没有聚合模型,因此只有当他们都一样时,才可以发生后面的聚合;假设city不一样就不能聚合;

  • 如果业务场景就是需要存入明细,那么一般的做法是列增加时间戳,这样每一列都不一样,就可以保存数据明细;
  • 数据在不同时间,可能聚合的程度不一致。比如一批数据刚导入时,可能还未与之前已存在的数据进行聚合。但是对于用户而言,用户只能查询到聚合后的数据。即不同的聚合程度对于用户查询而言是透明的。用户需始终认为数据以最终的完成的聚合程度存在,而不应假设某些聚合还未发生。

Uniq模型(保证 Key 的唯一性,适用于有更新需求的分析业务。)


  • 其实是Aggregate聚合模型的一个特例,即只有aggregate key 是唯一的,其他列都是replace特性

  • CREATE TABLE IF NOT EXISTS example_db.expamle_tbl
    (
        `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`, `user_name`)


     

    等同于:


  • CREATE TABLE IF NOT EXISTS example_db.expamle_tbl
    (
        `user_id` LARGEINT NOT NULL COMMENT "用户id",
        `username` VARCHAR(50) NOT NULL COMMENT "用户昵称",
        `city` VARCHAR(20) REPLACE COMMENT "用户所在城市",
        `age` SMALLINT REPLACE COMMENT "用户年龄",
        `sex` TINYINT REPLACE COMMENT "用户性别",
        `phone` LARGEINT REPLACE COMMENT "用户电话",
        `address` VARCHAR(500) REPLACE COMMENT "用户地址",
        `register_time` DATETIME REPLACE COMMENT "用户注册时间"
    )
    AGGREGATE KEY(`user_id`, `user_name`)


     

Duplicate模型(只指定排序列,相同的行不会合并。适用于数据无需提前聚合的分析业务。)

CREATE TABLE IF NOT EXISTS example_db.expamle_tbl
(
    `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(`timestamp`, `type`)

   

三、ROLLUP(上面的每个模型产生的其实是一个base表,我们需要在base表的基础上建立其他表,以便于提高查询效率)

Rollup 本质上可以理解为原始表(Base Table)的一个物化索引。建立 Rollup 时可只选取 Base Table 中的部分列作为 Schema。Schema 中的字段顺序也可与 Base Table 不同

表存储数据如下:

user_id

date

timestamp

city

age

sex

last_visit_date

cost

max_dwell_time

min_dwell_time

10000

2017-10-01

2017-10-01 08:00:05

北京

20

0

2017-10-01 06:00:00

20

10

10

10000

2017-10-01

2017-10-01 09:00:05

北京

20

0

2017-10-01 07:00:00

15

2

2

10001

2017-10-01

2017-10-01 18:12:10

北京

30

1

2017-10-01 17:05:45

2

22

22

在此基础上,我们创建一个 ROLLUP:

ColumnName

user_id

cost

创建完成后,表里面的数据为

user_id

cost

10000

35

10001

2

这个时候我们如果查询SELECT user_id, sum(cost) FROM table GROUP BY user_id; 则doris会自动命中这个 ROLLUP 表,从而只需扫描极少的数据量,即可完成这次聚合查询;

rollup重要特点:

· ROLLUP 是附属于 Base 表的,可以看做是 Base 表的一种辅助数据结构。用户可以在 Base 表的基础上,创建或删除 ROLLUP,但是不能在查询中显式的指定查询某 ROLLUP。是否命中 ROLLUP 完全由 Doris 系统自动决定。 · ROLLUP 的数据是独立物理存储的。因此,创建的 ROLLUP 越多,占用的磁盘空间也就越大。同时对导入速度也会有影响,但是不会降低查询效率; · ROLLUP 的数据更新与 Base 表示完全同步的; · 查询能否命中 ROLLUP 的一个必要条件(非充分条件)是,查询所涉及的所有列(包括 select list 和 where 中的查询条件列等)都存在于该 ROLLUP 的列中。否则,查询只能命中 Base 表。

  

四、join操作(hash join,broadcast join,shuffle join )

Join是数据库查询永远绕不开的话题,传统查询SQL技术总体可以分为简单操作(过滤操作-where、排序操作-limit等),聚合操作-groupBy等以及Join操作等。其中Join操作是其中最复杂、代价最大的操作类型

传统数据库单机模式做Join的场景毕竟有限,也建议尽量减少使用Join。然而大数据领域就完全不同,Join是标配,OLAP业务根本无法离开表与表之间的关联,对Join的支持成熟度一定程度上决定了系统的性能,夸张点说,“得Join者得天下”

Doris会自动尝试进行 Broadcast Join,如果预估小表过大则会自动切换至 Shuffle Join。注意,如果此时显式指定了 Broadcast Join 也会自动切换至 Shuffle Join。

  • hash join算法就来自于传统数据库,而shuffle和broadcast是大数据的皮,两者一结合就成了大数据的算法了
  1. hash join: 适用于至少有一个是小表的场景(hash join基本都只扫描两表一次,可以认为O(a+b),较之最极端的是笛卡尔积运算O(a*b), 小表的原因是在构建Hash Table时,最好可以把数据全部加载到内存中,因为这样效率才最高)
  1. 两个表,取小表为Build Table, 大表为Probe Table
  2. 构建Hash Table:依次读取Build Table的数据,对于每一条数据根据Join Key进行hash,hash到对应的bucket中(类似于HashMap的原理),最后会生成一张HashTable,HashTable会缓存在内存中,如果内存放不下会dump到磁盘中;
  3. 匹配:生成Hash Table后,在依次扫描Probe Table的数据,使用相同的hash函数(在spark中,实际上就是要使用相同的partitioner)在Hash Table中寻找hash(join key)相同的值,如果匹配成功就将两者join在一起。
  1. broadcast join(将其中一张较小的表通过广播的方式,由driver发送到各个executor,大表正常被分成多个区,每个分区的数据和本地的广播变量进行join(相当于每个executor上都有一份小表的数据,并且这份数据是在内存中的,过来的分区中的数据和这份数据进行join)。broadcast适用于表很小,可以直接被广播的场景;)
  1. 基表不能被广播,比如left outer join时,只能广播右表
  1. shuffle join(一旦小表比较大,此时就不适合使用broadcast hash join了。这种情况下,可以对两张表分别进行shuffle,将两张表相同key的数据分到一个分区中,然后分区和分区之间进行join。相当于将两张表都分成了若干小份,小份和小份之间进行hash join,充分利用集群资源。)

五、分区& 分桶

Doris 支持两级分区存储, 第一层为 RANGE 分区(partition), 第二层为 HASH 分桶(bucket)

  1. RANGE分区用于将数据划分成不同区间, 逻辑上可以理解为将原始表划分成了多个子表。业务上,多数用户会选择采用按时间进行partition, 让时间进行partition有以下好处:* 可区分冷热数据 
    * 可用上Doris分级存储(SSD + SATA)的功能 
    * 按分区删除数据时,更加迅速
根据hash值将数据划分成不同的 bucket。
* 建议采用区分度大的列做分桶, 避免出现数据倾斜
* 为方便数据恢复, 建议单个 bucket 的 size 不要太大, 保持在 10GB 以内, 所以建表或增加 partition 时请合理考虑 bucket 数目, 其中不同 partition 可指定不同的 buckets 数。

多字段的分区键比较是基于数组的比较。它先用插入的数据的第一个字段值和分区的第一个值进行比较,如果插入的第一个值小于分区的第一个值那么就不需要比较第二个值就属于该分区;如果第一个值等于分区的第一个值,开始比较第二个值同样如果第二个值小于分区的第二个值那么就属于该分区。

 

六、数仓分层

 doris库的表是有几种不同前缀的, 每一种前缀其实是代表了不同的数据分层;

  • ods层:最原始的数据,比如日志, 数据保持原有数据格式不变;
  • dwd层:对ods层的数据进行清洗,比如去除掉一些脏数据,空值等;
  • dws层:在dwd层的基础上,进行聚合,形成宽表,比如续报信息,会有新报、先报、续报等信息;宽表:一张表会涵盖比较多的业务内容,由于其字段较多,因此一般也会称该层的表为宽表,宽表缺点是数据会有大量冗余,而且生成相对比较滞后,查询结果可能并不及时。
  • Schema 中字段数比较多, 聚合模型中可能 key 列比较多, 导入过程中需要排序的列会增加。
  • 维度信息更新会反应到整张表中,而更新的频率直接影响查询的效率。
  • ads层:为各种统计报表提供数据;比如ads_ws_renew就是提供续报的相关数据;
  • dim:维表, 比如dim_course_info就是课程信息;维表分为:高基数维度数据:一般是用户资料表、商品资料表类似的资料表,数据量可能是千万级或者上亿级别;低基数维度数据:一般是配置表,比如枚举值对应的中文含义,或者日期维表。数据量可能是个位数或者几千几万