alibaba / nacos

an easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications.
https://nacos.io
Apache License 2.0
30.39k stars 12.87k forks source link

[Feature discuss] The discuss of Multi-data source plugin(多数据源插件实现讨论) #8803

Closed The-Gamer-01 closed 2 years ago

The-Gamer-01 commented 2 years ago

Nacos Multi-data source discuss

Background

The current configuration center module of Nacos is not perfect for the ability to access multiple data sources. With the growing voice of the community, we need to improve the plug-ins that support multiple data sources in Nacos. The existing SQL operations are abstracted from the SPI interface, and the implementation of Mysql is provided, and the existing SQL operations are transformed to support plug-in.

My Plan:

  1. Support for multiple data sources.
  2. Resolve SQL dialect differences between multiple data sources.
  3. Provide a Builder class to help plug-in writer write dynamic SQL statement, at the same time plug-in writer can also write SQL statement directly, this process does not do specific requirements.
  4. Keep scalability.

Architecture design

The overall architecture is shown below: image

  1. Abstract a Mapper layer on the original basis to focus on SQL writing and execution;
  2. A MapperManager class is introduced to manage the Mapper implementation of SPI.
  3. Introduce a DataSourceManager to manage multiple data sources.

The overall flowchart is shown below: image

  1. Load the data source according to the Application.properties;
  2. Use SPI to load Mapper;
  3. If there is no SPI, use the default MYSQL implementation;

Change point

  1. The SQL statement in the Config module of the NACOS is divided into the corresponding Mapper according to the table according to the table
  2. Use JDBCTEMPlate to directly execute the SQL statement method to obtain the request results by using the JDBCTEMPLETE layer in the Config module to change the required method to obtain the request result to obtain the required required method.

Scenes to be used

  1. For other data sources supported by NACOS, other data sources support developers with demand;
  2. The second developers need to expand the Config module SQL operation to provide a convenient expansion structure;

    How to use

    The main writing steps of the plug -in writer are as follows:

  3. Plug -in writers rewrite the method in the subclass of each mapper interface Mapper;
  4. You can use the implementation of the parent abstract class by default. The data source is MySQL;
  5. Write the full-limited name of the rewriting class into Meta-INF/Services/to load the SPI for a large data source plug-in;
  6. The data source transformation is completed;

How to use the user of MySQL and Derby database: No change in use;

Nacos多数据源issue讨论介绍

背景

Nacos 目前的配置中心模块对于接入多数据源的能力是不完善的,随着社区的呼声越来越高,我们需要完善Nacos 支持多数据源的插件。将现有的 SQL 操作抽象出 SPI 接口,并提供Mysql 的实现,改造现有的 SQL 操作,让它支持插件化。

我的方案:

  1. 支持多数据源;

  2. 解决多数据源间SQL方言的差别;

  3. 提供builder帮助插件编写者编写动态SQL语句,同时插件编写者也可以直接编写SQL语句,对此过程不做具体要求;

  4. 保持扩展性;

    架构设计

    整体架构如下所示: image

  5. 在原有的基础上抽象一层Mapper层去专注于SQL的编写以及执行;

  6. 引入一个MapperManager管理SPI的Mapper实现;

  7. 引入一个DataSourceManager管理多数据源;

整体流程图如下所示: image

  1. 启动后根据application.properties加载数据源;
  2. 使用SPI加载Mapper;
  3. 若无SPI那么使用默认MySQL实现;

改动点

  1. 将Nacos的Config模块中的SQL语句按表进行抽象划分为对应的Mapper
  2. 将Config模块中service层使用JdbcTemplate直接执行SQL语句获取请求结果的方法,变为service层调用所需Mapper的方法获取请求结果

使用场景

  1. 对于非老版本Nacos支持的其他数据源支持有需求的开发者;
  2. 需要扩展Config模块SQL操作的二次开发者提供方便扩展的架构;

使用方式

插件编写者的主要编写步骤如下:

  1. 插件编写者重写各个Mapper接口的子抽象类Mapper中的方法;
  2. 可默认使用父抽象类中的实现,其数据源为MySQL;
  3. 将重写后的类全限定名写入META-INF/services/下,用以多数据源插件进行SPI加载;
  4. 数据源改造完成;

MySQL以及Derby数据库的使用者的使用方式: 使用上并无改变;

li-xiao-shuang commented 2 years ago

8312 欢迎大家讨论起来,提供更好的建议

onewe commented 2 years ago

为啥不换成 jpa 🤪

five111 commented 2 years ago

说个简单的方案 当前数据源的加载是由自己初始化完成的 只需要将数据源的加载方式改为可以由外部注入就可以了 对应数据源的具体的实现类还是通过Conditional让spring去加载对应的bean就行了

