Apache Druid源码导读--Google guice DI框架

Posted yueguanghaidao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Apache Druid源码导读--Google guice DI框架相关的知识,希望对你有一定的参考价值。

文章目录

缘起

在大数据应用组件中,有两款OLAP引擎应用广泛,一款是偏离线处理的Kylin,另一个是偏实时的Druid。Kylin是一款国人开源的优秀离线OLAP引擎,基本上是Hadoop领域离线OLAP事实标准,在离线报表,指标分析领域应用广泛。而Apache Druid则在实时OLAP领域独领风骚,优异的性能、高可用、易扩展。Kylin的实现细节网上资料很多,而Druid很少,最近打算研究下源码,写几篇阅读导读,记录于此。

阅读Druid源码的第一个障碍莫过于Google Guice这个小巧的DI框架了。不了解Guice很难阅读Druid源码,而Guice在国内应用偏少,文章也少,无疑加大了难度。我把Druid中使用的guice扩展单独抽出来,放在了https://github.com/Skycrab/guice-module下,对druid源码注释放在了https://github.com/Skycrab/druid-comment

本文主要介绍下Google Guice以及Druid中实现的guice扩展模块。

Google Guice介绍

Guice是Google开源的一个小巧的依赖注入框架。
下面主要介绍下与Spring的对比,以及guice几个核心的能力。

与Spring的对比

Guice与Spring没有直接竞争关系,Spring是复杂的技术栈,而Guice只专注于依赖注入。

Guice与Spring的表现方式也稍微有所区别。Guice觉得基于xml的方式过于隐晦,而自动注入(AutoWired)又过于灵活,所以Guice基于代码绑定实现,较为克制。

而基于Module的方式让Guice获得了巨大的灵活性与可复用性,可以简单理解为多个xml装配,但更加强大,可复用。

Example

看下面这个例子(git地址)

@Slf4j
public class GuiceExample 
    public static void main( String[] args ) throws Exception 
        Injector injector = Guice.createInjector(new Module() 
            @Override
            public void configure(Binder binder) 
                binder.bind(ProduceService.class).to(KafkaPrduceService.class);
                binder.bind(String.class).annotatedWith(Names.named("server")).toInstance("localhost:9002");
                binder.bind(String.class).annotatedWith(Names.named("topic")).toInstance("test");
            
        );

        ProduceService produce = injector.getInstance(ProduceService.class);
        produce.produce("hello guice");
    

    public interface ProduceService 
        void produce(Object msg);
    

    @Singleton
    public static class KafkaPrduceService implements ProduceService 
        private String server;
        private String topic;

        @Inject
        public KafkaPrduceService(@Named("server") String server, @Named("topic") String topic) 
            this.server = server;
            this.topic = topic;
        
        @Override
        public void produce(Object msg) 
            log.info("produce --", server, topic, msg);
        
    

我们看到guice中的绑定关系是在Module中维护的,可以简单当做是spring的xml文件。Singleton代表该服务是单例的,通过@Inject注入需要的bean,如果需要的bean没有绑定,会通过默认构造函数实例化。
其它基本介绍可参考guice文档

覆盖已有绑定关系

Apache Druid好多模块是可以自定义替换的,一方面通过spi机制+ClassLoader加载扩展模块实现模块热插拔,另一方面通过Guice覆盖绑定关系将新实现注入到框架。下面要介绍的就是guice的覆盖绑定关系能力。
看下面这个例子

/**
 * Created by yihaibo on 2019-09-29.
 * Guice模块绑定覆盖
 */
public class GuiceOverrideExample 
  public static void main(String[] args) 
    List<Module> builtIns = ImmutableList.of(binder -> 
      binder.bind(Service.class).to(BuiltinService.class);
    );
//    List<Module> customs = ImmutableList.of();
    List<Module> customs = ImmutableList.of(binder -> 
      binder.bind(Service.class).to(CustomService.class);
    );
    Injector injector = Guice.createInjector(Modules.override(builtIns).with(customs));
    FrameWork frameWork = injector.getInstance(FrameWork.class);
    frameWork.start();
  
  
  public static class FrameWork 
    private Service service;
    @Inject
    public FrameWork(Service service) 
      this.service = service;
    
    public void start() 
      this.service.run();
    
  
  
  public interface Service 
    void run();
  
  
  public static class BuiltinService implements Service 
    @Override
    public void run() 
      System.out.println("BuiltinService");
    
  

  public static class CustomService implements Service 
    @Override
    public void run() 
      System.out.println("CustomService");
    
  

我们看到框架本来绑定的是BuiltinService,现在我们需要替换成CustomService,只需要Modules.override即可覆盖绑定关系。

默认绑定

在做基础库的时候,有时会依赖一些服务,但这些服务很可能被用户自定义,这时可以使用guice的默认绑定功能。
看下面代码

@Slf4j
public class GuiceOptionalBinderExample 
public static void main(String[] args) 
  Injector injector = Guice.createInjector(new FrameWorkModule(), new Module() 
    @Override
    public void configure(Binder binder) 
      //覆盖框架默认实现
      //如果需要传递参数,可以1.Inject 2.使用Provider
      OptionalBinder.newOptionalBinder(binder, Emit.class).setBinding().to(kafkaEmit.class);
    
  );

  TestService testService = injector.getInstance(TestService.class);
  testService.test();


