美文网首页
Druid历史节点懒加载实现原理分析

Druid历史节点懒加载实现原理分析

作者: RantLing | 来源:发表于2019-08-18 10:08 被阅读0次

Druid 历史节点懒加载机制原理

声明:此方案来自于gitHub,详情可见https://github.com/apache/incubator-druid/pull/6988

1. 解决问题

Druid集群由多个节点共同提供服务,不同节点之间各司其职。其中Historical(历史节点)的作用主要是从HDFS上拉取segment到本地,提供这部分segment的查询服务和处理路由到本地的查询。Historical在启动的时候会先加载本地segment文件,加载完成之后,这台节点才可以提供服务。

在生产环境中,我们会尽量追求灰度升级。此时,我们会利用历史节点多副本机制,滚动重启。这样可以确保,一个副本能够提供服务。但是,生产环境中的一台Historical节点上存储的数据量可能达到TB级别,重启加载时间可能达到40分钟+,服务重启周期长,人工耗费大,升级成本高。

究其根本,还是因为Historical加载时间过长导致。而懒加载机制直面了这一痛点。通过延迟加载的方式,先让节点和集群进入能过提供服务的状态。大大提升了升级和重启的速度。

2. 历史节点启动加载流程

Historical 服务启动后,segment正常的加载流程如下:

加载Segments

具体流程如下:

  1. 获取本地info_dir目录下的所有文件(cachedFile,描述segment的文件)

    cachedFile文件内容如下:

{
 "dataSource": "lazy_load_test",
 "interval": "2019-01-30T00:00:00.000+08:00/2019-02-01T00:00:00.000+08:00",
 "version": "2019-02-01T20:14:53.608+08:00",
 "loadSpec": {
     "type": "hdfs",
     "path": "/druid/segments/lazy_load_test/20190130T000000.000+0800_20190201T000000.000+0800/2019-02-01T20_14_53.608+08_00/0/index.zip"
 },
 "dimensions": "language,namespace,unpatrolled",
 "metrics": "count,delta",
 "shardSpec": {
     "type": "none"
 },
 "binaryVersion": 9,
 "size": 3553,
 "identifier": "lazy_load_test_2019-01-30T00:00:00.000+08:00_2019-02-01T00:00:00.000+08:00_2019-02-01T20:14:53.608+08:00"
}

校验Segment Cahced File对应的Segment 是否已经拉取到本地的缓存目录中了。若是已经拉取到本地了,则会将对应的segment放到一个队列中。没有拉取到本地的就会把 Cached File 删除。

  1. 遍历已经拉取到本地的的segments进行加载,每个segment提交一个线程去处理。加载之后就会到ZK上去注册服务。

  2. ZK的加载队列上也会增加一个监听器,监听加载队列的变化,若是有CHILD_ADDED事件发生,就会去加载该事件对应的segment。如果CacheFile不存在的话还会被CacheFile写到本地的info_dir目录下。具体的加载过程和本地的加载相似。

  3. 加载过程就是文件中对每一列进行反序列化。针对不同类型的列获取到不同的反序列器(long,float,complex,stringDictionary)。对每一列中的不同类型的值进行不同的发序列化,通过drd文件中的parts获取到不同的SerDe。

    历史节点记载过程耗时比较长的就是对列进行反序列化的过程。反序列化之后的列会被放到一个Map中,作为参数创建SimpleQueryableIndex,该类实现了ColumnSelector接口。在查询的过程中会用到,历史节点加载的过程中并不会用到这个类

    懒加载的原理就是在返回SimpleQueryableIndex类的时候,不进行反序列化。而是把序列化的过程推迟到对应segment被查询的时候。

3. 懒加载原理

3.1 Supplier 接口

这个接口只有一个方法T get(),这个方法需要返回一个T类型的对象,且只有调用该方法的时候对象才会被创建。

Demo

Supplier<Person> supplier = ()-> new Person(); //这里并不会创建Person对象
supplier.get.getName();//到这里才会创建Person对象

3.2 Suppliers 接口

这个接口接口中的一个方法为memoize,这个方法可以返回一个Supplier,区别是通过momoize创建的对象是单例的。

Demo

  public static void main(String[] args) throws Exception {
    Supplier<Person> s1 = Suppliers.memoize(()-> new Person());
    Person p1 = s1.get();
    Person p2 = s1.get();

    System.out.println("p1.name = "+ p1.getName());
    System.out.println("p2.name = "+ p2.getName());

    p1.setName("四儿");
    System.out.println("=====================\n"+"p2.name = "+ p2.getName());
  }

验证输出: 
 p1.name = 三儿
 p2.name = 三儿
 =====================
 p2.name = 四儿

3. Supplier实现历史节点的懒加载

3.1 Druid 原本的实现

反序列化.png

在加载的过程中就会对列进行序列化,但是序列化之后的列只有在查询之后才会被用到。

3.2 优化之后的实现

AfterOptimization.png

再改写一下原本的get方法

image.png

这样只有在调用getColumn的时候才会进行序列化的操作。这种推迟操作实现了历史节点启动或重启时的懒加载。

4. 优化前后测试

segment个数:21244

segment总大小:551G

优化前,加载总时间为 21 分钟

优化后,加载总时间为 2分30秒

加载速度优化效果明显。

5. 小结

这里的懒加载,就是将真正的反序列化阶段延迟,提前返回加载成功的状态,让历史节点提供服务。但是会对第一的查询产生影响,因为第一次get会触发真正的加载。

相关文章

  • Druid历史节点懒加载实现原理分析

    Druid 历史节点懒加载机制原理 声明:此方案来自于gitHub,详情可见https://github.com/...

  • 【Druid】Historical Node

    历史节点负责加载历史segment,使segment数据能够被查询。 Running io.druid.cli.M...

  • 【Druid】Coordinator Node

    Druid协调节点主要负责segment的管理和分配,它基于配置信息通知历史节点加载和删除segments。具体来...

  • webpack打包代码实现

    webpack模块加载 异步模块加载 通过 import()实现指定模块的懒加载操作 懒加载的核心原理就是创建js...

  • 2021-04-20

    历史节点的配置druid.service=druid/historicaldruid.port=8083 HTTP...

  • Druid-高性能实时数据分析数据库

    概览 事件流的分析 druid 提供了快速的分析查询一个高并发,在实时节点和历史节点上;强大的用户交互界面; 重构...

  • Druid 监控实现原理

    Druid监控功能 druid提供了丰富的监控功能,这篇文章主要分析下监控功能的实现原理。 通过代理模式控制sta...

  • 懒加载---原理分析

    懒加载的本意就是,让界面显示的时候再去加载数据。对于Fragment来说,他的onCreateView()方法被执...

  • ios逆向 - fishhook的源码分析

    一 知识回顾 在上一节,我们分析了fishhook的原理, 知道fishhook 通过动态修改懒加载或非懒加载指针...

  • 图片的懒加载和预加载

    懒加载:又称延迟加载,需要等到某个情况下才加载,相对可以缓解服务器压力。 实现原理:以懒加载图片为例,当加载图片较...

网友评论

      本文标题:Druid历史节点懒加载实现原理分析

      本文链接:https://www.haomeiwen.com/subject/dsfrsctx.html