The-Gamer-01 commented 2 years ago

Reference in new

因为之前Nacos内部是使用的JdbcTemplate,我先去看下JPA的相关的资料,比较一下

li-xiao-shuang commented 2 years ago

说个简单的方案 当前数据源的加载是由自己初始化完成的 只需要将数据源的加载方式改为可以由外部注入就可以了 对应数据源的具体的实现类还是通过Conditional让spring去加载对应的bean就行了

感觉没办法解决sql 方言问题,目前执行的sql 是在nacos内部固定的

The-Gamer-01 commented 2 years ago

为啥不换成 jpa 🤪

JPA主要是用作访问数据库,可以看做是Mapper的方法的一种实现,Mapper的实现的话不做限制,由插件编写者自己决定,但是我可以尝试默认的抽象类使用JPA来方便我的编写

five111 commented 2 years ago

说个简单的方案 当前数据源的加载是由自己初始化完成的 只需要将数据源的加载方式改为可以由外部注入就可以了 对应数据源的具体的实现类还是通过Conditional让spring去加载对应的bean就行了

感觉没办法解决sql 方言问题

可以解决的 具体一点是这样 以 ExternalStoragePersistServiceImpl为列 他的实现是满足mysql的方言 他的加载条件是ConditionOnExternalStorage.matches为true 我们做这样一个修改 将他的加载条件改为 满足ConditionOnExternalStorage且数据库类型为mysql 这时如果我要使用第三方数据库,比如华为的gauss数据库 那么我自己去实现PersistService接口 并且将我的加载条件改为ConditionOnExternalStorage且数据库类型为gauss 这样方言的事情只需要让使用第三方数据库的人自己去适配就可以了 ps: 我们当前就是这么搞的

wuchubuzai2018 commented 2 years ago

楼主那个方案短期内应该不适用,改动比较大吧。

我的设计说明

我本地自己基于Nacos2.1实现了一版多数据源的设计,支持核心config模块等,虽然其他辅助功能暂时未测试,常见的config操作都可以,目前支持PostgreSQL和Oracle,准备在支持下DB2,计划最近在抽取优化一下,然后开源一下。你们要是喜欢,我马上就开源,或提供Oracle版本的2.1打包的格式部署文件。 修改文件截图如下: nacos多数据源 这一个版本的设计类图如下: nacos方案一

SPI加载数据库方言设计

1、SPI初始化,增加DatabaseDialect方言类,封装分页等查询方法,使用方式为: dataSourceService = DynamicDataSource.getInstance().getDataSource(); databaseDialect = this.dataSourceService.databaseDialect();

` @Override public String getTestQuery() { return " SELECT 1"; }

@Override
public String getLimitTopSql(String sql) {
    return sql + " LIMIT ? ";
}

@Override
public String getLimitPageSql(String sql) {
    return sql + "  OFFSET ? LIMIT ? ";
}

@Override
public String getLimitPageSql(String sql, int pageNo, int pageSize) {
    return sql + "  OFFSET " + getPagePrevNum(pageNo, pageSize) + " LIMIT " + pageSize;
}

@Override
public int getPagePrevNum(int page, int pageSize) {
    return (page - 1) * pageSize;
}

@Override
public int getPageLastNum(int page, int pageSize) {
    return pageSize;
}

`

核心类分页等SQL抽取设计

2、ExternalStoragePersistServiceImpl核心的持久化类,将统一的分页更改为包装方式调用 String sql = databaseDialect.getLimitTopSql(orignSql);

通用问题解决方案设计

1、动态获取驱动配置及JDBC测试查询语句,核心逻辑在ExternalDataSourceProperties文件中,及调用数据库方言进行 2、某些查询需要返回主键问题,抽取为常量,并进行指定返回字段说明,RETURN_PRIMARY_KEYS。 3、某些方法使用LIKE '%' ? '%'问题,更改为通用的方式 4、抽取类中的查询SQL中的分页代码为sqlInner局部变量,动态去方言获取分页代码 5、ExternalStoragePaginationHelperImpl类中的fetchPage方法,更改调用为方言实现通用查询代码 6、对数据库的分页的计算方式(pageNo - 1) * pageSize,封装为2个单独方法,便于不同的数据库进行分页参数控制

PostgreSQL的兼容性问题

已解决: 1、PostgreSQL数据库脚本相关梳理 未解决: 1、针对dump业务的removeConfigHistory方法,进行重写,目前删除时,未加上分页控制,待实现如何处理

4.3、Oracle的兼容性问题:

