druid有三种类型的数据结构: timestamp列,维度列,指标列. 时间撮和指标在底层都是int数组或long数组. 指标值是int或long,而时间撮为long. Segment文件的内部结构可以看做是列式存储. 每一列的数据都是以不同的数据结果存储. 通过列式存储,查询时只查询需要的列可以减少延迟. 因为列式存储,要保存的是某一列的所有行. 所以数组的每一个元素表示的是每一行的这一列的值. 列式存储还有一个好处是压缩,当查询确定要查询哪些行之后, 首先解压缩(因为存储的时候会压缩),然后直接取出相关的行(定位到数组的指定位置),并运用聚合算子计算指标

目录

  • 数据集
  • 数据结构
  • segment

数据集


  • Druid中的数据表(称为数据源)是一个时间序列事件数据的集合,并分割到一组segment中,而每一个segment通常是0.5-1千万行。
  • 在形式上,我们定义一个segment为跨越一段时间的数据行的集合。Segment是Druid里面的基本存储单元,复制和分布都是在segment基础之上进行的
  • Druid将数据集分成三种类型: Timestamp column, Dimension columns(过滤数据), Metric columns(聚合和计算)
    1. 事件的定义: 时间戳
    2. Dimensions: (things to filter on) 参与事件过滤
    3. Metrics: (things to aggregate over) 要聚合的字段
  • 以下面的事件为例,Druid会将publisher,advertiser,gender,country当做维度列,click和price当做指标列,如下:

    timestamp             publisher          advertiser  gender  country  click  price
    2011-01-01T01:01:35Z bieberfever.com google.com Male USA 0 0.65
    2011-01-01T01:03:63Z bieberfever.com google.com Male USA 0 0.62
    2011-01-01T01:04:51Z bieberfever.com google.com Male USA 1 0.45
    2011-01-01T01:00:00Z ultratrimfast.com google.com Female UK 0 0.87
    2011-01-01T02:00:00Z ultratrimfast.com google.com Female UK 0 0.99
    2011-01-01T02:00:00Z ultratrimfast.com google.com Female UK 1 1.53
  • ruid读取数据的入口并不会直接存储原始数据, 而是使用Roll-up这种first-level聚合操作压缩原始数据,如下:
    timestamp             publisher          advertiser  gender country impressions clicks revenue
    2011-01-01T01:00:00Z ultratrimfast.com google.com Male USA 1800 25 15.70
    2011-01-01T01:00:00Z bieberfever.com google.com Male USA 2912 42 29.18
    2011-01-01T02:00:00Z ultratrimfast.com google.com Male UK 1953 17 17.31
    2011-01-01T02:00:00Z bieberfever.com google.com Male UK 3194 170 34.01
  • 用SQL表示类似于对时间撮和所有维度列进行分组,并以原始的指标列做常用的聚合操作

    GROUP BY timestamp, publisher, advertiser, gender, country
    :: impressions = COUNT(1), clicks = SUM(click), revenue = SUM(price)
  • 为什么不存原始数据? 因为原始数据量可能非常大,对于广告的场景,一秒钟的点击数是以千万计数. 如果能够在读取数据的同时就进行一点聚合运算,就可以大大减少数据量的存储.这种方式的缺点是不能查询单条事件,也就是你无法查到每条事件具体的click和price值了.由于后面的查询都将以上面的查询为基础,所以Roll-up的结果一定要能满足查询的需求.通常count和sum就足够了,因此Rollup的粒度是你能查询的数据的最小时间单位. 假设每隔1秒Rollup一次,后面的查询你最小只能以一秒为单位,不能查询一毫秒的事件.默认的粒度单位是ms.


  • Druid的分片是Segment文件. Druid首先总是以时间撮进行分片, 因为事件数据总是有时间撮. 假设以小时为粒度创建下面的两个Segment文件
  • 在几乎所有的NoSQL中都有数据分片的概念,比如ES的分片,HBase的Region,都表示的是数据的存储介质.为什么要进行分片,因为数据大了,不能都存成一个大文件吧,所以要拆分成小文件以便于快速查询. 伴随拆分通常都有合并小文件
  • 从Segment文件的名称可以看出它包含的数据一定是在文件名称对应的起始和结束时间间隔之内的
  • Segment文件名称的格式:dataSource_interval_version_partitionNumber.最后一个分区号是当同一个时间撮下数据量超过阈值要分成多个分区了
    1. 分片和分区都表示将数据进行切分. 分片是将不同时间撮分布在不同的文件中, 而分区是相同时间撮放不下了,分成多个分区
    2. 巧合的是Kafka中也有Segment和Partition的概念.Kafka的Partition是topic物理上的分组,一个topic可以分为多个partition,它的partition物理上由多个segment组成.即Partition包含Segment,而Druid是Segment包含Partition.

