Presto可以支持很多的数据源,并且数据源都是以plugin的方式添加的。Presto可以很方便地添加新的Connector,接下来以自带的presto-example-http为例,详细介绍在Presto中如何添加一个新的Connector。
一、创建Maven工程
在Presto目录下创建一个presto-example-http的目录,在presto-example-http根目录下编辑pom.xml配置文件,以下几项是必须配置的。
-
将当前工程加入到Presto组中,版本需要和整个Presto工程的版本一致。
<parent> <groupId>com.facebook.presto</groupId> <artifactId>presto-root</artifactId> <version>${project.version}</version> </parent>
-
描述当前工程的信息,packaging选项为presto-plugin,在打包编译时,会将当前工程打包编译到Presto的plugin目录下。
<artifactId>presto-example-http</artifactId> <description>Presto - Example HTTP Connector</description> <packaging>presto-plugin</packaging>
-
每个Plugin工程都需要依赖presto-spi模块,presto-spi中统一定义了各种API接口。
<!-- Presto SPI --> <dependency> <groupId>com.facebook.presto</groupId> <artifactId>presto-spi</artifactId> <scope>provided</scope> </dependency>
presto-spi最终会打包编译到presto/lib目录下,所有的plugin模块都会加载lib下的jar,所以当前的presto-spi不需要重复打包编译到当前的plugin目录中。
在整个Presto工程编译时,需要将我们自定义添加的connector也编译打包到plugin目录下,通过以下的配置即可达到我们的目的。
-
在Presto根目录下的pom.xml中,将我们新增的Connector加入到modules中:
<modules> <module>presto-example-http</module> </modules>
-
在presto-server的src/main/assembly/presto.xml配置文件中,注册新增的Connector:
<fileSets> <!-- plugins --> <fileSet> <directory>${project.build.directory}/dependency/presto-example-http-${project.version}</directory> <outputDirectory>plugin/example-http</outputDirectory> </fileSet> </fileSets>
经过以上步骤,新增Connector的准备工作就已经完成。在开发过程中根据具体实现使用到的类来添加其依赖。Plugin使用了独立的类加载器,和其他的类是隔离的,因此Plugin可以使用不同版本的类库,区别于Presto内部使用的版本,例如当前Plugin要使用的jar包与Presto内部所使用的版本有冲突,那么Plugin内部可以定义声明不同于Presto内部所使用的jar包版本。
二、注册Plugin
每个Plugin必须要实现com.facebook.presto.spi.Plugin接口,Presto会在服务启动环节加载所有的Plugin,入口代码在com.facebook.presto.server.PrestoServer.run方法中:
// PrestoServer.scala
public void run()
{
Bootstrap app = new Bootstrap(modules.build());
Injector injector = app.initialize();
injector.getInstance(PluginManager.class).loadPlugins();
}
injector为Guice的注解加载工具,其含义为:实例化PluginManager并且调用其loadPlugins方法。loadPlugins方法的逻辑为:读取Presto部署的Plugin目录,构造类加载器,最终调用PluginManager.installPlugin(Plugin plugin)方法向PrestoServer注册Plugin。
// PluginManager.java
public void loadPlugins()
throws Exception
{
for (File file : listFiles(installedPluginsDir)) {
if (file.isDirectory()) {
loadPlugin(file.getAbsolutePath());
}
}
}
private void loadPlugin(String plugin)
throws Exception
{
URLClassLoader pluginClassLoader = buildClassLoader(plugin); // 默认使用PluginClassLoader.java类加载器
try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(pluginClassLoader)) {
loadPlugin(pluginClassLoader);
}
}
private void loadPlugin(URLClassLoader pluginClassLoader)
{
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, pluginClassLoader);
List<Plugin> plugins = ImmutableList.copyOf(serviceLoader);
for (Plugin plugin : plugins) {
installPlugin(plugin);
}
}
public void installPlugin(Plugin plugin)
{
for (ConnectorFactory connectorFactory : plugin.getConnectorFactories()) {
if (disabledConnectors.contains(connectorFactory.getName())) {
log.info("Skipping disabled connector %s", connectorFactory.getName());
continue;
}
log.info("Registering connector %s", connectorFactory.getName());
connectorManager.addConnectorFactory(connectorFactory);
}
}
从上面我们可以看到,最终需要调用Plugin的getConnectorFactories方法注册connector,getConnectorFactories会返回一个ConnectorFactory列表,并且将该ConnectorFactory加入到ConnectorManager中,那么在注册一个Connector时需要overwrite getConnectorFactories
方法,在presto-example-http module的ExamplePlugin类中进行了实现:
// ExamplePlugin.java
@Override
public Iterable<ConnectorFactory> getConnectorFactories()
{
return ImmutableList.of(new ExampleConnectorFactory());
}
这样整个Plugin的注册工作就完成了。工厂类ExampleConnectorFactory继承自接口com.facebook.presto.spi.ConnectorFactory(该接口属于presto-spi工程),因此ExampleConnectorFactory类需要实现以下三个方法:
- getName:返回Plugin的名称。
- getHandleResolver:返回handler解析器,该Connector各种相应处理类ConnectorHandleResolver。
- create(String connectorId, Map<String, String> config):注册该Plugin所要启动的module,设置Connector的相关配置参数并完成Connector的实例化。
三、定义Connector
在注册Plugin时会将ConnectorFactory注册到ConnectorManager,而且最终是需要通过ConnectorFactory.create方法来实例化Connector,这个过程的实现是在PrestoServer.run方法中加载catalog配置:
injector.getInstance(StaticCatalogStore.class).loadCatalogs();
使用StaticCatalogStore加载Presto中配置的各个Catalog,loadCatalogs读取到Catal