文章目錄
緣起
在大數據應用組件中,有兩款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-jsonconfig | Properties配置文件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;
}
}
}