已解决: 1、Oracle数据库脚本相关梳理 2、PERMISSIONS表的RESOURCE字段名称冲突问题,创建表的时候,加上双引号,未来看看是否重命名该字段 3、主键自增长问题,目前采用手动创建序列和触发器的方式,对于Oralce11g的脚本 4、tenant_id默认空NULL查询的问题,目前自定义子类,重写相关方法,采用增加默认存储public空间判断的方式 5、分页SQL的问题,目前采用单独封装分页起始范围的方法,进行处理 未解决: 1、用户表boolean类型问题,但是在Oracle上没有报错,存储的是1,可以用。

The-Gamer-01 commented 2 years ago

楼主那个方案短期内应该不适用,改动比较大吧。

我本地自己基于Nacos2.1实现了一版多数据源的设计,支持核心config模块等,虽然其他辅助功能暂时未测试,常见的config操作都可以,目前支持PostgreSQL和Oracle,准备在支持下DB2,计划最近在抽取优化一下,然后开源一下。你们要是喜欢,我马上就开源,或提供Oracle版本的2.1打包的格式部署文件。修改文件截图如下: nacos多数据源

我的核心设计如下: 1、SPI初始化,增加DatabaseDialect方言类,封装分页等查询方法,使用方式为: dataSourceService = DynamicDataSource.getInstance().getDataSource(); databaseDialect = this.dataSourceService.databaseDialect();

` @OverRide public String getTestQuery() { return " SELECT 1"; }

@Override
public String getLimitTopSql(String sql) {
    return sql + " LIMIT ? ";
}

@Override
public String getLimitPageSql(String sql) {
    return sql + "  OFFSET ? LIMIT ? ";
}

@Override
public String getLimitPageSql(String sql, int pageNo, int pageSize) {
    return sql + "  OFFSET " + getPagePrevNum(pageNo, pageSize) + " LIMIT " + pageSize;
}

@Override
public int getPagePrevNum(int page, int pageSize) {
    return (page - 1) * pageSize;
}

@Override
public int getPageLastNum(int page, int pageSize) {
    return pageSize;
}

` 2、ExternalStoragePersistServiceImpl核心的持久化类,将统一的分页更改为包装方式调用 String sql = databaseDialect.getLimitTopSql(orignSql);

这个是相当于写了个方言Builder吗,我之前考虑过这种方案,参考了jOOQ和myBatis-dynamic-sql,但是我考虑到以后Nacos可能会有扩展,而且issue方面有要求抽离SQL,所以我是直接把SQL语句都抽象出来一个单独的层专门做SQL的执行和编写

The-Gamer-01 commented 2 years ago

说个简单的方案 当前数据源的加载是由自己初始化完成的 只需要将数据源的加载方式改为可以由外部注入就可以了 对应数据源的具体的实现类还是通过Conditional让spring去加载对应的bean就行了

感觉没办法解决sql 方言问题

可以解决的 具体一点是这样 以 ExternalStoragePersistServiceImpl为列 他的实现是满足mysql的方言 他的加载条件是ConditionOnExternalStorage.matches为true 我们做这样一个修改 将他的加载条件改为 满足ConditionOnExternalStorage且数据库类型为mysql 这时如果我要使用第三方数据库,比如华为的gauss数据库 那么我自己去实现PersistService接口 并且将我的加载条件改为ConditionOnExternalStorage且数据库类型为gauss 这样方言的事情只需要让使用第三方数据库的人自己去适配就可以了 ps: 我们当前就是这么搞的

我大概理解了你的意思,就相当于是用Spring的Conditional注解去条件判断加载哪个数据库的具体实现,我去比对一下具体的方案,我感觉可以参考一下

li-xiao-shuang commented 2 years ago

说个简单的方案 当前数据源的加载是由自己初始化完成的 只需要将数据源的加载方式改为可以由外部注入就可以了 对应数据源的具体的实现类还是通过Conditional让spring去加载对应的bean就行了

感觉没办法解决sql 方言问题

可以解决的 具体一点是这样 以 ExternalStoragePersistServiceImpl为列 他的实现是满足mysql的方言 他的加载条件是ConditionOnExternalStorage.matches为true 我们做这样一个修改 将他的加载条件改为 满足ConditionOnExternalStorage且数据库类型为mysql 这时如果我要使用第三方数据库,比如华为的gauss数据库 那么我自己去实现PersistService接口 并且将我的加载条件改为ConditionOnExternalStorage且数据库类型为gauss 这样方言的事情只需要让使用第三方数据库的人自己去适配就可以了 ps: 我们当前就是这么搞的

我理解你这个方案 是要自己实现增删改查 本质上和这个插件设计是一样的

li-xiao-shuang commented 2 years ago

楼主那个方案短期内应该不适用,改动比较大吧。

我的设计说明