数据结构


  • 维度列因为要支持过滤和分组,每一个维度列的数据结构包含了三部分:
    1. 值到ID的Map映射
    2. 列的值列表, 存储的是上一步对应的ID
    3. 倒排索引
  • 示例进行说明:
  • 代表这一维度列的数据结构如下:
    1: Dictionary that encodes column values
    {
    "Justin Bieber": 0,
    "Ke$ha": 1
    } 2: Column data
    [0,
    0,
    1,
    1] 3: Bitmaps - one for each unique value of the column
    value="Justin Bieber": [1,1,0,0]
    value="Ke$ha": [0,0,1,1]
  • 注意: 在最坏情况下前面两种会随着数据量的大小而线性增长. 而BitMap的大小则等于数据量大小 * 列的个数.

结构说明


  • 字典表的key都是唯一的, 所以Map的key是unique的column value, Map的value从0开始不断增加. 示例数据的page列只有两个不同的值. 所以为Bieber编号0, Ke$ha编号为1.

    Key            |Value
    ---------------|-----
    Justin Bieber |0
    Ke$ha |1
  • 列的数据: 要保存的是每一行中这一列的值, 值是ID而不是原始的值. 因为有了上面的Map字典, 所以有下面的对应关系,这样列的值列表直接取最后一列: [0,0,1,1],
rowNum  page                ID
1 Justin Bieber 0
2 Justin Bieber 0
3 Ke$ha 1
4 Ke$ha 1
  • BitMap的key是第一步Map的key(列的原始值). value数组的每个元素表示指定列的某一行是否包含/存在/等于当前key. 注意: BitMap保存的value数组只有两个值: 1和0, 1表示这一行包含或等于BitMap的key, 0表示不存在/不包含/不等于,如下:

                            第一行的page列值为Justin Bieber/列值为Justin Bieber的在第一行里
    ^
    |
    value="Justin Bieber": [1,1,0,0]
    value="Ke$ha": [0,0,1,1]
    ^
    |
    第一行的page列值不是Ke$ha
  • 这种存储方式, 如果unique重复的列很少,比如page列的每一个值都是不同的. BitMap就会是一个稀疏矩阵.

    A: [1,0,0,0,0,0,0,0,0,0,0]
    B: [0,1,0,0,0,0,0,0,0,0,0]
    C: [0,0,1,0,0,0,0,0,0,0,0]
    D: [0,0,0,1,0,0,0,0,0,0,0]
    E: [0,0,0,0,1,0,0,0,0,0,0]
  • unique的重复数量很少也叫做high cardinality,表示基数很高,不同列的数量很多,列值相同的记录数很少.

  • 稀疏矩阵对于BitMap而言却是有优点的,因为越是稀疏,它可以被压缩的比例越大,最后存储的空间越少(相对原始数据).

  • 上面只是针对page列的BitMap, 对于其他的维度列, 都有自己的BitMap! 即每一个维度列都有一个BitMap.

segment


  • 数据进入到Druid首先会进行索引, 这给予了Druid一个机会可以进行分析数据, 添加索引结构, 压缩, 为查询优化调整存储结构
    1. 转换为列式结构
    2. 使用BitMap索引
    3. 使用不同的压缩算法
  • 索引的结果是生成Segment文件,Segment中除了保存不同的维度和指标,还保存了这些列的索引信息
  • Druid将索引数据保存到Segment文件中,Segment文件根据时间进行分片. 最基本的设置中, 每一个时间间隔都会创建一个Segment文件
  • 这个时间间隔的长度配置在granularitySpec的segmentGranularity参数.为了Druid工作良好,通常Segment文件大小为300-700M
  • 前面Roll-up时也有一个时间粒度:queryGranularity指的是在读取时就进行聚合.segmentGranularity则是用于分片进来之后的数据.

