Trino动态catalog实现

本文基于trino423版本进行修改,实现从数据库中加载catalog配置,并设置刷新时间间隔动态更新trino中catalog配置

  1. 修改 CatalogStoreConfig类

    io.trino.connector.CatalogStoreConfig文件,增加数据库枚举类型为 DATABASE
public class CatalogStoreConfig {
    public enum CatalogStoreKind {
        MEMORY, FILE, DATABASE
    }

    private CatalogStoreKind catalogStoreKind = CatalogStoreKind.FILE;

    @NotNull
    public CatalogStoreKind getCatalogStoreKind() {
        return catalogStoreKind;
    }

    @Config("catalog.store")
    public CatalogStoreConfig setCatalogStoreKind(CatalogStoreKind catalogStoreKind) {
        this.catalogStoreKind = catalogStoreKind;
        return this;
    }
}

DynamicCatalogManagerModule增加DATABASE实现

 switch (config.getCatalogStoreKind()) {
                case MEMORY -> binder.bind(CatalogStore.class).to(InMemoryCatalogStore.class).in(Scopes.SINGLETON);
                case FILE -> {
                    configBinder(binder).bindConfig(FileCatalogStoreConfig.class);
                    binder.bind(CatalogStore.class).to(FileCatalogStore.class).in(Scopes.SINGLETON);
                }
                case DATABASE -> {
                    configBinder(binder).bindConfig(DatabaseCatalogStoreConfig.class);
                    binder.bind(CatalogStore.class).to(DatabaseCatalogStore.class).in(Scopes.SINGLETON);
                }
            }

创建 DatabaseCatalogStoreConfig 对象

public class DatabaseCatalogStoreConfig {
    //数据库连接地址
    private String databaseUrl;
    //用户名
    private String userName;
    //密码
    private String password;
    //数据库类型 支持 mysql、dm、sqlserver、postgresql、oracle
    private String databaseType;
    //刷新时间间隔
    private int refreshInterval;
    //读取配置的数据表
    private String tableName;

    @Config("catalog.database-url")
    public DatabaseCatalogStoreConfig setDatabaseUrl(String databaseUrl) {
        this.databaseUrl = databaseUrl;
        return this;
    }

    @Config("catalog.database-userName")
    public DatabaseCatalogStoreConfig setUserName(String userName) {
        this.userName = userName;
        return this;
    }

    @Config("catalog.database-password")
    public DatabaseCatalogStoreConfig setPassword(String password) {
        this.password = password;
        return this;
    }

    @Config("catalog.database-type")
    public DatabaseCatalogStoreConfig setDatabaseType(String databaseType) {
        this.databaseType = databaseType;
        return this;
    }

    @Config("catalog.database-refreshInterval")
    public DatabaseCatalogStoreConfig setRefreshInterval(int refreshInterval) {
        this.refreshInterval = refreshInterval;
        return this;
    }

    @Config("catalog.database-table")
    public DatabaseCatalogStoreConfig setTableName(String tableName) {
        this.tableName = tableName;
        return this;
    }


    public String getDatabaseUrl() {
        return databaseUrl;
    }

    public String getUserName() {
        return userName;
    }

    public String getPassword() {
        return password;
    }

    public String getDatabaseType() {
        return databaseType;
    }

    public int getRefreshInterval() {
        return refreshInterval;
    }

    public String getTableName() {
        return tableName;
    }
}

创建DatabaseCatalogStore 对象

DatabaseCatalogStore对象主要目的如下:

1:从数据库中加载catalog信息

2:组装各类型 连接器需要的配置信息

public class DatabaseCatalogStore implements CatalogStore {

    private static final Logger log = Logger.get(DatabaseCatalogStore.class);
    public final ConcurrentMap<String, DatabaseStoredCatalog> catalogs = new ConcurrentHashMap<>();
    private final DatabaseCatalogStoreConfig databaseCatalogStoreConfig;
    private final static String DS_ALIAS = "alias";

