java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java设计多实例多库查询

用Java设计实现多实例多库查询方式

作者:菠萝-琪琪

这篇文章主要介绍了用Java设计实现多实例多库查询方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

服务的边界职责 

大数据层取数统一实现入口(数据源的路由,ADB/CK/HBASE... 大数据操作层数据源的路由)

支持多实例、多库、多表的异构数据查询

通过 查询 语义分析+元信息解析,拆解 查询输入 中的异构数据源处理,所有异构数据处理采用异步 Callback 方式

解决的问题 

多个数据来源写入到不同实例、不同库中,并且一个圈选可以支持圈多个实例(不同库)中的标签、事件数据

单表数据量过大,目前只能通过压缩保留时间解决,需要可以进行按时间段拆表分表查询数据

单实例配置上限问题,实例升级配置是有上限的成本也很高,支持多实例后就可以使用多实例中等配置无限扩展

无法支持场景 

同一个圈选不支持跨不同类型数据库混用,类似一个圈选包含了ADB+CK两种类型数据库的配置

创建库表的时候,需要有原则:

不同实例的库、表名字如果完全一样,默认他们代表的业务语意也是一致的,如果不一致会出现问题

场景描述: 

架构全景图 

传统数据库中间件的几种模式: 

种类

优势

缺点

JAR方式嵌入到应用端

  • 接入简单,无需单独部署独立服务
  • 小数据量场景下速度很快,比原先直连JDBC性能损失很低
  • 调试使用简单
  • 与应用资源公用,互相影响
  • 大数量场景下会有很多连带问题
  • 对应用端代码有一定的入侵性
  • 扩展性差

中间件方式存在,伪装数据库代理层

  • 对应用代码无入侵,只需要修改配置层
  • 资源独立,不会有捆绑风险
  • 可以分层独立扩展,可扩展性强
  • 适合大数据量的场景中
  • 让应用层基本无感知中间件的存在
  • 开发维护成本高
  • 中间件编码复杂,需要对接开源数据库的各种代理协议以及字节码转换逻辑
  • 小数据量的请求场景中性能比直连的性能损耗严重30%~40%
  • 在JDBC上面无法再次扩展,传输协议和模型固定

中间件方式存在,不伪装数据库代理层

  • 中间件开发简单,无需对接各种开源数据库的代理转换协议
  • 资源独立,不会有捆绑风险
  • 可以分层独立扩展,可扩展性强
  • 适合大数据量的场景中
  • 脱离应用端到中间件的JDBC协议,扩展性自由
  • 应用接入层会有感知,配置层缺失了原先的JDBC配置,SQL操作也无法使用原先的数据库框架而是要将SQL通过Dubbo方式交互
  • 小数据量的请求场景中性能比直连的性能损耗严重30%~40%

模块说明:

模块名

执行边界

输入描述

输出描述

代理模块

模型接入

请求模型

结果模型

运行模式模块

执行线程池处理

在线模式/离线模式

执行线程池

计划模块

离线模式和在线模式都需要创建入口执行计划和执行日志

离线异构取数场景,每一层内嵌取数都是单独计划和执行日志,完成后上推执行计划

计划存在父子计划任务

创建计划

查询可执行计划任务数据

更新计划状态

可执行的计划任务

解析模块

解析查询请求的参数,转换成 AST 语法树

查询语句,可以是 SQL,可以是 JSON

也可以是规则XML

AST语法树对象、核心解析模型

权限校验模块

判断token是否有使用的实例、库、表权限

根据token,库,表查询到有权限的实例信息

是否有权限,以及有权限的实例信息

路由模块

获取有权限且最优的实例

请求来源的系统,请求来源的库,请求来源的表

符合权限校验的实例 ID+库信息

连接池管理模块

管理各种数据源的连接池

库元信息

连接池连接

数据执行模块

取数执行逻辑,根据改写后的SQL进行分实例分库分表查询,最终汇总到临时表,在进行完整原始SQL改写的执行

大数据量临时表通过生成SQL执行语句导入到OSS