我本地自己基于Nacos2.1实现了一版多数据源的设计,支持核心config模块等,虽然其他辅助功能暂时未测试,常见的config操作都可以,目前支持PostgreSQL和Oracle,准备在支持下DB2,计划最近在抽取优化一下,然后开源一下。你们要是喜欢,我马上就开源,或提供Oracle版本的2.1打包的格式部署文件。修改文件截图如下: nacos多数据源

SPI加载数据库方言设计

1、SPI初始化,增加DatabaseDialect方言类,封装分页等查询方法,使用方式为: dataSourceService = DynamicDataSource.getInstance().getDataSource(); databaseDialect = this.dataSourceService.databaseDialect();

` @OverRide public String getTestQuery() { return " SELECT 1"; }

@Override
public String getLimitTopSql(String sql) {
    return sql + " LIMIT ? ";
}

@Override
public String getLimitPageSql(String sql) {
    return sql + "  OFFSET ? LIMIT ? ";
}

@Override
public String getLimitPageSql(String sql, int pageNo, int pageSize) {
    return sql + "  OFFSET " + getPagePrevNum(pageNo, pageSize) + " LIMIT " + pageSize;
}

@Override
public int getPagePrevNum(int page, int pageSize) {
    return (page - 1) * pageSize;
}

@Override
public int getPageLastNum(int page, int pageSize) {
    return pageSize;
}

`

核心类分页等SQL抽取设计

2、ExternalStoragePersistServiceImpl核心的持久化类,将统一的分页更改为包装方式调用 String sql = databaseDialect.getLimitTopSql(orignSql);

通用问题解决方案设计

1、动态获取驱动配置及JDBC测试查询语句,核心逻辑在ExternalDataSourceProperties文件中,及调用数据库方言进行 2、某些查询需要返回主键问题,抽取为常量,并进行指定返回字段说明,RETURN_PRIMARY_KEYS。 3、某些方法使用LIKE '%' ? '%'问题,更改为通用的方式 4、抽取类中的查询SQL中的分页代码为sqlInner局部变量,动态去方言获取分页代码 5、ExternalStoragePaginationHelperImpl类中的fetchPage方法,更改调用为方言实现通用查询代码 6、对数据库的分页的计算方式(pageNo - 1) * pageSize,封装为2个单独方法,便于不同的数据库进行分页参数控制

PostgreSQL的兼容性问题

已解决: 1、PostgreSQL数据库脚本相关梳理 未解决: 1、针对dump业务的removeConfigHistory方法,进行重写,目前删除时,未加上分页控制,待实现如何处理

4.3、Oracle的兼容性问题:

已解决: 1、Oracle数据库脚本相关梳理 2、PERMISSIONS表的RESOURCE字段名称冲突问题,创建表的时候,加上双引号,未来看看是否重命名该字段 3、主键自增长问题,目前采用手动创建序列和触发器的方式,对于Oralce11g的脚本 4、tenant_id默认空NULL查询的问题,目前自定义子类,重写相关方法,采用增加默认存储public空间判断的方式 5、分页SQL的问题,目前采用单独封装分页起始范围的方法,进行处理 未解决: 1、用户表boolean类型问题,但是在Oracle上没有报错,存储的是1,可以用。

我理解你这个是就针对例如分页等特殊处理了下?

wuchubuzai2018 commented 2 years ago

8312 欢迎大家讨论起来,提供更好的建议

目前是处理了分页和其他基础相关,nacos大部分不兼容都在SELECT方面,其次就是DELETE分页删除方面,还有些其他辅助类写死的mysql语法支持。 短期内抽取SQL估计改动大。我们公司的BI系统也是类似的设计。

five111 commented 2 years ago

说个简单的方案 当前数据源的加载是由自己初始化完成的 只需要将数据源的加载方式改为可以由外部注入就可以了 对应数据源的具体的实现类还是通过Conditional让spring去加载对应的bean就行了

感觉没办法解决sql 方言问题

可以解决的 具体一点是这样 以 ExternalStoragePersistServiceImpl为列 他的实现是满足mysql的方言 他的加载条件是ConditionOnExternalStorage.matches为true 我们做这样一个修改 将他的加载条件改为 满足ConditionOnExternalStorage且数据库类型为mysql 这时如果我要使用第三方数据库,比如华为的gauss数据库 那么我自己去实现PersistService接口 并且将我的加载条件改为ConditionOnExternalStorage且数据库类型为gauss 这样方言的事情只需要让使用第三方数据库的人自己去适配就可以了 ps: 我们当前就是这么搞的

我理解你这个方案 是要自己实现增删改查 本质上和这个插件设计是一样的

是的 相对来说 nacos现在的源码修改量较小