    @Inject
    public DatabaseCatalogStore(DatabaseCatalogStoreConfig config) throws Exception {
        this.databaseCatalogStoreConfig = config;
        checkConfig(databaseCatalogStoreConfig);
        loadDsCatalogs(catalogs);
    }

    @Override
    public Collection<StoredCatalog> getCatalogs() {
        return ImmutableList.copyOf(catalogs.values());
    }


    @Override
    public CatalogProperties createCatalogProperties(String catalogName, ConnectorName connectorName, Map<String, String> properties) {
        return new CatalogProperties(
                createRootCatalogHandle(catalogName, computeCatalogVersion(catalogName, connectorName, properties)),
                connectorName,
                ImmutableMap.copyOf(properties));
    }

    @Override
    public void addOrReplaceCatalog(CatalogProperties catalogProperties) {
        String catalogName = catalogProperties.getCatalogHandle().getCatalogName();
        Map<String, String> catalogPropertiesMap = new HashMap<>();
        catalogPropertiesMap.put("connector.name", catalogProperties.getConnectorName().toString());
        catalogPropertiesMap.putAll(catalogProperties.getProperties());
        catalogs.put(catalogName, new DatabaseStoredCatalog(catalogName, catalogPropertiesMap));
    }

    @Override
    public void removeCatalog(String catalogName) {
        catalogs.remove(catalogName);
    }

  
    public void loadDsCatalogs(ConcurrentMap<String, DatabaseStoredCatalog> catalogConcurrentMap) {
        try {
            List<Entity> dsConfigList = loadDsConfig();
            if (CollUtil.isNotEmpty(dsConfigList)) {
                for (Entity entity : dsConfigList) {
                    String alias = entity.getStr(DS_ALIAS);
                    String catalogName = getNameWithoutExtension(alias);
                    checkArgument(!catalogName.equals(GlobalSystemConnector.NAME), "Catalog name SYSTEM is reserved for internal usage");
                    catalogConcurrentMap.put(catalogName, new DatabaseStoredCatalog(catalogName, buildCatalogProperties(entity)));
                }
            }
        } catch (Exception e) {
            log.error("加载数据源配置异常,请检查 !!!");
        }
    }


    public static class DatabaseStoredCatalog implements StoredCatalog {

        private String name;
        private Map<String, String> properties;

        public DatabaseStoredCatalog(String name, Map<String, String> properties) {
            this.name = requireNonNull(name, "name is null");
            this.properties = requireNonNull(properties, "catalogConfig is null");
        }

        @Override
        public String getName() {
            return name;
        }

        public Map<String, String> getProperties() {
            return properties;
        }

        @Override
        public CatalogProperties loadProperties() {
            String connectorNameValue = properties.remove("connector.name");
            checkState(connectorNameValue != null, "Catalog configuration %s does not contain 'connector.name'");
            if (connectorNameValue.indexOf('-') >= 0) {
                String deprecatedConnectorName = connectorNameValue;
                connectorNameValue = connectorNameValue.replace('-', '_');
                log.warn("Catalog '%s' is using the deprecated connector name '%s'. The correct connector name is '%s'", name, deprecatedConnectorName, connectorNameValue);
            }
            ConnectorName connectorName = new ConnectorName(connectorNameValue);
            CatalogHandle catalogHandle = createRootCatalogHandle(name, computeCatalogVersion(name, connectorName, properties));
            return new CatalogProperties(catalogHandle, connectorName, ImmutableMap.copyOf(properties));
        }
    }

    
    static CatalogHandle.CatalogVersion computeCatalogVersion(String catalogName, ConnectorName connectorName, Map<String, String> properties) {
        Hasher hasher = Hashing.sha256().newHasher();
        hasher.putUnencodedChars("catalog-hash");
        hashLengthPrefixedString(hasher, catalogName);
        hashLengthPrefixedString(hasher, connectorName.toString());
        hasher.putInt(properties.size());
        ImmutableSortedMap.copyOf(properties).forEach((key, value) -> {
            hashLengthPrefixedString(hasher, key);
            hashLengthPrefixedString(hasher, value);
        });
        return new CatalogHandle.CatalogVersion(hasher.hash().toString());
    }

