Apache Druid

基本概念:

概述:

Metamarkets 公司(一家为在线媒体或广告公司提供数据分析服务的公司)推出的一个分布式内存实时分析系统,用于解决如何在大规模数据集下进行快速的、交互式的查询和分析。
Druid 是一个开源的数据分析引擎工具,为实时和历史数据的次秒级(多于一秒)查询设计。主要应用于对数据的OLAP查询,Druid 提供低延迟(实时)的数据摄取、灵活的数据探索、快速的数据聚合。现有的 Druid 部署已支持扩展到数万亿时间和 PB 级数据

对比kylin:

同样是cube预计算
支持流式更灵活
Kylin 利用 Hadoop/HBase 做计算和存储,使用 SQL 查询,提供 JDBC/ODBC 驱动与常见 BI 工具集成
Druid 有自己独立的分布式集群,能够实时摄入数据,有自己的查询接口(与BI兼容性较弱),通常多用于实时要求高的场景

核心特点:

Apache Druid是一个开源的、分布式、实时OLAP分析工具。Druid的核心设计结合了数据仓库、时间序列数据库和搜索系统的思想,适用于多种场景的高性能数据实时分析。
Druid将这三个系统中的每个系统的关键特征合并到其接收层、存储格式、查询层和核心体系结构中。
列式存储
    Druid 独立的存储和压缩每一列,只需要读取特定查询所需的内容,这可以支持快速扫描、排名和聚合
流式和批量摄取(Ingestion)
    支持 Apache Kafka、HDFS、AWS S3、stream processors 等现成连接器
本地的搜索索引
    Druid 为字符串创建倒排索引,以支持快速搜索和排序
灵活的 schema
    Druid 可以处理变化的 schema 和嵌套数据
基于时间优化 partition
    Druid 基于时间智能的对数据进行分区,基于时间的查询比传统数据库要快得多
支持 SQL
    Druid 支持本机的 JSON 语言,还支持基于 HTTP 或者 JDBC 的 SQL
水平扩展性
    Druid 已经用户生产环境中,每秒接收数百万个事件,保存多年的数据并提供次秒级查询
操作简单
    只需要增加或删除服务器即可扩展或缩小规模,Druid 会自动平衡,容错架构通过服务器的故障进行路由

Ingestion(摄取):

Druid支持流式传输和批量摄取。Druid连接到数据源,包括:Kafka(用于流数据加载),或分布式文件系统,如HDFS(用于批处理数据加载)。
Druid在 “索引” 过程中将数据源中的原始数据转换为支持高效读取的优化格式(Segment,段)。

存储:

Druid的数据存储采用列式存储格式。根据列的类型(字符串,数字等),应用不同的压缩和编码方法,根据列类型构建不同类型的索引。
Druid为字符串列构建倒排索引,以进行快速搜索和过滤。Druid可按时间对数据进行智能分区,以实现面向时间的快速查询。
Druid在摄取数据时对数据进行预聚合,节省大量存储空间。

优点:

对于大部分查询场景可以亚秒级响应
事件流实时写入与批量数据导入兼备
数据写入前预聚合节省存储空间,提升查询效率
水平扩容能力强
社区活跃

适合场景:

处理时间序列事件
快速的聚合以及探索式分析
近实时分析亚秒级响应
存储大量(TB级、PB级)可以预先定义若干维度的事件
无单点问题的数据存储

架构:

服务:

Master:Coordinator & Overload 进程,管理数据可用性和数据摄取
Data:Historical & MiddleManager,执行提取工作负载并存储所有可查询数据
Query:Broker & Router,处理来自外部客户端的查询

外部依赖:

Deep Storage:深度存储,例如HDFS或者S3。不是用来存储查询数据的。而是作为数据的备份或者进程间数据交换。所以需在HADOOP集群内。
Metadata Storage:元数据存储,可以用RDBMS。
ZooKeeper:服务发现、leader选举、服务协调。

运维部署:

下载:

cd /opt/lagou/software
wget http://apache.communilink.net/druid/0.19.0/apache-druid-0.19.0-bin.tar.gz
tar -zxvf apache-druid-0.19.0-bin.tar.gz