wuchubuzai2018 commented 2 years ago

楼主的方案,个人觉得可以补充的是: 1、建议不影响Nacos核心外部调用PersistService的引用,同时将JdbcTemplate下沉到DB实现层 2、SPI不建议去封装那12张表的Mapper类,可以将那12张表的mapper组合到一个门面类里面,暴露的是多种数据库门面类的SPI,每个具体的SPI里面可以去维护Mapper 3、同时在PersistService类中引入委派模式,比如ExternalPersistDelegeService让,然后在委派里面调用SPI具体的某一个数据库的门面实现。 针对楼主的方案,个人补充的图片:

SPI委派封装实现

li-xiao-shuang commented 2 years ago

楼主的方案,个人觉得可以补充的是: 1、建议不影响Nacos核心外部调用PersistService的引用,同时将JdbcTemplate下沉到DB实现层 2、SPI不建议去封装那12张表的Mapper类,可以将那12张表的mapper组合到一个门面类里面,暴露的是多种数据库门面类的SPI,每个具体的SPI里面可以去维护Mapper 3、同时在PersistService类中引入委派模式,比如ExternalPersistDelegeService让,然后在委派里面调用SPI具体的某一个数据库的门面实现。 针对楼主的方案,个人补充的图片:

SPI委派封装实现

jdbc 是不需要放到spi的实现的 ,spi的实现只作为插件,目标是通过 spi 返回 可执行的sql ,由 nacos 来执行。我认为 不影响Nacos核心外部调用PersistService的引用 这点是可以考虑的

YunWZ commented 2 years ago

是否可以考虑使用spring-data-jdbc的功能?

The-Gamer-01 commented 2 years ago

是否可以考虑使用spring-data-jdbc的功能?

感觉Nacos毕竟不是数据库中间件,引入其他的ORM框架感觉是不是有点重了,JdbcTemplate 是对jdbc 封装的模板,灵活性高一些。并且nacos的相关表的操作都是固定下来的,短期内基本不会改动。所以就想先基于现有的方式改造下,

The-Gamer-01 commented 2 years ago

楼主的方案,个人觉得可以补充的是: 1、建议不影响Nacos核心外部调用PersistService的引用,同时将JdbcTemplate下沉到DB实现层 2、SPI不建议去封装那12张表的Mapper类,可以将那12张表的mapper组合到一个门面类里面,暴露的是多种数据库门面类的SPI,每个具体的SPI里面可以去维护Mapper 3、同时在PersistService类中引入委派模式,比如ExternalPersistDelegeService让,然后在委派里面调用SPI具体的某一个数据库的门面实现。 针对楼主的方案,个人补充的图片:

SPI委派封装实现

第一点我觉得确实不能影响对于外部调用PersistService的引用,可以做下修改;jdbc没有做spi中实现,这里面的SPI主要的是返回SQL语句的应该算是; 第二点我大概理解了就是希望能够将12个mapper组合到一个类中,然后插件编写者去实现这个SPI,在这个类中自己维护mapper;我觉得这种方法应该属于是在原有方案上的优化,因为mapper其实还是需要插件编写者自己去维护; 第三点我觉得SPI的可以通过单例的管理类去获取,我觉得可以暂时不用动的样子;

beijixing1745 commented 2 years ago
  1. EmbeddedStorage、ExternalStorage的很多操作都写死了,建议把所有数据库存储方式统一对待,抽象出一套config storage需要的接口专门在一个package 如“com.alibaba.nacos.config.storage”,不同的storage实现放到不同的包如:“com.alibaba.nacos.config.storage.mysql”,“com.alibaba.nacos.config.storage.derby”,定制只要实现增加新的数据库接口实现就行了。
  2. 不同的接口实现可以通过bean factory获取实现bean,或不把实现直接声明为bean,集中到一个config类声明为bean,再在config类通过config condition注解,通过配置来判断使用那个数据库实现。
The-Gamer-01 commented 2 years ago
  1. EmbeddedStorage、ExternalStorage的很多操作都写死了,建议把所有数据库存储方式统一对待,抽象出一套config storage需要的接口专门在一个package 如“com.alibaba.nacos.config.storage”,不同的storage实现放到不同的包如:“com.alibaba.nacos.config.storage.mysql”,“com.alibaba.nacos.config.storage.derby”,定制只要实现增加新的数据库接口实现就行了。
  2. 不同的接口实现可以通过bean factory获取实现bean,或不把实现直接声明为bean,集中到一个config类声明为bean,再在config类通过config condition注解,通过配置来判断使用那个数据库实现。

现在的方案跟这个差不多,有些微差别但是我也注意到了这个问题;第二点的话工厂模式的话可能得慢慢来,因为这是一个大工程。