    private static void hashLengthPrefixedString(Hasher hasher, String value) {
        hasher.putInt(value.length());
        hasher.putUnencodedChars(value);
    }

   
    private List<Entity> loadDsConfig() {
        Connection connection = null;
        try {
            connection = loadConnection(databaseCatalogStoreConfig);
            SqlConnRunner sqlConnRunner = SqlConnRunner.create(loadDialect(databaseCatalogStoreConfig));
            Entity where = new Entity();
            where.setTableName(databaseCatalogStoreConfig.getTableName());
            where.put("deleted", 0);
            return sqlConnRunner.findAll(connection, where);
        } catch (Exception e) {
            log.error("加载数据源配置异常,请检查 !!!", e);
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception e) {
                }
            }
        }
        return null;
    }

   
    private static void checkConfig(DatabaseCatalogStoreConfig config) {
        requireNonNull(config, "config is null");
        if (StrUtil.isBlank(config.getDatabaseUrl())) {
            requireNonNull(config, "动态数据源 数据库URL不能为空,请检查 !!!");
        }
        if (StrUtil.isBlank(config.getDatabaseType())) {
            requireNonNull(config, "动态数据源 数据库类型不能为空,请检查 !!!");
        }
        if (StrUtil.isBlank(config.getPassword())) {
            requireNonNull(config, "动态数据源 数据库连接密码不能为空,请检查 !!!");
        }
        if (StrUtil.isBlank(config.getTableName())) {
            requireNonNull(config, "动态数据源 数据库数据源配置表不能为空,请检查 !!!");
        }
        if (StrUtil.isBlank(config.getUserName())) {
            requireNonNull(config, "动态数据源 数据库用户名不能为空,请检查 !!!");
        }

    }


    
    private static Connection loadConnection(DatabaseCatalogStoreConfig config) throws Exception {
        switch (config.getDatabaseType()) {
            case "MYSQL":
                return DbUtils.getMysqlConn(config.getDatabaseUrl(), config.getUserName(), config.getPassword());
            case "ORACLE":
                return DbUtils.getOracleConn(config.getDatabaseUrl(), config.getUserName(), config.getPassword());
            case "POSTGRESQL":
                return DbUtils.getPostGresqlConn(config.getDatabaseUrl(), config.getUserName(), config.getPassword());
            case "DM":
                return DbUtils.getDmSqlConn(config.getDatabaseUrl(), config.getUserName(), config.getPassword());
            case "SQLSERVER":
                return DbUtils.getSqlServerConn(config.getDatabaseUrl(), config.getUserName(), config.getPassword());
            default:
                throw new Exception("不支持的数据库类型,请配置 MYSQL、ORACLE、POSTGRESQL、DM、SQLSERVER 其中一种数据库,请检查 !!!");
        }
    }

    
    private static Dialect loadDialect(DatabaseCatalogStoreConfig config) throws Exception {
        switch (config.getDatabaseType()) {
            case "MYSQL":
                return new MysqlDialect();
            case "ORACLE":
                return new OracleDialect();
            case "POSTGRESQL":
                return new PostgresqlDialect();
            case "DM":
                return new OracleDialect();
            case "SQLSERVER":
                return new SqlServer2012Dialect();
            default:
                throw new Exception("不支持的数据库类型,请配置 MYSQL、ORACLE、POSTGRESQL、DM、SQLSERVER 其中一种数据库,请检查 !!!");
        }
    }


    
    public static Map<String, String> buildCatalogProperties(Entity entity) throws Exception {
        Map<String, String> catalogProperties = new HashMap<>();
        String dbType = entity.getStr("db_type");
        String userName = entity.getStr("username");
        String password = entity.getStr("password");
        requireNonNull(dbType, "数据库类型异常,请检查 !!!");
        requireNonNull(userName, "用户名异常,请检查 !!!");
        requireNonNull(password, "数据库密码异常,请检查 !!!");
        DataBaseTypeEnum dataBaseTypeEnum = DataBaseTypeEnum.queryByKey(dbType);
        requireNonNull(dataBaseTypeEnum, "不支持的数据库类型,请检查 !!!");
        password = RSAUtils.decrypt(password);
        switch (dataBaseTypeEnum) {
            case Mysql:
                catalogProperties.put("connector.name", "mysql");
                catalogProperties.put("connection-url", buildMysqlConnUrl(entity));
                catalogProperties.put("connection-user", userName);
                catalogProperties.put("connection-password", password);
                return catalogProperties;
            case Postgresql:
                catalogProperties.put("connector.name", "postgresql");
                catalogProperties.put("connection-url", buildPostGreSqlConnUrl(entity));
                catalogProperties.put("connection-user", userName);
                catalogProperties.put("connection-password", password);
                return catalogProperties;
            case Sqlserver:
                catalogProperties.put("connector.name", "sqlserver");
                catalogProperties.put("connection-url", buildSqlServerConnUrl(entity));
                catalogProperties.put("connection-user", userName);
                catalogProperties.put("connection-password", password);
                return catalogProperties;
            case Oracle:
                catalogProperties.put("connector.name", "oracle");
                catalogProperties.put("connection-url", buildOracleConnUrl(entity));
                catalogProperties.put("connection-user", userName);
                catalogProperties.put("connection-password", password);
                return catalogProperties;
            case Dm:
                catalogProperties.put("connector.name", "dm");
                catalogProperties.put("connection-url", buildDmConnUrl(entity));
                catalogProperties.put("connection-user", userName);
                catalogProperties.put("connection-password", password);
                return catalogProperties;
            case MongoDB:
                catalogProperties.put("connector.name", "mongodb");
                catalogProperties.put("connection-url", buildMongodbConnUrl(userName, password, entity));
                catalogProperties.put("mongodb.case-insensitive-name-matching", "true");
                return catalogProperties;
            case Elasticsearch:
                catalogProperties.put("connector.name", "elasticsearch");
                catalogProperties.put("elasticsearch.host", entity.getStr("host"));
                catalogProperties.put("elasticsearch.port", entity.getStr("port"));
                catalogProperties.put("elasticsearch.ignore-publish-address", "true");
                catalogProperties.put("elasticsearch.default-schema-name", entity.getStr("db_name"));
                if (StrUtil.isNotBlank(userName) && StrUtil.isNotBlank(password)) {
                    catalogProperties.put("elasticsearch.security", "PASSWORD");
                    catalogProperties.put("elasticsearch.auth.user", userName);
                    catalogProperties.put("elasticsearch.auth.password", password);
                }
                return catalogProperties;
            case ClickHouse:
                catalogProperties.put("connector.name", "clickhouse");
                catalogProperties.put("connection-url", buildClickhouseConnUrl(entity));
                catalogProperties.put("connection-user", userName);
                catalogProperties.put("connection-password", password);
                return catalogProperties;
            case Redshift:
                catalogProperties.put("connector.name", "redshift");
                catalogProperties.put("connection-url", buildRedshiftConnUrl(entity));
                catalogProperties.put("connection-user", userName);
                catalogProperties.put("connection-password", password);
                return catalogProperties;
            default:
                throw new Exception("不支持的数据库,请检查 !!!");
        }
    }

   
    private static String buildMysqlConnUrl(Entity entity) {
        String suffix = entity.getStr("suffix");
        String host = entity.getStr("host");
        String port = entity.getStr("port");
        if (StrUtil.isBlank(suffix)) {
            suffix = String.format("?%s", "useSSL=false&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=Asia/Shanghai");
        } else {
            suffix = String.format("?%s", suffix);
            List<String> suffixList = StrUtil.split(suffix, "&");
            if (CollUtil.isNotEmpty(suffixList)) {
                if (!suffixList.contains("allowMultiQueries")) {
                    suffix = String.format("%s&%s", suffix, "allowMultiQueries=true");
                }
                if (!suffixList.contains("useSSL")) {
                    suffix = String.format("%s&%s", suffix, "useSSL=false");
                }
            }
        }
        return String.format("jdbc:mysql://%s:%s%s", host, port, suffix);
    }


    
    private static String buildPostGreSqlConnUrl(Entity entity) {
        String suffix = entity.getStr("suffix");
        String host = entity.getStr("host");
        String port = entity.getStr("port");
        String dbName = entity.getStr("db_name");
        suffix = StrUtil.isBlankIfStr(suffix) ? "" : "?" + suffix;
        return String.format("jdbc:postgresql://%s:%s/%s%s", host, port, dbName, suffix);
    }

    
    private static String buildSqlServerConnUrl(Entity entity) {
        String host = entity.getStr("host");
        String port = entity.getStr("port");
        String dbName = entity.getStr("db_name");
        return String.format("jdbc:sqlserver://%s:%s;DatabaseName=%s;encrypt=false", host, port, dbName);
    }

    
    private static String buildOracleConnUrl(Entity entity) {
        String suffix = entity.getStr("suffix");
        String host = entity.getStr("host");
        String port = entity.getStr("port");
        String extendParam = entity.getStr("extend_param");
        suffix = StrUtil.isBlankIfStr(suffix) ? "" : "?" + suffix;
        return String.format("jdbc:oracle:thin:@%s:%s:%s%s", host, port, extendParam, suffix);
    }

  
    private static String buildDmConnUrl(Entity entity) {
        String suffix = entity.getStr("suffix");
        String host = entity.getStr("host");
        String port = entity.getStr("port");
        String dbName = entity.getStr("db_name");
        suffix = StrUtil.isBlankIfStr(suffix) ? "" : "&" + suffix;
        return String.format("jdbc:dm://%s:%s?schema=%s%s", host, port, dbName, suffix);
    }

   
    private static String buildMongodbConnUrl(String userName, String password, Entity entity) {
        String host = entity.getStr("host");
        String port = entity.getStr("port");
        return String.format("mongodb://%s:%s@%s:%s", userName, password, host, port);
    }

    
    private static String buildClickhouseConnUrl(Entity entity) {
        String suffix = entity.getStr("suffix");
        String host = entity.getStr("host");
        String port = entity.getStr("port");
        suffix = StrUtil.isBlankIfStr(suffix) ? "" : "?" + suffix;
        return String.format("jdbc:clickhouse://%s:%s%s", host, port, suffix);
    }

   
    private static String buildRedshiftConnUrl(Entity entity) {
        String suffix = entity.getStr("suffix");
        String host = entity.getStr("host");
        String port = entity.getStr("port");
        suffix = StrUtil.isBlankIfStr(suffix) ? "" : "?" + suffix;
        return String.format("jdbc:redshift://%s:%s%s", host, port, suffix);
    }