取数语句分析拆分后的执行语句

异构数据的话返回实例名+库名+表名

非异构数据返回数据结果集

计费模块

根据临时表数据量,跨库+跨表数量进行公式化计费

AST语法树对象、SQL

最终成本费用

复用模块

数据复用的逻辑判断

执行语句,间隔时间

是否复用模型

熔断模块

对执行中的计划进行强制熔断,没有任何业务逻辑,只是提供熔断标示

计划ID

是否熔断

项目模块依赖描述:(开发分之:multiple-instances)(项目名:datacenter-night-watchman) 

模块间逻辑交互

运行模式模块  

计划表结构:(Mysql-watchman库) 

CREATE TABLE `extract_data_calculation_log`  (   `id` bigint(32) NOT NULL,   `exe_id` bigint(32) NOT NULL COMMENT '计划表主键ID',   `last_exe_id` bigint(32) NOT NULL COMMENT '最大用户ID',   `exe_state` tinyint(1) NOT NULL COMMENT '执行状态,1-执行中 2-执行成功  3-执行失败',   `create_time` datetime(0) NOT NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '记录创建时间',   `updat_time` datetime(0) NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '记录更新时间',   `version` int(8) NOT NULL COMMENT '记录版本号',   `exe_quantity` bigint(32) NULL COMMENT '数据冗余字段,执行的数据量',   PRIMARY KEY (`id`),   INDEX `exe`(`circle_exe_id`) ) COMMENT = '提取数据日志表';  
CREATE TABLE `extract_data_execute`  (   `id` bigint(32) NOT NULL,   `storage_result` json NOT NULL COMMENT '{"type":"存储类型,1-adb  2-rmq  3-oss  4-ck","result":"adb/ck 代表表名,rmq代表topic,oss代表存储地址","example":"实例地址","database":"库地址"}',   `create_time` timestamp(0) NOT NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '记录创建时间',   `updat_time` timestamp(0) NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '记录更新时间',   `plan_logic` tinyint(1) NOT NULL DEFAULT 1 COMMENT '计划逻辑,1-正常 2-暂不执行',   `priority` int(8) NOT NULL DEFAULT 1 COMMENT '任务优先级,数字越小优先级越高',   `data_type` tinyint(1) NOT NULL COMMENT '执行模式,1-立即执行  2-离线执行',   `parent_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '父级计划ID ',   `rewrite_result` json NOT NULL COMMENT '改写模型',   PRIMARY KEY (`id`) ) COMMENT = '数据取数计划表';  
ALTER TABLE `extract_data_calculation_log` ADD CONSTRAINT `exe` FOREIGN KEY (`circle_exe_id`) REFERENCES `extract_data_execute` (`id`); 
ALTER TABLE `extract_data_execute` ADD CONSTRAINT `config` FOREIGN KEY (`circle_config_id`) REFERENCES `circle_config` (`id`); 

查询解析模块 

Sharding5 的解析引擎已经支持多种数据库包含各种数据库新增的函数语法解析,主要是Mysql、Pg、Sqlserver、Oracle 

<dependency>     
  <groupId>org.apache.shardingsphere</groupId>     
  <artifactId>shardingsphere-sql-parser-engine</artifactId> 
</dependency> 
 
<dependency>     
  <groupId>org.apache.shardingsphere</groupId>     
  <artifactId>shardingsphere-sql-parser-mysql</artifactId> 
</dependency> 
 
<dependency>     
  <groupId>org.apache.shardingsphere</groupId>     
  <artifactId>shardingsphere-infra-federation-optimizer</artifactId> </dependency> 
  
<dependency>     
  <groupId>org.apache.calcite</groupId>     
  <artifactId>calcite-core</artifactId> 
</dependency> 

Clickhouse和ADB目前都是支持原生mysql协议的,那么进入的数据库解析方言使用mysql引擎即可 

ShardingSphere解析引擎模块代码示例: 

public static void main(String[] args) {     
    CacheOption cacheOption = new CacheOption( 128, 1024L, 4 );     			  SQLParserEngine sqlParserEngine = new SQLParserEngine( "MySQL", cacheOption, true );     
    ParseContext parseContext = sqlParserEngine.parse( "select * from user where id in (select id from city where id = 11);", true );     
    SQLVisitorEngine visitorEngine = new SQLVisitorEngine( "MySQL", "STATEMENT", new Properties() );     
    SelectStatement selectStatement = visitorEngine.visit( parseContext );      	SelectStatementConverter selectStatementConverter = new SelectStatementConverter();     
    SqlSelect sqlSelect = (SqlSelect) selectStatementConverter.convertToSQLNode( selectStatement );     
    System.out.println( sqlSelect.getSelectList() );     
    System.out.println( sqlSelect.getFrom() );     
    System.out.println( sqlSelect.getWhere() ); 
}  
输出结果: 查询字段:* 查询表:user 查询条件:`id` IN (SELECT `id` FROM `city` WHERE `id` = 11) 

解析模块注意点: 

权限校验模块:(此模块代码接口预留,逻辑暂不实现) 

改写引擎 

改写案例描述: 

改写模型描述: 

路由模块 

链接池模块 

需要支持通配符方式的连接配置,案例:

# 同一个实例下不同库的通配连接 db.datasource.watchman.jdbcUrl=jdbc:mysql://A.mysql.rds.aliyuncs.com:3306/{db1,db2,db3} 
db.datasource.watchman.username={A.db1:dw_datacenter_A,A.db2:dw_datacenter_B,A.db3:dw_datacenter_C} db.datasource.watchman.password= {A.db1:password_A,A.db2:password_B,A.db3:password_C}   
# 不同实例下同库的通配连接 db.datasource.watchman.jdbcUrl=jdbc:mysql://{A,B}.mysql.rds.aliyuncs.com:3306/db1 
db.datasource.watchman.username={A.db1:dw_datacenter_A,B.db1:dw_datacenter_B} db.datasource.watchman.password= {A.db1:password_A,B.db2:password_B}   
# 同一个实例下不同库的区间通配连接 db.datasource.watchman.jdbcUrl=jdbc:mysql://A.mysql.rds.aliyuncs.com:3306/{db[1~20]} 
db.datasource.watchman.username={A.db1:dw_datacenter_A,A.db2:dw_datacenter_B,A.db3:dw_datacenter_C,A.db...} db.datasource.watchman.password= {A.db1:password_A,A.db2:password_B,A.db3:password_C,A.db...}   
# 多数据源连接配置 db.datasource.watchman.jdbcUrl.ck=jdbc:mysql://A.mysql.rds.aliyuncs.com:3306/{db[1~20]} 
db.datasource.watchman.username.ck={A.db1:dw_datacenter_A,A.db2:dw_datacenter_B,A.db3:dw_datacenter_C,A.db...} db.datasource.watchman.password.ck= {A.db1:password_A,A.db2:password_B,A.db3:password_C,A.db...}  db.datasource.watchman.jdbcUrl.adb=jdbc:mysql://A.mysql.rds.aliyuncs.com:3306/{db[1~20]} 
db.datasource.watchman.username.adb={A.db1:dw_datacenter_A,A.db2:dw_datacenter_B,A.db3:dw_datacenter_C,A.db...} db.datasource.watchman.password.adb= {A.db1:password_A,A.db2:password_B,A.db3:password_C,A.db...} 

链接池内部使用druid框架,没有单独对数据源进行druid参数配置的话全部采用守夜人默认提供的运行参数,如果需要单独对不同数据源进行配置,那么原先druid的配置加上对应的后缀

db.datasource.watchman.jdbcUrl.adb=jdbc:mysql://A.mysql.rds.aliyuncs.com:3306/{db[1~20]} 
db.datasource.watchman.username.adb={A.db1:dw_datacenter_A,A.db2:dw_datacenter_B,A.db3:dw_datacenter_C,A.db...} db.datasource.watchman.password.adb= {A.db1:password_A,A.db2:password_B,A.db3:password_C,A.db...} 

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文