beijixing1745 commented 2 years ago
  1. EmbeddedStorage、ExternalStorage的很多操作都写死了,建议把所有数据库存储方式统一对待,抽象出一套config storage需要的接口专门在一个package 如“com.alibaba.nacos.config.storage”,不同的storage实现放到不同的包如:“com.alibaba.nacos.config.storage.mysql”,“com.alibaba.nacos.config.storage.derby”,定制只要实现增加新的数据库接口实现就行了。
  2. 不同的接口实现可以通过bean factory获取实现bean,或不把实现直接声明为bean,集中到一个config类声明为bean,再在config类通过config condition注解,通过配置来判断使用那个数据库实现。

现在的方案跟这个差不多,有些微差别但是我也注意到了这个问题;第二点的话工厂模式的话可能得慢慢来,因为这是一个大工程。

1.建议先把持久化的类统一下package:如 “com.alibaba.nacos.config.storage”、“com.alibaba.nacos.config.storage.mysql”、“com.alibaba.nacos.config.storage.derby”,这样增加新数据库支撑可以明确知道修改那些东西。 2.数据库连接池的配置项,通过配置可以配置,不要写死到类里,如:“connection-test-query:SELECT 1 FROM DUAL” `datasource: type: com.zaxxer.hikari.HikariDataSource

platform: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/xxxx
username: test
password: test

hikari:
  minimum-idle: 20
  maximum-pool-size: 50
  auto-commit: true
  idle-timeout: 30000
  pool-name: DatebookHikariCP
  max-lifetime: 1800000
  connection-timeout: 30000
  connection-test-query: SELECT 1 FROM DUAL;`

3.数据库的差异实际并不太大,因为有些代码常量配死导致不同数据库扩展难。 如:ExternalDataSourceProperties 。。。。。。 HikariDataSource ds = poolProperties.getDataSource(); ds.setConnectionTestQuery(TEST_QUERY); 。。。。。。 4.技术含量最低的方式把用到所有的sql,写到配置文件,只要提供不同数据库的sql文件就可以了。

beijixing1745 commented 2 years ago
  1. EmbeddedStorage、ExternalStorage的很多操作都写死了,建议把所有数据库存储方式统一对待,抽象出一套config storage需要的接口专门在一个package 如“com.alibaba.nacos.config.storage”,不同的storage实现放到不同的包如:“com.alibaba.nacos.config.storage.mysql”,“com.alibaba.nacos.config.storage.derby”,定制只要实现增加新的数据库接口实现就行了。
  2. 不同的接口实现可以通过bean factory获取实现bean,或不把实现直接声明为bean,集中到一个config类声明为bean,再在config类通过config condition注解,通过配置来判断使用那个数据库实现。

现在的方案跟这个差不多,有些微差别但是我也注意到了这个问题;第二点的话工厂模式的话可能得慢慢来,因为这是一个大工程。

1.建议先把持久化的类统一下package:如 “com.alibaba.nacos.config.storage”、“com.alibaba.nacos.config.storage.mysql”、“com.alibaba.nacos.config.storage.derby”,这样增加新数据库支撑可以明确知道修改那些东西。 2.数据库连接池的配置项,通过配置可以配置,不要写死到类里,如:“connection-test-query:SELECT 1 FROM DUAL” `datasource: type: com.zaxxer.hikari.HikariDataSource

platform: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/xxxx
username: test
password: test

hikari:
  minimum-idle: 20
  maximum-pool-size: 50
  auto-commit: true
  idle-timeout: 30000
  pool-name: DatebookHikariCP
  max-lifetime: 1800000
  connection-timeout: 30000
  connection-test-query: SELECT 1 FROM DUAL;`

3.数据库的差异实际并不太大,因为有些代码常量配死导致不同数据库扩展难。 如:ExternalDataSourceProperties 。。。。。。 HikariDataSource ds = poolProperties.getDataSource(); ds.setConnectionTestQuery(TEST_QUERY); 。。。。。。 4.技术含量最低的方式把用到所有的sql,写到配置文件,只要提供不同数据库的sql文件就可以了。

wang3develop commented 2 years ago

我认为还是不要使用SQL而换做ORM框架比较好,毕竟对于SQL里某些变动有时候可能找不全要修改的地方,而使用ORM框架后编译就可立即找到变更处,同时对于SQL方言的复杂度都交给ORM框架处理,对于ORM对付不了了的,按照不同的数据库先抽到一个类临时放着。 platform那个参数就弄成Profile注解里的参数(SpringApplication.setAdditionalProfiles(…​)),让其按照不同的数据库激活业务实现类,相同的地方可以抽取出抽象类。 可以用spring推荐的jOOQ重写捣鼓个第三版本,先弄两种数据库试试。

