最近在调研Trino和Clickhouse的打通问题,简单研究了下Trino对于CH的适配,这里简单总结下。详细的代码提交参见这个commit:Add ClickHouse Connector。
加载Plugin
Trino在启动的时候,会加载所有已经支持的plugin,也就是说常说的connector,加载的路径位于plugin/下,如下所示:
可以看到,目前支持的plugin种类非常多,有40多个。这里以CH为例,加载完成之后,服务端会打印相应的日志:
代码语言:javascript复制2022-02-08T16:57:26.445 0800 INFO main io.trino.server.PluginManager -- Loading plugin /data/impala/presto/data/plugin/clickhouse --
2022-02-08T16:57:26.463 0800 INFO main io.trino.server.PluginManager Installing io.trino.plugin.clickhouse.ClickHousePlugin
2022-02-08T17:37:26.845 0800 INFO main io.trino.server.PluginManager Registering connector clickhouse
2022-02-08T16:57:26.472 0800 INFO main io.trino.server.PluginManager -- Finished loading plugin /data/impala/presto/data/plugin/clickhouse --
可以看到,这里主要就是加载了ClickHousePlugin这个类,相关的函数调用栈如下所示:
代码语言:javascript复制doStart(Server.java):126
-loadPlugins(PluginManager.java):129
--loadPlugins(ServerPluginsProvider.java):58
---loadPlugin(PluginManager.java):148
---loadPlugin(PluginManager.java):162
----installPlugin(PluginManager.java):168
-----installPluginInternal(PluginManager.java):191
------addConnectorFactory(ConnectorManager.java)
在服务启动之后,会先注册相应的ConnectorFactory,这里涉及到了比较多的类,我们将相关类的UML图简单整理了下:
ConnectorFactory里面就包含对应的plugin,对于CH而言,ClickHousePlugin。Plugin本身又包含了对应的ConnectionFactory。最终,对于CH的plugin来说,就是使用了ClickHouseDriver去连接CH集群的。后续再进行各种元数据加载和查询的时候,就会利用这个connection来与CH集群进行交互,如下所示:
加载ClickHouse元数据
下面简单来看下Trino是如何加载catalog的。简单的代码调用栈如下所示:
代码语言:javascript复制doStart(Server.java):128
-loadCatalogs(StaticCatalogStore.java):68
--loadCatalog(StaticCatalogStore.java)
默认配置在服务端路径的etc/catalog/下,以“.properties”结尾的所有文件,如下所示:
通过循环加载每个catalog对应的配置文件,如下所示:
代码语言:javascript复制for (File file : listFiles(catalogConfigurationDir)) {
if (file.isFile() && file.getName().endsWith(".properties")) {
loadCatalog(file);
}
}
private File catalogConfigurationDir = new File("etc/catalog/");
这里同样以ClickHouse为例,常见的配置如下:
代码语言:javascript复制connector.name=clickhouse
connection-url=jdbc:clickhouse://host:8123/
connection-user=xxx
connection-password=xxx
allow-drop-table=true
case-insensitive-name-matching=true
当服务启动之后,同样会打印相关的日志,如下所示:
代码语言:javascript复制2022-02-08T16:13:31.644 0800 INFO main io.trino.metadata.StaticCatalogStore -- Loading catalog etc/catalog/clickhouse.properties --
2022-02-08T16:13:32.098 0800 WARN main ru.yandex.clickhouse.ClickHouseDriver ******************************************************************************************
2022-02-08T16:13:32.098 0800 WARN main ru.yandex.clickhouse.ClickHouseDriver * This driver is DEPRECATED. Please use [com.clickhouse.jdbc.ClickHouseDriver] instead. *
2022-02-08T16:13:32.098 0800 WARN main ru.yandex.clickhouse.ClickHouseDriver * Also everything in package [ru.yandex.clickhouse] will be removed starting from 0.4.0. *
2022-02-08T16:13:32.098 0800 WARN main ru.yandex.clickhouse.ClickHouseDriver ******************************************************************************************
2022-02-08T16:13:32.270 0800 INFO main io.trino.metadata.StaticCatalogStore -- Added catalog clickhouse using connector clickhouse --
根据上面的Plugin加载流程可以知道,Trino会根据这些配置项利用ClickHouseDriver来构造CH的Connection,进而进行各种操作。 例如,当我们通过show tables查看CH集群指定db下的表时,就会通过ClickHouseClient中的方法来执行,相关代码如下所示:
代码语言:javascript复制public ResultSet getTables(Connection connection, Optional<String> schemaName, Optional<String>
tableName) throws SQLException
{
// ClickHouse maps their "database" to SQL catalogs and does not have schemas
DatabaseMetaData metadata = connection.getMetaData();
return metadata.getTables(null,schemaName.orElse(null),
escapeNamePattern(tableName, metadata.getSearchStringEscape()).orElse(null),
new String[] {"TABLE", "VIEW"});
}
相关的类图如下所示:
这里CH对应的就是ClickHouseClient。
查询ClickHouse数据
当执行查询的时候,也是通过CH的jdbc来执行的,首先会通过ClickHouseClient来构造一个PreparedStatement,相关的调用栈如下所示:
代码语言:javascript复制buildSql(BaseJdbcClient.java):380
-prepareStatement(QueryBuilder.java):185
--getPreparedStatement(BaseJdbcClient.java):763 --实际当前是ClickHouseClient类型
---prepareStatement(ForwardingConnection.java):54
----prepareStatement(ClickHouseConnectionImpl.java)
后续就会通过PreparedStatement.executeQuery()来执行真正的查询,所以,对于CH的查询来说,Trino还是通过JDBC来让CH自己去查询。关于整个查询流程的代码,后续有空再深入研究。