修改CoordinatorDynamicCatalogManager文件 实现定时调度加载更新catalog配置

引入调度执行器
    private final ScheduledExecutorService refreshExecutor = newSingleThreadScheduledExecutor(daemonThreadsNamed("DynamicCatalogRefreshEvent"));
实现刷新方法
private void refreshCatalogConfig() {
        try {
            //新的数据源列表
            DatabaseCatalogStore databaseCatalogStore = (DatabaseCatalogStore) catalogStore;
            ConcurrentMap<String, DatabaseCatalogStore.DatabaseStoredCatalog> newCatalogs = new ConcurrentHashMap<>();
            databaseCatalogStore.loadDsCatalogs(newCatalogs);
            //老的数据源列表
            ConcurrentMap<String, DatabaseCatalogStore.DatabaseStoredCatalog> oldCatalogs = databaseCatalogStore.catalogs;

            //需要新增的数据源
            ConcurrentMap<String, CatalogStore.StoredCatalog> addCatalogs = new ConcurrentHashMap<>();
            //需要删除的数据源
            ConcurrentMap<String, CatalogStore.StoredCatalog> deletedCatalogs = new ConcurrentHashMap<>();


            if (CollUtil.isNotEmpty(newCatalogs) && CollUtil.isNotEmpty(oldCatalogs)) {
                for (Entry<String, DatabaseCatalogStore.DatabaseStoredCatalog> newDsStoredCatalog : newCatalogs.entrySet()) {
                    DatabaseCatalogStore.DatabaseStoredCatalog oldDsStoredCatalog = oldCatalogs.get(newDsStoredCatalog.getKey());
                    if (oldDsStoredCatalog != null) {
                        if (!sameConfig(newDsStoredCatalog.getValue(), oldDsStoredCatalog)) {
                            addCatalogs.put(newDsStoredCatalog.getKey(), newDsStoredCatalog.getValue());
                            deletedCatalogs.put(oldDsStoredCatalog.getName(), oldDsStoredCatalog);
                        }
                    } else {
                        addCatalogs.put(newDsStoredCatalog.getKey(), newDsStoredCatalog.getValue());
                    }
                }
                for (Entry<String, DatabaseCatalogStore.DatabaseStoredCatalog> oldStoredCatalog : oldCatalogs.entrySet()) {
                    if (newCatalogs.get(oldStoredCatalog.getKey()) == null) {
                        deletedCatalogs.put(oldStoredCatalog.getKey(), oldStoredCatalog.getValue());
                    }
                }
            } else if (CollUtil.isNotEmpty(newCatalogs) && CollUtil.isEmpty(oldCatalogs)) {
                for (Entry<String, DatabaseCatalogStore.DatabaseStoredCatalog> newDsStoredCatalog : newCatalogs.entrySet()) {
                    addCatalogs.put(newDsStoredCatalog.getKey(), newDsStoredCatalog.getValue());
                }
            } else if (CollUtil.isEmpty(newCatalogs) && CollUtil.isNotEmpty(oldCatalogs)) {
                for (Entry<String, DatabaseCatalogStore.DatabaseStoredCatalog> oldStoredCatalog : oldCatalogs.entrySet()) {
                    deletedCatalogs.put(oldStoredCatalog.getKey(), oldStoredCatalog.getValue());
                }
            }
            if (CollUtil.isNotEmpty(deletedCatalogs)) {
                deletedCatalogs.forEach((key, value) -> dropCatalog(key, false));
            }

            if (CollUtil.isNotEmpty(addCatalogs)) {
                addCatalogs.forEach((key, value) -> {
                    CatalogProperties catalogProperties = value.loadProperties();
                    createCatalog(key, catalogProperties.getConnectorName(), catalogProperties.getProperties(), false);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("刷新数据源配置失败,请检查 !!!", e);
        }


    }

    
    private boolean sameConfig(DatabaseCatalogStore.DatabaseStoredCatalog newDatabaseStoredCatalog, DatabaseCatalogStore.DatabaseStoredCatalog oldDatabaseStoredCatalog) {
        for (Entry<String, String> stringStringEntry : newDatabaseStoredCatalog.getProperties().entrySet()) {
            Map<String, String> properties = oldDatabaseStoredCatalog.getProperties();
            String oldValue = properties.get(stringStringEntry.getKey());
            if (StrUtil.isNotBlank(oldValue) && !oldValue.equals(stringStringEntry.getValue())) {
                return false;
            }
        }
        return true;
    }
调用刷新
ConnectorServicesProvider类增加接口方法

CoordinatorDynamicCatalogManager类实现刷新
@Override
    public void refreshCatalogs() {
        if (started.compareAndSet(false, true)) {
            refreshExecutor.scheduleWithFixedDelay(this::refreshCatalogConfig, 1, 1, TimeUnit.MINUTES);
        }
    }
主程序启动时调用刷新

 配置文件修改catalog加载配置

catalog.management=DYNAMIC
catalog.store=DATABASE
catalog.database-type=MYSQL
catalog.database-url= 数据库url
catalog.database-userName=username
catalog.database-password=password
catalog.database-table=table
catalog.database-refreshInterval=1

注意 catalog的配置表可根据自己实际情况进行修改。只需要获取到catalog 关键的配置参数即可。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值