qhyjoe commented 2 years ago

楼主那个方案短期内应该不适用,改动比较大吧。

我的设计说明

我本地自己基于Nacos2.1实现了一版多数据源的设计,支持核心config模块等,虽然其他辅助功能暂时未测试,常见的config操作都可以,目前支持PostgreSQL和Oracle,准备在支持下DB2,计划最近在抽取优化一下,然后开源一下。你们要是喜欢,我马上就开源,或提供Oracle版本的2.1打包的格式部署文件。 修改文件截图如下: nacos多数据源 这一个版本的设计类图如下: nacos方案一

SPI加载数据库方言设计

1、SPI初始化,增加DatabaseDialect方言类,封装分页等查询方法,使用方式为: dataSourceService = DynamicDataSource.getInstance().getDataSource(); databaseDialect = this.dataSourceService.databaseDialect();

` @OverRide public String getTestQuery() { return " SELECT 1"; }

@Override
public String getLimitTopSql(String sql) {
    return sql + " LIMIT ? ";
}

@Override
public String getLimitPageSql(String sql) {
    return sql + "  OFFSET ? LIMIT ? ";
}

@Override
public String getLimitPageSql(String sql, int pageNo, int pageSize) {
    return sql + "  OFFSET " + getPagePrevNum(pageNo, pageSize) + " LIMIT " + pageSize;
}

@Override
public int getPagePrevNum(int page, int pageSize) {
    return (page - 1) * pageSize;
}

@Override
public int getPageLastNum(int page, int pageSize) {
    return pageSize;
}

`

核心类分页等SQL抽取设计

2、ExternalStoragePersistServiceImpl核心的持久化类,将统一的分页更改为包装方式调用 String sql = databaseDialect.getLimitTopSql(orignSql);

通用问题解决方案设计

1、动态获取驱动配置及JDBC测试查询语句,核心逻辑在ExternalDataSourceProperties文件中,及调用数据库方言进行 2、某些查询需要返回主键问题,抽取为常量,并进行指定返回字段说明,RETURN_PRIMARY_KEYS。 3、某些方法使用LIKE '%' ? '%'问题,更改为通用的方式 4、抽取类中的查询SQL中的分页代码为sqlInner局部变量,动态去方言获取分页代码 5、ExternalStoragePaginationHelperImpl类中的fetchPage方法,更改调用为方言实现通用查询代码 6、对数据库的分页的计算方式(pageNo - 1) * pageSize,封装为2个单独方法,便于不同的数据库进行分页参数控制

PostgreSQL的兼容性问题

已解决: 1、PostgreSQL数据库脚本相关梳理 未解决: 1、针对dump业务的removeConfigHistory方法,进行重写,目前删除时,未加上分页控制,待实现如何处理

4.3、Oracle的兼容性问题:

已解决: 1、Oracle数据库脚本相关梳理 2、PERMISSIONS表的RESOURCE字段名称冲突问题,创建表的时候,加上双引号,未来看看是否重命名该字段 3、主键自增长问题,目前采用手动创建序列和触发器的方式,对于Oralce11g的脚本 4、tenant_id默认空NULL查询的问题,目前自定义子类,重写相关方法,采用增加默认存储public空间判断的方式 5、分页SQL的问题,目前采用单独封装分页起始范围的方法,进行处理 未解决: 1、用户表boolean类型问题,但是在Oracle上没有报错,存储的是1,可以用。

楼主那个方案短期内应该不适用,改动比较大吧。

我的设计说明

我本地自己基于Nacos2.1实现了一版多数据源的设计,支持核心config模块等,虽然其他辅助功能暂时未测试,常见的config操作都可以,目前支持PostgreSQL和Oracle,准备在支持下DB2,计划最近在抽取优化一下,然后开源一下。你们要是喜欢,我马上就开源,或提供Oracle版本的2.1打包的格式部署文件。 修改文件截图如下: nacos多数据源 这一个版本的设计类图如下: nacos方案一

SPI加载数据库方言设计

1、SPI初始化,增加DatabaseDialect方言类,封装分页等查询方法,使用方式为: dataSourceService = DynamicDataSource.getInstance().getDataSource(); databaseDialect = this.dataSourceService.databaseDialect();

` @OverRide public String getTestQuery() { return " SELECT 1"; }

@Override
public String getLimitTopSql(String sql) {
    return sql + " LIMIT ? ";
}

@Override
public String getLimitPageSql(String sql) {
    return sql + "  OFFSET ? LIMIT ? ";
}

@Override
public String getLimitPageSql(String sql, int pageNo, int pageSize) {
    return sql + "  OFFSET " + getPagePrevNum(pageNo, pageSize) + " LIMIT " + pageSize;
}

@Override
public int getPagePrevNum(int page, int pageSize) {
    return (page - 1) * pageSize;
}

@Override
public int getPageLastNum(int page, int pageSize) {
    return pageSize;
}

`