Nano-Quickstart:1个CPU,4GB RAM
    启动命令: bin/start-nano-quickstart
    配置目录: conf/druid/single-server/nano-quickstart/*
微型快速入门:4个CPU,16GB RAM
    启动命令: bin/start-micro-quickstart
    配置目录: conf/druid/single-server/micro-quickstart/*
小型:8 CPU,64GB RAM(〜i3.2xlarge)
    启动命令: bin/start-small
    配置目录: conf/druid/single-server/small/*
中:16 CPU,128GB RAM(〜i3.4xlarge)
    启动命令: bin/start-medium
    配置目录: conf/druid/single-server/medium/*
大型:32 CPU,256GB RAM(〜i3.8xlarge)
    启动命令: bin/start-large
    配置目录: conf/druid/single-server/large/*
大型X:64 CPU,512GB RAM(〜i3.16xlarge)
    启动命令: bin/start-xlarge
    配置目录: conf/druid/single-server/xlarge/*

访问:登录http://linux121:8888/查看页面

架构说明:

主节点部署 Coordinator 和 Overlord进程
数据节点运行 Historical 和 MiddleManager进程
查询节点 部署 Broker 和 Router 进程

安装流程:

设置环境变量
MySQL中创建相关数据库
配置Druid参数
    将hadoop配置文件core-site.xml、hdfs-site.xml、yarn-site.xml、mapred-site.xml链接到conf/druid/cluster/_common/ 下
将MySQL的驱动程序,链接到 $DRUID_HOME/extensions/mysql-metadata-storage/ 下
修改配置文件($DRUID_HOME/conf/druid/cluster/_common/common.runtime.properties)
    配置ip、metadata等信息
配置主节点文件(参数大小根据实际情况配置)
    $DRUID_HOME/conf/druid/cluster/master/coordinator-overlord/jvm.config
配置数据节点文件(参数大小根据实际情况配置)
    $DRUID_HOME/conf/druid/cluster/data/historical/jvm.config
    $DRUID_HOME/conf/druid/cluster/data/historical/runtime.properties
    $DRUID_HOME/conf/druid/cluster/data/middleManager/jvm.config
配置查询节点文件(参数大小根据实际情况配置)
    $DRUID_HOME/conf/druid/cluster/query/broker/jvm.config
    $DRUID_HOME/conf/druid/cluster/query/broker/runtime.properties
    $DRUID_HOME/conf/druid/cluster/query/router/jvm.config
启动:
    在主节点上执行nohup start-cluster-master-no-zk-server &
    在数据节点上执行nohup start-cluster-data-server &
    在查询节点上执行nohup start-cluster-query-server &

日志路径:
    /data/druid_master/apache-druid-0.17.0/var/sv
    页面点击具体task也能看log

数据存储:

概述:

Druid中的数据存储在被称为DataSource中,DataSource类似RDBMS中的 Table
每个DataSource按照时间划分,每个时间范围称为一个Chunk(比如按天分区,则一个chunk为一天)
在Chunk中数据被分为一个或多个Segment
Segment是数据实际存储结构,Datasource、Chunk只是一个逻辑概念
Segment是按照时间组织成的Chunk,所以在按照时间查询数据时,效率非常高
每个Segment都是一个单独的文件,通常包含几百万行数据

数据分区:

Druid处理的是事件数据,每条数据都会带有一个时间戳,可以使用时间进行分区
上图指定了分区粒度为为天,那么每天的数据都会被单独存储和查询

Segment内部存储结构:

Druid采用列式存储,每列数据都是在独立的结构中存储
Segment中的数据类型主要分为三种
时间戳。每一行数据,都必须有一个timestamp,Druid一定会基于时间戳来分片
维度列。用来过滤filter或者组合groupby的列,通常是stringfloatdoubleint类型
指标列。用来进行聚合计算的列,指定的聚合函数 sum、average 等

segment状态:

is_published:如果 segment 元数据已发布到存储的元数据中,used则为 true,此值也为 true
is_available:如果该 segment 当前可用于实时任务或Historical查询,则为 True。
is_realtime:如果 segment 在实时任务上可用,则为 true 。对于使用实时写入的数据源,通常会先设置成true,然后随着 segment 的发布和移交而变成false
            实时流Task kill掉后,会转移为is_published状态,写入metadata表。
is_overshadowed:如果该 segment 已发布(used设置为 true)并且被其他一些已发布的 segment 完全覆盖,则为 true。通常,这是一个过渡状态,处于此状态的 segment 很快就会将其used标志自动设置为 false

索引服务:

概述:

数据导入并创建 segments 数据文件的服务

架构:

overlord 作为主节点
    负责创建task、分发task到middlemanager上运行,为task创建锁以及跟踪task运行状态并反馈给用户
middlemanager是从节点
    作为从节点,负责接收主节点分配的任务,然后为每个task启动一个独立的JVM进程来完成具体的任务
peon用于运行一个task
    由middlemanager启动的一个进程用于运行一个task任务

Task类型:

index hadoop task:Hadoop索引任务,利用Hadoop集群执行MapReduce任务以完成segment数据文件的创建,适合体量比较大的segments数据文件的创建任务
index kafka task:用于Kafka数据的实时摄入,通过Kafka索引服务可以在Overlord上配置一个KafkaSupervisor,通过管理Kafka索引任务的创建和生命周期来完成 Kafka 数据的摄取
merge task:合并索引任务,将多个segments数据文件按照指定的聚合方法合并为一个segments数据文件
kill task : 销毁索引任务,将执行时间范围内的数据从Druid集群的深度存储中删除

索引及压缩机制:

查询时延低性能好的原因:

数据预聚合
列式存储、数据压缩
Bitmap 索引
mmap(内存文件映射方式)
查询结果的中间缓存

数据预聚合:

Druid通过一个roll-up的处理,将原始数据在注入的时候就进行汇总处理
Roll-up可以压缩我们需要保存的数据量
Druid会把选定的相同维度的数据进行聚合操作,可减少存储的大小
Druid可以通过 queryGranularity 来控制注入数据的粒度。 最小的queryGranularity 是 millisecond(毫秒级)

位图索引:

通过建立位图索引,实现快速数据查找。
Bitmap 索引主要为了加速查询时有条件过滤的场景。Druid 在生成索引文件的时候,对每个列的每个取值生成对应的 Bitmap 集合。
结构:
    索引位图可以看作是HashMap<String, Bitmap>
    key就是维度的取值
    value就是该表中对应的行是否有该维度的值
执行过程分析:
    根据时间段定位到segment
    Appkey in ('appkey1''appkey2'and area='北京' 查到各自的bitmap,取出对应的行数,取交集或者并集

操作:

DML操作:

页面进行,删除datasource需要先删或者暂停ingestion
关于diamession和metric
diamession理解为group by的字段
metric理解为sum(字段),count(字段)这种,包含了聚合函数+字段。

新建流程:

1.start

2.Connect。注意kafka topic没有消息会卡住

3.parse data 可以查看topic的数据

4.Parse Time

可以选择格式。auto或者指定的datetime format

5.transform

添加diamession,比如新列为 column1 + column2 拼接起来。

6.filter

7.configure Schema

Query granularity可以指定时间维度保留的粒度。如时、分、秒。

8.partition分区

根据数据量来定义

9.tune

调整task数量,读取时间范围等

10.publish

11.edit spec

API介绍:

数据导入:

bin/post-index-task --file quickstart/tutorial/retention-index.json --url http://localhost:8081

数据保留/丢弃:

点击Cluster default: loadForever旁边的小按钮,配置dropforever

更新/覆盖/追加数据:

bin/post-index-task --file quickstart/tutorial/updates-overwrite-index.json --url http://localhost:8081
bin/post-index-task --file quickstart/tutorial/updates-append-index.json --url http://localhost:8081
bin/post-index-task --file quickstart/tutorial/updates-append-index2.json --url http://localhost:8081

删除数据:

先禁用指定interval的segments,标记为unused
    curl -X 'POST' -H 'Content-Type:application/json' -d '{ "interval" : "2015-09-12T18:00:00.000Z/2015-09-12T20:00:00.000Z" }' http://localhost:8081/druid/coordinator/v1/datasources/deletion-tutorial/markUnused
禁用所有的segments:
    DELETE请求 /druid/coordinator/v1/datasources/{dataSourceName}
删除segments:
    curl -X 'POST' -H 'Content-Type:application/json' -d @quickstart/tutorial/deletion-kill.json http://localhost:8081/druid/indexer/v1/task
    请求体:
        {
            "type""kill",
            "id": <task_id>,
            "dataSource": <task_datasource>,
            "interval" : <all_segments_in_this_interval_will_die!>,
            "markAsUnused": <true|false>,       // 如果为true,会先将interval标记为unused,然后再删除。
            "context": <task context>
        }

metadata相关表介绍:

druid_segments   存放is_published状态的segment,其他状态暂无记录,可能存放于其他组件。
druid_pendingSegments   每次创建segment(初始状态)的历史表

生产环境排查:

1. 更改同一个ingestion的interval,会导致新的segment创建失败,报错为org.apache.druid.java.util.common.ISE: Could not allocate segment for row with timestamp

到overlord机器上查看log,发现报了segment conflict冲突。旧的interval貌似影响了新的interval。
解决:
    暂时将interval改回去。

2.datasource删除失败,即使先停掉任务

实时节点(MiddleManager):即时摄入实时数据,以及生成Segment数据文件 实时节点负责消费实时数据,实时数据首先会被直接加载进实时节点内存中的堆结构缓存区,当条件满足时, 缓存区的数据会被冲写到硬盘上形成一个数据块(Segment Split),同时实时节点又会立即将新生成的数据库加载到内存的非堆区, 因此无论是堆结构缓存区还是非堆区里的数据都能被查询节点(Broker Node)查询 
历史节点(Historical Node):加载已经生成好的文件,以供数据查询 
查询节点(Broker Node):对外提供数据查询服务,并同时从实时节点和历史节点查询数据,合并后返回给调用方 
协调节点(Coordinator Node):负责历史节点的数据负载均衡,以及通过规则(Rule)管理数据的生命周期

原因:
    is_realtime状态的segment不能删除,位于historycal缓存中(未验证)
    is_realtime的segment在一定时间或者暂停supervisor后,状态会转移为is_published,写入metadata中,才能删除。
解决方法:
    重命名datasource 或者等待空闲时段再试试。

3.如何实现删除旧数据?

1.请求删除相关api。
    注意realtime状态的segment需要先暂停supervisor任务,等待所有的Segement从realtime变为publish即可。
    (暂停任务后,dataSource被disable,此时segement会短暂不可用,重新enable dataSource之后or等待一段时间自动load后,segement才变为publish状态。)
2.使用data retention规则(失败)
    先定义loadByInterval,再定义dropForever即可
    注意:
        realtime状态的segment要先转换publish才行。
        __time字段为写入时间,而不是业务时间。这样才能过滤旧版本的全量数据。(问题:pivot展示失败,__time字段必选,而diamession类型没有Date,所以不能作为Time类型。)
            overlap must have matching types (are SET/STRINGSET/TIME_RANGE)
    脚本示例:
        curl -X 'POST' -H 'Content-Type:application/json' -d '[{"type":"loadByInterval","tieredReplicants":{"_default_tier":1},"interval":"2010-01-01/2020-01-01"}]' http://10.0.0.48:18888/druid/coordinator/v1/rules/${dataSourceName}
3.每天数据一个版本,pivot展示的时候通过version_date来筛选
    注意字段类型得为数字,因为TIME类型不可选,而String会报错:  max must have expression of type NUMBER or TIME (is STRING)
    `insert_timestamp` UInt64
    然后pivot筛选
        $main.filter($insert_timestamp==$main.max($insert_timestamp)).sum($sum_arpu)
    存在问题:
        $insert_timestamp==$main.max($insert_timestamp)pivot会加不了filter。
    解决:
        insert kafka表前,先用一个物理表保存数据,然后T-1的时候再发送相同diamession,负metric的过去抵消。
        or 
        每天定时脚本sed更新config.yaml里面写死的字段值。(在生产上使用过)

4.supervisors或者task删除返回500,

页面点击task log提示Duplicate entry 'partner-partition_2021-06-14T00:00:00.000Z_2021-06-15T00:00:00.0' for key 'PRIMARY' [statement:\"INSERT INTO druid_pendingSegments
解决:
    登录数据库,执行delete from druid_pendingSegments where dataSource='partner-partition';

pivot可视化:

概述:

开源版本turnilo
文档地址:https://allegro.github.io/turnilo/configuration-cluster.html

使用:

turnilo --druid http://10.0.0.16:8082 --port 9099  --print-config --with-comments > config_v10.yaml
可以修改配置,比如汉化,diamession字段等

启动:

turnilo --config config.yaml

停止:

kill 

只显示部分cube:

nginx配置:

location /sources/ {
        proxy_pass http://10.0.0.16:9099/sources/;
        proxy_set_header 'x-turnilo-allow-datacubes' '*';      # 控制显示的datasource
        proxy_set_header Host $proxy_host# 修改转发请求头,让8080端口的应用可以受到真实的请求
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    location /pivot/ {
            proxy_pass http://10.0.0.16:9099/;
            auth_basic "Please input password"#这里是验证时的提示信息
            auth_basic_user_file /usr/local/nginx/passwd;
            proxy_set_header a a;
            proxy_set_header 'x-turnilo-allow-datacubes' '*,agent_sales_stats_v1';
            proxy_set_header Host $proxy_host# 修改转发请求头,让8080端口的应用可以受到真实的请求
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
        location /fonts/Open-Sans-regular/ {
            proxy_pass http://10.0.0.16:9099/fonts/Open-Sans-regular/;
            proxy_set_header Host $proxy_host# 修改转发请求头,让8080端口的应用可以受到真实的请求
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

config.yaml配置:

clusters:

  - name: druid
    guardDataCubes: true

支持选择多个split:

dataCubes:

- name: globalsearch_and_launcher_pv_uv_for_olap
  maxSplits: 10

默认选择metric:

defaultSelectedMeasures: [a,b]

metric format配置:

0.44444 0.00a 0.45

0.43 '(0.000 %)' 43.000 %

1230974 '($ 0.00 a)' $ 1.23 m

1234 0,0 1,234

参考http://numeraljs.com/#format

metric常用函数:

1.sum($field)

2.count()

3.countDistinct($field)        // druid提供count(distinct xxx),但是不准确,基于hyperlog来实现的。

4.filter(condition)

5.average($field)

生产问题:

1.如果有多个值的时候,需要multiValue: true。一个值的时候,则需要为false,否则pivot的类型有问题,显示为null
    - name: __time
          title: 归属时间
          kind: time
          formula: $__time

2.数据去重问题:

        场景:字段修改,需要count总数

解决方法:

            a.可以根据binlog对于update或者delete操作,会发一条旧数据,clickhouse引擎标记sign-1。所以sum(sign)即可,druid会对metric相同的数据进行预聚合(启用rollup)。
            b.如果不是binlog,可以考虑T-1离线更新。
            c.可以添加一层去重层,物化视图基于这一层,insert select的时候判断数据是否重复。
            d.如果不是严格要求,可以用hyper Unique聚合。

3.实现countif/count的效果

        $main.filter($statusCode == 500).sum($requests) / $main.sum($requests)

    4.metric获取_time以外的数据,比如历史全量数据。
        暂时没有方法。
        暂时做法:将全量数据每个左表时刻都打进去,但只有对应记录的状态才为正确,否则为未标记。

5.metric不能使用concat,只能先定义diamession

    6.long类型不能搜索(只能范围min-max),而string类型可以(背后调用contains函数)。
        所以druid定义schema的时候推荐都用string类型。
posted @ 2022-02-23 20:06  心平万物顺  阅读(931)  评论(0编辑  收藏  举报