Druid架构分析(三)

存储引擎的设计是Druid中最为精彩的地方——涉及到了列式存储,数据压缩,数据多版本等一系列问题。是Druid高效快速的保障。

Druid存储引擎

  数据在Druid中以column oriented的方式组织,所以说Druid Storage是一个面向列的存储引擎。严格的说是行列存储——数据按行划分,按列存储,分类压缩。类似于RCFile的存储格式。

  在Druid中将一组Segment集合称作DataSource,DataSource是一个逻辑概念,类似关系数据库中的表。每一个Segment数据是按照时间范围有序存储的,存储规模通常在500W-100W行左右(Druid为这么都提供了配置参数)。这样设计的好处显而易见,按照时间范围检索数据不用做全表扫描。在Druid中用time interval + version的方式来唯一标识一个Segment。其中version的信息标识数据的“新鲜程度”——version的数值越大表示在此时间范围内的数据越新。所以在读取数据的时候总是能够访问到在某个时间内最新的数据(实际上没有那么简单这里面还有并发控制的问题)。

  现在思考另一个问题,why column oriented? 今天我尝试归纳一下,列式存储在哪些场景下能够提升性能,同时又面临什么问题。在以磁盘为主要介质的存储引擎中,IO带宽是主要的性能瓶颈,在一台有12快磁盘的机器上,就算让磁盘满负荷加起来总吞吐也不会超过1G/S,而基于基于内存的数据检索可以轻松的达到数十G的吞吐,磁盘并不是对随机访问友好的设备。那么提高磁盘的读写吞吐除了换更快的磁盘之外有什么好办法么?可能不外乎以下几点。

不论是那种类型的磁盘都是在顺序读取数据的时候效率最高,但事实上,就算你只需要几个bytes的数据,磁盘也会读取4096字节大小的数据。因为两者的读取效率一样。如果数据按行存储显然一次读取一行的效率是最高的。这种操作对于类似 select COLUMN SET/* from table where PREDICATE SET 既需要检索一次检索大部分数据效率会比较高。然而这类操作在OLAP的场景中不具备代表性,在数据分析的场景中,典型的是BI系统roll up,dill down是最常见操作。转换成SQL以为类似 select COLUMN,Aggreagtor FUN from table where PREDICATE SET group by COLUMN,此时你会发现要查询返回的数据量只是目标表的很少一部分,在这种场景下按行存储就不免有些浪费了,因为显然磁盘要跳过你不需要的column,数据量越大表越宽额外的IO代价就越高。所以此时列式存储就有明显的优势。那么如果涉及到整行读取怎么办?为了使组成一行的检索代表尽量的小,所以在一个数据块中就需要包含尽量多的行(实际上这就是所谓的PAX——Partition Attributes Across)——这里解释了为什么一个segment所包含的数据会那么多,实际上RCFile也是类似的。尤其在分布式存储系统中数据shuffle成本显然更高。列式存储的另外一个好处是,由于同一列的数据类型相同,所以能够更好的压缩你的数据,而且可以对不同的数据类型应用不同的压缩算法。当然列存也不是银弹,因为用户期待的最终还是一行存取的结果,在查询引擎中什么时候做这个转换。这取决于你的查询计划如何生成。到底是pipeline scan还是parallel scan,值得思考。

数据是否可靠

实时节点 实时节点数据的可用性与在其数据流上游的消息总线的数据可用性相当。由于没有WAL Log,出于数据持久化的目的,通常会在数据生产者与Real-time Node之间存在消息总线(Kafka, RabbitMQ),Druid允许多个Real-time Node消费同一组数据,避免当某一个Real-time Node fail(假设磁盘损坏)时候,不至于有数据丢失。貌似一切完美并不丢数据。 不过!!Druid的Segment存在一个时间窗口的概念,举个例子:比如说希望Segment的时间跨度是一个小时,也就是说把一个小时的存储起来,以供后面的查询。 在时间窗口范围内的数据Druid会写入Segment,如果由于网络变化,数据延迟,本应该在窗口内的数据但是晚到了,Druid的做法是把他丢了。这对于要求精确统计的场景是不能接受的。怎么解决?只能在消息总线在开出一个旁路,通过Flume或者类似的方式将其数据在写入Hadoop或者HBase中,如果发现数据有丢失,则重新索引。两个字麻烦!

历史节点 Segment在DeepStorage中备份,即便是当Historical Node失效,再恢复后也会通过zookeeper来重新加载Segment。如果zookeeper不可用,Historical Node不能为新的数据在提供服务或者对数据进行outdate,但是已经加载过的Segment仍然可以供查询。事实上对外的服务层都是通过Broker Node 提供的Http接口。Historical Node & Real-time Node并不直接对外提供服务。