数据规划
Druid索引好的数据放在Historical中,随着数据规模的扩大,分离数据的需求逐渐变得迫切。Druid提供了Tier机制与数据加载Rule机制,通过它们能很好的将数据进行分离,从而达到灵活的分布数据的目的。
Tier机制
Tier机制的作用是将Historical节点进行分组。默认情况下所有的Historical节点属于语默认的“_default_tier”分组。但是我们能通过Historical配置文件中的“druid.server.tier”参数来指定分组。另外请注意Tier只针对Historical节点,而与datasource无关。
数据加载Rule机制
设置了Tier之后,再给Tier添加对应的数据加载Rule,就能实现数据分离的目的。数据加载Rule机制是进行数据分离的基础,也是本文的重点。
Rule主要分两大类Load与Drop,每个大类有细分为Period、Interval和Forever三种。其中Period的意思是最近的一段时间,比如最近一天,随着时间的推移这段时间内的数据也会更迭。Interval和Forever分别指固定时间段与整个数据源的生命周期。
与Tier不同,Rule都是针对datasource的。比如我们给datasource1设置了一些Rule,这些Rule只针对datasource1的数据生效,对其它datasource没有影响。
Druid应用Rule遵循两个原则:(1)按顺序;(2)数据只能应用一条Rule。为了更好的解释它们,请看以下示例:
上例中分别应用了三条Rule到三个Tier中,这三条Rule组合后的意思是:将最近一天的数据加载到hot分组中,后两天的数据加载到cold分组中,剩下的数据加载到default分组中。
第一天(当前天)的数据很好理解应用Rule1,数据被加载到hot分组所在的Historical中。因为第一天的数据已经被应用了Rule1,所以Rule2对第一天的数据失效,所以只有第二第三天的数据被加载到cold分组的Historical中。同理前三天的数据已经应用了Rule,只有剩余的数据应用Rule3,加载到了default分组的Historical中。
随着时间的推移,新的一天的数据变成了第一天,它们被加载到hot分组的Historical中,老的第一天数据被迁移到cold分组的Historical中,同理cold分组前移一天的数据到default分组的Historical中。
Interval类型的Rule很好理解不做过多的解释。以上是Druid提供的Tier与Rule机制,它们对管理规划Druid中的数据提供了基础。下面我们从几个生产环境中很可能出现的场景讨论如何对Druid中的数据进行规划。
冷热数据分离
对于大部分业务来说用户关注的焦点都在最近一段时间内,也就是对一个较长时间段内的数据来说,数据是分冷热的,我们需要做的是尽量保证热数据的高效查询。
而对于Druid来说查询的高效与否有两个很重要的因素:(1)机器配置,主要是CPU与内存;(2)操作系统buffer内存与数据量的比例。在考虑机器成本的前提下,如果我们把这两项资源更多的倾向于热数据,那么对查询效率的提升应该是显而易见的。到此我们已经明确了需求与大概思路,下边看具体如何解决。
首先看第一点将最近的热数据放在配置较好的机器中,冷数据放在较差的机器中。我们将Historical进行分组:一组为"hot"(配置较好的机器,打算存放最近的热数据);另一组为"_default_tier"(配置较差的机器,打算存放冷数据)。然后配置如下规则,以将冷热不同的数据加载到对应的Historical中。
{"type":"loadByPeriod","tieredReplicants":{"hot":1},"period":"P1D"} //load recent 1 day data into hot tier
{"type":"loadForever","tieredReplicants":{"_default_tier":1}} //load the rest data into _default_tier tier
Druid内部采用Json的形式来表示,这里约定下文的Rule也都采用Json的形式。但是我们给datasource设置Rule的时候是通过coordinator的web console界面完成的。
再来看第二点,Druid使用操作系统buffer机制来减少磁盘IO,从而加速查询。Historical能配置自己本地缓存数据使用磁盘最大容量,这是一个很重要的参数,存放冷数据的Historical机器需要一个较大值以存放较多的数据。同样该参数能通过Historical的配置文件指定:
druid.segmentCache.locations=[{"path":"/path/to/druid/segment/cache","maxSize"\:300000000000}]
备份数据分离
为了保证查询服务的高可用性,Druid提供了数据replication机制。考虑到生产环境中机器的共性,比如是否在同一机架上,如果机架故障或断电这一批机器将不可用。出于可用性考虑,可能有将数据分布到两批机器上的需求。为此首先需要将两批机器配置成不同的Tier。然后配置如下Rule:
{"tieredReplicants":{"tier_1":1, "tier_2":1},"type":"loadForever"}
业务数据分离
默认情况下所有的Historical节点都在“_default_tier”中,所有的datasource都公平的使用机器资源。在机器资源有限的情况下,需要优先保证重要业务的查询。为此可以将业务进行分离,重要业务分配较多的机器资源。思路同上首先进行分组,在配置Rule:
{"tieredReplicants":{"tier_1":2},"type":"loadForever"} // for important datasources
{"tieredReplicants":{"tier_2":2},"type":"loadForever"} // for normal datasources
加载部分数据
Druid的使用场景多是一些报表类业务,报表具有很强的时效性,很多场景都只关注最近一段时间的数据,所以为了更充分的利用资源,可以将不关注的数据从Druid中剔除出去。我们可以配置如下Rule:
{"type":"loadByPeriod","period":"P3D","tieredReplicants":{"_default_tier":2}}
{"type":"dropForever"}
上例的两个Rule保留最近3天的数据,其它的则从Druid(也就是Historical)中删掉。删掉的只是Druid本地缓存的数据,Deep Storage中的数据没有删掉。所以有一天删掉这两个Rule,Druid又会将数据加载回来。特别注意这两个Rule的顺序,反过来的话会删掉Druid中所有的数据。
数据分离对查询效率的影响
Druid对数据进行查询的优化有两个基本的点。第一在内存利用上充分利用操作系统Buffer减少IO操作;第二在架构上采用分布式查询树架构,以增加查询的并行性。以下尝试分别从内存利用与架构两个方面对比数据分离与不分离对查询的影响。
首先架构方面,Druid采用的分布式查询树有点类似下图:
Broker节点接收到客户端的请求后,同是分发请求到相应的多个Historical节点,Historical节点首先本地查询并聚合一次数据,发送到Borker节点,Broker节点再次聚合多个Historical节点发送过来的数据,并最终返回给客户端。在整个请求处理过程中,Historical节点本地查询数据环节占用大部分时间,所以尽量多的增加Historical节点的并行度能有效缩短查询时间。
为了达到增加查询并行度的目的,Druid尽量将时间上连续的Segment分散到不同的Historical节点中,算法细节可以查看github上相关文档。但是如果将数据进行分离,就减小了查询时候Historical的并行度。
所以就提高Historical查询并行度角度来说,分离数据后查询效率是要降低的。
再来看内存利用方面,将查询热度最高的数据放到内存中能保证最优的整体查询效率,由于操作系统Buffer采用LRU机制淘汰数据,所以它本身就保证了热数据常驻内存的原则。如果人为将数据进行切割,如果切割得当还好,否则破坏了这个原则,对查询效率是有影响的。
随着业务与数据的增加,分离数据的需求在特定场景下是有的。但是分离数据后对查询效率是有影响的,尤其在集群规模小的情况下,影响可能会较大,所以分离数据的时候需要谨慎。
写在最后
以上是本人在使用Druid过程中,对数据规划方面的一点认识,由于水平有限,如有问题欢迎指正。