Durid(二): 数据集及存储的更多相关文章

  1. Spring Boot 揭秘与实战(二) 数据存储篇 - 声明式事务管理

    文章目录 1. 声明式事务 2. Spring Boot默认集成事务 3. 实战演练4. 源代码 3.1. 实体对象 3.2. DAO 相关 3.3. Service 相关 3.4. 测试,测试 本文 ...

  2. Spring Boot 揭秘与实战(二) 数据存储篇 - ElasticSearch

    文章目录 1. 版本须知 2. 环境依赖 3. 数据源 3.1. 方案一 使用 Spring Boot 默认配置 3.2. 方案二 手动创建 4. 业务操作5. 总结 4.1. 实体对象 4.2. D ...

  3. Spring Boot 揭秘与实战(二) 数据存储篇 - MongoDB

    文章目录 1. 环境依赖 2. 数据源 2.1. 方案一 使用 Spring Boot 默认配置 2.2. 方案二 手动创建 3. 使用mongoTemplate操作4. 总结 3.1. 实体对象 3 ...

  4. Spring Boot 揭秘与实战(二) 数据存储篇 - Redis

    文章目录 1. 环境依赖 2. 数据源 2.1. 方案一 使用 Spring Boot 默认配置 2.2. 方案二 手动创建 3. 使用 redisTemplate 操作4. 总结 3.1. 工具类 ...

  5. Spring Boot 揭秘与实战(二) 数据存储篇 - JPA整合

    文章目录 1. 环境依赖 2. 数据源 3. 脚本初始化 4. JPA 整合方案一 通过继承 JpaRepository 接口 4.1. 实体对象 4.2. DAO相关 4.3. Service相关 ...

  6. Spring Boot 揭秘与实战(二) 数据存储篇 - MyBatis整合

    文章目录 1. 环境依赖 2. 数据源3. 脚本初始化 2.1. 方案一 使用 Spring Boot 默认配置 2.2. 方案二 手动创建 4. MyBatis整合5. 总结 4.1. 方案一 通过 ...

  7. Spring Boot 揭秘与实战(二) 数据存储篇 - 数据访问与多数据源配置

    文章目录 1. 环境依赖 2. 数据源 3. 单元测试 4. 源代码 在某些场景下,我们可能会在一个应用中需要依赖和访问多个数据源,例如针对于 MySQL 的分库场景.因此,我们需要配置多个数据源. ...

  8. Spring Boot 揭秘与实战(二) 数据存储篇 - MySQL

    文章目录 1. 环境依赖 2. 数据源3. 脚本初始化 2.1. 方案一 使用 Spring Boot 默认配置 2.2. 方案二 手动创建 4. 使用JdbcTemplate操作5. 总结 4.1. ...

  9. 二叉树的二叉链表存储结构及C++实现

    前言:存储二叉树的关键是如何表示结点之间的逻辑关系,也就是双亲和孩子之间的关系.在具体应用中,可能要求从任一结点能直接访问到它的孩子. 一.二叉链表 二叉树一般多采用二叉链表(binary linke ...

随机推荐

  1. Git最佳实践

    1.git init 2.git add. 3.git add README.md 4.git commit -m "init" 5.git remote add origin h ...

  2. solr 6.1 服务端 tomcat 搭建及调用

    一.下载 apache solr6.1.0 最新版本zip,解压缩生成一个solr6.1.0文件夹 二.安装 1.在d:/projects下新建一个solr 2.把solr6.1.0/server/s ...

  3. ubuntu上mysql服务器安装后只能本地连接不能远程连接的问题

    安装好mysql后,想使用另一个电脑进行远程登录,在登录时 提示拒绝连接 百度后,发现需要两个步骤解决该问题 /etc/mysql/my.cnf 里修改bind_address = 0.0.0.0  ...

  4. ajax 异步插入图片到数据库(单图上传)

    其实也没啥  如图: 点击按钮选择图片,选择完成后 无需点击确定 ,自动上传到服务器指定文件夹 然后插入到数据库中. 下面来看看这要代码 index.php <!DOCTYPE HTML> ...

  5. 使用Git进行代码管理的心得

    使用GitHub进行代码托管的方法 我是直接下载 Github for Windows ,这个软件自带一个 Github 客户端 和一个 Git shell,其中Github是图形化界面,对初学者来说 ...

  6. QT打开ROS工作空间时遇到的问题和解决方法

    之前一直觉得不用IDE写程序看着好像我很能的样子. 其实就相当于工业时代我还钻木取火并且告诉别人你们用打火机根本不知道火被点燃的过程是怎样的. 因为这个技能并非人人都会,就可以拿出去到处臭屁 好了, ...

  7. 在 Apache error_log 中看到多个信息,提示 RSA server certificate CommonName (CN) 与服务器名不匹配(转)

    在 Apache error_log 中看到多个信息,提示 RSA server certificate CommonName (CN) 与服务器名不匹配. Article ID: 1500, cre ...

  8. 字符串中带有emoji表情处理

    1:先删除字符然后解析当前字符再显示 edit.addTextChangedListener(new TextWatcher() { @Override public void beforeTextC ...

  9. 简单通过java的socket&serversocket以及多线程技术实现多客户端的数据的传输,并将数据写入hbase中

    业务需求说明,由于公司数据中心处于刚开始部署的阶段,这需要涉及其它部分将数据全部汇总到数据中心,这实现的方式是同上传json文件,通过采用socket&serversocket实现传输. 其中 ...

  10. a few changes of Android 5.0

    1.Service Intent must be explicit Intent serviceIntent = new Intent(context,MyService.class);context ...