核心类分页等SQL抽取设计

2、ExternalStoragePersistServiceImpl核心的持久化类,将统一的分页更改为包装方式调用 String sql = databaseDialect.getLimitTopSql(orignSql);

通用问题解决方案设计

1、动态获取驱动配置及JDBC测试查询语句,核心逻辑在ExternalDataSourceProperties文件中,及调用数据库方言进行 2、某些查询需要返回主键问题,抽取为常量,并进行指定返回字段说明,RETURN_PRIMARY_KEYS。 3、某些方法使用LIKE '%' ? '%'问题,更改为通用的方式 4、抽取类中的查询SQL中的分页代码为sqlInner局部变量,动态去方言获取分页代码 5、ExternalStoragePaginationHelperImpl类中的fetchPage方法,更改调用为方言实现通用查询代码 6、对数据库的分页的计算方式(pageNo - 1) * pageSize,封装为2个单独方法,便于不同的数据库进行分页参数控制

PostgreSQL的兼容性问题

已解决: 1、PostgreSQL数据库脚本相关梳理 未解决: 1、针对dump业务的removeConfigHistory方法,进行重写,目前删除时,未加上分页控制,待实现如何处理

4.3、Oracle的兼容性问题:

已解决: 1、Oracle数据库脚本相关梳理 2、PERMISSIONS表的RESOURCE字段名称冲突问题,创建表的时候,加上双引号,未来看看是否重命名该字段 3、主键自增长问题,目前采用手动创建序列和触发器的方式,对于Oralce11g的脚本 4、tenant_id默认空NULL查询的问题,目前自定义子类,重写相关方法,采用增加默认存储public空间判断的方式 5、分页SQL的问题,目前采用单独封装分页起始范围的方法,进行处理 未解决: 1、用户表boolean类型问题,但是在Oracle上没有报错,存储的是1,可以用。

你好,我们最近打算使用postgresql替换mysql,能否开源你的代码

wuchubuzai2018 commented 2 years ago

你好,我们最近打算使用postgresql替换mysql,能否开源你的代码

可以看下我的Github个人主页,之前已经放到仓库中了,欢迎Star

limng06 commented 2 years ago

并不觉得引入orm框架很重,nacos做好自己的事情就好了,为什么还要去考虑适配什么什么数据库呢交给orm去做多好,orm才是应该考虑数据库方言的地方。

limng06 commented 2 years ago

方向不对啊,总是动不动就要SPI扩展,真的好用吗,友好吗? 我只想引入个jdbc包,改下配置即生效。

wuwen5 commented 1 year ago

方向不对啊,总是动不动就要SPI扩展,真的好用吗,友好吗? 我只想引入个jdbc包,改下配置即生效。

分享一下我的方案 (https://github.com/wuwen5/ojdbc-mysql2oracle)

  1. ojdbc-mysql2oracle-x.y.z.jar(自行打包)放入nacos安装目录target目录下

  2. 修改startup.sh

    export MYSQL_ORACLE_AGENT="ojdbc-mysql2oracle-1.0.2-SNAPSHOT.jar"
    JAVA_OPT="${JAVA_OPT} -javaagent:${BASE_DIR}/target/${MYSQL_ORACLE_AGENT} -jar ${BASE_DIR}/target/${SERVER}.jar"

    目前Oracle实现已在nacos-1.4、nacos-2.1、xxl-job项目中使用,仅供参考,其他数据库转换可自行实现。

douxiaofeng99 commented 1 year ago

我尝试自己写一个单独的plugin,参考了所谓官方文档,里面非常的不详细,配置spi的地方需要在nacos自己plugin工程里配置,就是说不能配置在自己单独的插件jar里面。文档也没说明如何nacos-server.jar如何依赖外部包来启动。导致我只能解开,将包加进去,然后才可以正常。感觉这种方式不优雅。当然,后来找到了bootjar怎么依赖外部启动,但不知道是不是可以定义多个spi。com.alibaba.nacos.plugin.datasource.mapper.Mapper

dylan-tao commented 1 year ago

你好,我们最近打算使用postgresql替换mysql,能否开源你的代码

可以看下我的Github个人主页,之前已经放到仓库中了,欢迎Star

目前我这边已经开源支持postgresql的nacos-2.2.0版本(已投产),可以直接食用:https://github.com/dylan-tao/nacos-gaussdb