public static class TestService 
  private Emit emit;
  @Inject
  public TestService(Emit emit) 
    this.emit = emit;
  
  public void test() 
    this.emit.emit("start TestService");
  


//-------应用代码
public static class kafkaEmit implements Emit 
  @Override
  public void emit(Object object) 
    log.info("kafkaEmit emit");
  


//-------库代码
public static class FrameWorkModule implements Module 
  @Override
  public void configure(Binder binder) 
    //库默认实现
    OptionalBinder.newOptionalBinder(binder, Emit.class).setDefault().to(HttpEmit.class);
  


public interface Emit 
  void emit(Object object);


public static class HttpEmit implements Emit 
  @Override
  public void emit(Object object) 
    log.info("HttpEmit emit");
  


Apache Druid中Guice模块

在Druid中有几个通用的Guice扩展,不了解会对代码阅读产生影响。

模块功能
guice-lifecycle实现生命周期托管,实现服务start、stop方法
guice-jsonconfigProperties配置文件bean自动装配
guice-jersey-jetty内嵌jetty的jersey Restful

guice-lifecycle

LifecycleModule提供服务托管能力,提供了4级服务优先级,框架会自动调用start和stop方法。该功能在druid应用非常广泛,是应用启动的原点。

/**
 * Guice 生命周期管理
 */
public class GuiceLifecycleExample

    public static void main( String[] args ) throws Exception
    
        final Injector injector = Guice.createInjector(new LifecycleModule());
        final Bootstrap bootstrap = injector.getInstance(Bootstrap.class);
        bootstrap.run();
    

    public static class Bootstrap
        // 必须主动注入ManageLifecycle,否则需要通过Lifecycle.addHandler主动注册
        private PrintLifecycle printLifecycle;
        private Lifecycle lifecycle;
        @Inject
        public Bootstrap(Lifecycle lifecycle, PrintLifecycle printLifecycle) 
            this.lifecycle = lifecycle;
            this.printLifecycle = printLifecycle;
        
        public void run() throws Exception 
            System.out.println("Bootstrap run");
            lifecycle.start();
            lifecycle.join();
        
    
    
    @ManageLifecycle
    @Slf4j
    public static class PrintLifecycle 
        @LifecycleStart
        public void start() 
            System.out.println("PrintLifecycle start");
        

        @LifecycleStop
        public void stop()
        
            System.out.println("PrintLifecycle stop");
        
    

guice-jsonconfig

jsonconfig提供了配置文件bean自动装配,并支持validation注解校验。Druid中读取配置功能都使用了该功能。

public class GuiceJsonConfigExample 
  public static void main( String[] args ) 
    Injector injector = Guice.createInjector(new JsonConfigModule(), new Module() 
      @Override
      public void configure(Binder binder) 
        JsonConfigProvider.bind(binder, "druid.server", DruidServerConfig.class);
      

      @Provides
      @Singleton
      /**
       * Properties代码注入需注意必须为string
       */
      Properties provideProperties() 
        Properties props = new Properties();
        props.put("druid.server.hostname", "0.0.0.0");
        props.put("druid.server.port", "3333");
        return props;
      
    );

    DruidServerConfig druidServerConfig = injector.getInstance(DruidServerConfig.class);

    System.out.println(druidServerConfig.port);
    System.out.println(druidServerConfig.hostname);
  

  public static class DruidServerConfig 
    @JsonProperty
    @NotNull
    public String hostname = null;
    @JsonProperty @Min(1025) public int port = 8080;
  

guice-jersey-jetty

jersey-jetty提供了内嵌jetty的jersey Restful。Druid中http接口都由jersey支持,由Jetty充当servlet容器。

/**
 * Created by yihaibo on 2019-10-08.
 * jersey restful简单列子
 */
@Slf4j
public class GuiceJerseryJettyExample 
  public static void main(String[] args) throws Exception 
    Injector injector = Guice.createInjector(new JsonConfigModule(), new JettyServerModule(), new Module() 
      @Override
      public void configure(Binder binder) 
        Jerseys.addResource(binder, IndexResource.class);
      

      @Provides @Singleton
      Properties provideProperties() 
        Properties props = new Properties();
        props.put("server.http.host", "0.0.0.0");
        props.put("server.http.port", "9000");
        return props;
      
    );

    JerseyJettyServer jerseyJettyServer = injector.getInstance(JerseyJettyServer.class);
    jerseyJettyServer.start();
    Thread.currentThread().join();
  

  @Singleton
  @Path("/index")
  public static class IndexResource 
    private ServerConfig serverConfig;
    @Inject
    public IndexResource(ServerConfig serverConfig) 
      this.serverConfig = serverConfig;
    

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public ServerConfig doGet(@Context final HttpServletRequest req) 
      return serverConfig;
    
  


以上是关于Apache Druid源码导读--Google guice DI框架的主要内容,如果未能解决你的问题,请参考以下文章

Druid 认知 | Apache Druid 的集群设计与工作流程

Apache Druid 底层存储设计(列存储与全文检索)

大数据实时多维OLAP分析数据库Apache Druid入门分享-上

大数据Apache Druid:Druid流式数据加载

大数据Apache Druid:Druid批量数据加载

大数据Apache Druid:使用Imply进行Druid集群搭建