2.2 结构说明
字典表的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 结构
DataSource 是一个逻辑概念, Segment 却是数据的实际物理存储格式, Druid 正是通过Segment 实现了对数据的 横纵向切割( Slice and Dice)操作。从数据按时间分布的角度来看,通过参数segmentGranularity 的设置,Druid 将不同时间范围内的数据存储在不同的Segment 数据块中,这便是所谓的数据横向切割。
这种设计为Druid 带来一个显而易见的优点:按时间范围查询数据时,仅需要访问对应时间段内的这些Segment 数据块,而不需要进行全表数据范围查询,这使效率得到了极大的提高。
Druid的分片是Segment文件。 Druid首先总是以时间戳进行分片,因为事件数据总是有时间戳。假设以小时为粒度创建下面的两个Segment文件。
通过Segment 将数据按时间范围存储,同时,在Segment 中也面向列进行数据压缩存储,这便是所谓的数据纵向切割。而且在Segment 中使用了Bitmap 等技术对数据的访问进行了优化。
- 在几乎所有的NoSQL中都有数据分片的概念:比如ES的分片,HBase的Region,都表示的是数据的存储介质。为什么要进行分片,因为数据大了,不能都存成一个大文件吧,所以要拆分成小文件以便于快速查询,伴随拆分通常都有合并小文件。
- 从Segment文件的名称可以看出它包含的数据一定是在文件名称对应的起始和结束时间间隔之内的。
- Segment文件名称的格式:dataSource_interval_version_partitionNumber。最后一个分区号是当同一个时间戳下数据量超过阈值要分成多个分区。
- 分片和分区都表示将数据进行切分。分片是将不同时间戳分布在不同的文件中,而分区是相同时间戳放不下了,分成多个分区。
- 巧合的是Kafka中也有Segment和Partition的概念。Kafka的Partition是topic物理上的分组,一个topic可以分为多个partition,它的partition物理上由多个segment组成。即Partition包含Segment,而Druid是Segment包含Partition。
3.1 小总结
数据进入到Druid首先会进行索引,这给予了Druid一个机会可以进行分析数据,添加索引结构、压缩、为查询优化调整存储结构。
- 转换为列式结构
- 使用BitMap索引
- 使用不同的压缩算法
- 索引的结果是生成Segment文件,Segment中除了保存不同的维度和指标,还保存了这些列的索引信息
- Druid将索引数据保存到Segment文件中,Segment文件根据时间进行分片。最基本的设置中,每一个时间间隔都会创建一个Segment文件。
- 这个时间间隔的长度配置在granularitySpec的segmentGranularity参数。为了Druid工作良好,通常Segment文件大小为300-700M。
- 前面Roll-up时也有一个时间粒度:queryGranularity指的是在读取时就进行聚合,segmentGranularity则是用于分片进来之后的数据。