1 ShardingSphere-JDBC 内核工作原理

  • 当往 ShardingSphere 提交一个逻辑SQL后,ShardingSphere 到底做了哪些事情呢?首先要从 ShardingSphere 官方提供的这张整体架构图说起:

    在这里插入图片描述

1.1 配置管控

  • 在 SQL 进入 ShardingSphere 内核处理(如解析、路由、重写等)之前,ShardingSphere 会先对应用的配置信息进行处理。这些配置可能涉及:

    • 数据库分片规则(哪些表分片、分片键是什么、分片算法如何);

    • 读写分离规则(读请求和写请求路由到哪些库);

    • 数据加密规则(哪些字段需要加密、加密算法是什么)等;

  • ShardingSphere 不仅能解析应用本地的配置,还支持将配置信息存储到 第三方注册中心(如 Nacos、ZooKeeper 等)。这样做的价值是:

    • 实现应用层的水平扩展:多个应用实例可共享注册中心的配置,无需每个实例单独维护配置,集群扩容时更高效;

    • 配置集中管理:运维人员能在注册中心统一修改、下发配置,无需逐个修改应用配置,降低维护成本;

  • ShardingSphere-JDBC vs ShardingProxy

    • ShardingSphere-JDBC:作为客户端侧的数据库中间件(以 Jar 包形式集成到应用中),应用本身可以自己管理配置(比如在应用配置文件里写规则),或者自行接入 Nacos 等配置中心。因此,配置管控对 ShardingSphere-JDBC 来说,不是特别亮眼的功能(因为应用有其他替代方案);

    • ShardingProxy:作为服务端侧的数据库中间件(对外提供数据库服务,应用像连普通数据库一样连 Proxy),运维人员通过 Proxy 管理多应用的数据库访问规则。此时,配置管控(尤其是对接注册中心实现集中配置)的价值就非常突出——能更高效地管理多应用、多节点的配置。

1.2 SQL Parser:SQL解析引擎

  • SQL 解析分为两步:

    • 词法解析:把 SQL 拆成不可再分的原子符号(Token),并根据不同数据库方言锁提供的字典,将其归类为关键字(如 SELECT FROM WHERE)、表达式、字面量(如 'ACTIVE' 18)、操作符(如 = > AND)等;

    • 语法解析:基于词法解析的结果,将 SQL 转换为抽象语法树(AST,Abstract Syntax Tree)——用“树结构”表达 SQL 的逻辑结构,例:

      SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18
      

      在这里插入图片描述

  • ShardingSphere 对 SQL 解析引擎的选择,经历了多个阶段:

    • 1.4.x 及之前:用 Druid(性能较快的开源解析引擎);

    • 1.5.x 版本:自研解析引擎。针对分库分表场景,采用对 SQL 半理解的方式,提升解析性能和兼容性(更适配中间件的业务需求);

    • 3.0.x 及之后:改用 ANTLR(开源的 SQL 解析引擎)。ANTLR 被很多开源产品采用(如 Druid、Flink、Hive 等),通用性和扩展性更强。

1.3 SQL Router:SQL 路由引擎

  • 路由引擎的关键是分片键(决定数据分片的字段):

    • 携带分片键的SQL:走分片路由——根据分片键的操作符(如=单片路由、IN多片路由、BETWEEN范围路由),匹配数据库/表的分片策略,生成精准的路由路径(明确该SQL该访问哪些分片);

    • 不携带分片键的SQL:走广播路由——因为没分片键,无法精准定位分片,需向所有相关数据库/表广播执行(但广播路由影响大,不利于集群管理,所以实际应尽量用携带分片键的SQL);

  • 分片路由又因SQL场景不同,分为多种子路由:

    • 直接路由:通过hint(手动指定路由规则),强制SQL路由到特定分片;

    • 标准路由:单表或绑定表(关联时可视为单表的表)的SQL,按分片规则精准路由;

    • 笛卡尔路由:多表且无绑定表关系的关联查询,需对多表分片做笛卡尔积组合路由(性能较差,应尽量避免);

  • 不携带分片键时,不同类型的SQL(如DQL查询、DML增删改、DDL建表、DCL权限操作等),广播路由也细分了多种子路由:

    • 全库表路由:DQL/DML/DDL类语句(如select * from course),遍历所有库的所有表执行;

    • 全库路由:设置类DAL/TCL语句(如set autocommit=0),遍历所有库执行;

    • 全实例路由:DCL语句(如CREATE USER),每个数据库实例执行一次;

    • 单播路由:查询类DAL语句(如DESCRIBE course),仅从任意一个库表获取元数据;

    • 阻断路由:像USE database这类对虚拟库的操作,直接阻断(因为中间件的库是虚拟的,无需切换真实库)。

1.4 SQL Rewriter: SQL 优化引擎

  • ShardingSphere 能实现不同数据库方言之间的自动转换

    • 例如:用 MySQL 客户端发送 MySQL 方言的 SQL 给 ShardingSphere,ShardingSphere 会识别目标存储节点类型(比如要访问 PostgreSQL、MariaDB),自动把 SQL 转成对应数据库的方言,再下发执行;
    • 这让用户可以面向逻辑库/逻辑表写 SQL,无需关心底层不同数据库的语法差异,ShardingSphere 会负责“翻译”;

    在这里插入图片描述

  • SQL 改写分为正确性改写优化改写,目的是让 SQL 能在真实数据库中正确执行或更高效执行;

  • 正确性改写:让 SQL 能正确执行。解决逻辑库表到真实库表的匹配问题,确保 SQL 语义准确。包含以下能力:

    • 标识符改写:修改表名、索引名、Schema(数据库名)等标识符。比如分表场景下,把逻辑表名(如 t_order)改写成真实的分片表名(如 t_order_0 t_order_1);

    • 补列:为 SQL 补充必要的列,保证执行逻辑正确。比如:

      • 排序补列:若排序依赖分片键,补充分片键列确保排序逻辑对;
      • 分组补列:分组查询时补充分片键列,保证分组正确;
      • 聚合补列:聚合(如 COUNT SUM)时补充相关列;
      • 自增主键补列:处理分布式场景下自增主键的生成与填充;
    • 分页修正:分库分表后,分页逻辑可能跨多个分片,需修正分页参数,保证结果正确;

    • 批量拆分:把批量操作(如批量插入、IN 条件包含大量值)拆分成小批量,避免单条 SQL 过大或触发数据库限制;

  • 优化改写:让 SQL 执行更高效。在不影响 SQL 正确性的前提下,提升执行性能。包含:

    • 单节点优化:针对单个数据库节点的 SQL 执行逻辑优化;

    • 流式归并优化:对多分片返回的结果,用流式归并的方式聚合,减少内存占用、提升响应速度(比如多分片的查询结果,边查边合并,而非等所有分片都查完再合并);

在这里插入图片描述

1.5 SQL Executor: SQL执行引擎

  • ShardingSphere 采用一套自动化的执行引擎,负责将路由和改写完成之后的真实 SQL 安全且高效发送到底层数据源执行;

    • ShardingSphere 的执行引擎,不是简单用 JDBC 直连数据库发 SQL,也不是直接把请求丢进线程池并发执行。它更关注平衡资源消耗

      • 控制数据库连接创建的开销;

      • 控制内存占用的消耗;

      • 最大化利用并发能力

    • 最终实现自动化平衡资源控制与执行效率

  • 执行流程

    在这里插入图片描述

    • 准备阶段

      • 结果集分组:把要执行的 SQL 按目标数据源分组(确定哪些 SQL 要发给哪个数据库);
      • 获取连接 & 创建执行单元:为每组 SQL 获取数据库连接,并封装成执行单元(包含 SQL、连接等信息);
      • 锁数据源(可选):若满足“结果集数量≠1 且 内存限制模式”,会锁定数据源(避免并发冲突);
    • 执行阶段

      • 分组执行:按分组,执行每个执行单元里的 SQL;
      • 事件发送:执行过程中,触发分布式事务订阅(保证分布式场景下事务一致性)和性能跟踪订阅(监控执行性能);
      • 查询结果集:以流式内存方式获取结果(流式适合大数据量,减少内存爆仓;内存适合小数据量,提升读取速度);
  • 执行模式由每个数据库连接需执行的 SQL 数量决定,而这个数量的计算公式是:

    每个数据库连接需执行的SQL数量=所有需在该数据库上执行的SQL数量maxConnectionSizePerQuery \text{每个数据库连接需执行的SQL数量} = \frac{\text{所有需在该数据库上执行的SQL数量}}{\text{maxConnectionSizePerQuery}} 每个数据库连接需执行的SQL数量=maxConnectionSizePerQuery所有需在该数据库上执行的SQL数量

    • 所有需在该数据库上执行的SQL数量是路由至该数据源的路由结果;
    • maxConnectionSizePerQuery是用户配置项;
  • 基于这个数量,分为两种模式:

    • 内存限制模式

      • 条件:每个数据库连接需执行的 SQL 数量 ≤ 1(即 = 0 或 1);

      • 逻辑:一个 JDBC 连接只执行 1 条 SQL;ShardingSphere 不限制一次操作消耗的数据库连接总数(比如要执行 10 条 SQL,可能开 10 个连接,每个连执行 1 条);

      • 适合场景:SQL 执行耗时短、并发不高,优先减少单连接压力,用多连接提升并行度;

    • 连接限制模式

      • 条件:每个数据库连接需执行的 SQL 数量 > 1

      • 逻辑:一个 JDBC 连接要执行多条 SQL;ShardingSphere 严格控制一次操作消耗的数据库连接总数(比如要执行 10 条 SQL,可能只开 2 个连接,每个连接执行 5 条);

      • 适合场景:数据库连接资源宝贵(比如连接池大小有限),优先节省连接数,用单连接执行多 SQL 减少连接开销。

1.6 Result Merger:结果归并

  • 当 SQL 涉及多分片(多个数据节点)时,每个分片会返回部分结果。结果归并就是把这些分散的结果集组合成一个完整的结果集,再返回给客户端;

  • 归并引擎会根据 SQL 的分页、分组、排序、聚合等需求,选择不同的归并策略:

    在这里插入图片描述

  • 带分页的场景 → 分页归并:处理需要分页的查询(如 LIMIT 语句),确保最终结果的分页逻辑正确(比如从多分片结果中,筛选出符合页码的记录);

  • 分组/排序/无分组排序的场景

    • 分组归并:若 SQL 有分组GROUP BY)需求,对多分片结果按分组规则合并(又分流式分组归并内存分组归并,取决于排序分组列是否相同);

    • 排序归并:若 SQL 有排序ORDER BY)需求,对多分片结果按排序规则合并;

    • 迭代归并:若 SQL 既无分组也无排序,直接迭代合并多分片结果;

  • 带聚合的场景 → 聚合归并:若 SQL 有聚合函数(如 COUNT SUM AVG MAX MIN),对多分片的聚合结果再做一次聚合:

    • COUNT/SUM → 累加归并(把各分片的计数/求和结果相加);

    • AVG → 平均值归并(基于各分片的计数,计算整体平均值);

    • MAX/MIN → 比较归并(从各分片的最大/最小值中,再选最大/最小);

  • 归并模式的选择,决定了结果如何存储、如何返回,适配不同业务场景:

    • 流式归并

      • 每次从结果集中取一条数据,逐条返回(和数据库原生返回结果集的方式一致);

      • 无需把所有结果都加载到内存,内存消耗小,适合数据量大、需快速返回首条结果的场景(如 OLTP 在线交易,强调低延迟、高并发);

      • 典型场景:遍历、排序、流式分组归并等。通常内存限制模式会用流式归并;

    • 内存归并

      • 把所有分片的结果全部加载到内存,再统一做分组、排序、聚合,最后封装成可逐次访问的结果集返回;

      • 需要更多内存,但能支持更复杂的全局分组、排序、聚合逻辑。适合分析型查询(OLAP)(如报表统计,需处理大量数据做全局计算);

      • 典型场景:通常连接限制模式会用内存归并。

2 ShardingSphere-JDBC 扩展机制

2.1 ShardingSphereDataSource

  • 如何调试 ShardingSphere-JDBC 的源码呢?这就需要一个比较简单明了的测试案例来作为调试代码的入口:

    public class ShardingJDBCDemo {public static void main(String[] args) throws SQLException {// 一、配置数据库连接池:创建两个物理数据库的数据源Map<String, DataSource> dataSourceMap = new HashMap<>(2);// 配置第一个数据源,对应数据库 shardingdb1HikariDataSource dataSource0 = new HikariDataSource();dataSource0.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSource0.setJdbcUrl("jdbc:mysql://192.168.65.212:3306/shardingdb1?serverTimezone=GMT%2B8&useSSL=false");dataSource0.setUsername("root");dataSource0.setPassword("root");dataSourceMap.put("m0", dataSource0); // 数据源标识为 m0// 配置第二个数据源,对应数据库 shardingdb2HikariDataSource dataSource1 = new HikariDataSource();dataSource1.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSource1.setJdbcUrl("jdbc:mysql://192.168.65.212:3306/shardingdb2?serverTimezone=GMT%2B8&useSSL=false");dataSource1.setUsername("root");dataSource1.setPassword("root");dataSourceMap.put("m1", dataSource1); // 数据源标识为 m1// 二、配置分库分表规则:定义数据如何分布到不同的库和表中ShardingRuleConfiguration shardingRuleConfig = createRuleConfig();// 三、配置ShardingSphere属性:开启SQL执行日志显示Properties properties = new Properties();properties.setProperty("sql-show", "true"); // 显示分片后的真实SQL语句// TEST:创建ShardingSphere数据源,整合所有配置,创建具有分片功能的数据源DataSource dataSource = ShardingSphereDataSourceFactory.createDataSource(dataSourceMap,Collections.singleton(shardingRuleConfig), properties);// 测试部分ShardingJDBCDemo test = new ShardingJDBCDemo();// 建表操作(需要时取消注释执行)//test.droptable(dataSource);//test.createtable(dataSource);// 插入测试数据(需要时取消注释执行)//test.addcourse(dataSource);// TEST(调试的起点):查询数据,验证分片查询功能test.querycourse(dataSource);}/*** 创建分片规则配置* 配置逻辑表course如何映射到物理表(分布在m0和m1两个库,每个库有course_1和course_2两个表)*/private static ShardingRuleConfiguration createRuleConfig(){ShardingRuleConfiguration result = new ShardingRuleConfiguration();// 配置逻辑表course对应的实际数据节点:m0和m1两个库,每个库有course_1和course_2表ShardingTableRuleConfiguration courseTableRuleConfig = new ShardingTableRuleConfiguration("course","m$->{0..1}.course_$->{1..2}");// 配置分布式ID生成算法(雪花算法)Properties snowflakeprop = new Properties();snowflakeprop.setProperty("worker.id", "123"); // 设置工作节点IDresult.getKeyGenerators().put("alg_snowflake", new AlgorithmConfiguration("SNOWFLAKE", snowflakeprop));// 配置课程表的主键生成策略:使用雪花算法为cid字段生成IDcourseTableRuleConfig.setKeyGenerateStrategy(new KeyGenerateStrategyConfiguration("cid","alg_snowflake"));// 配置分库策略:按照cid字段进行分库,使用MOD算法(取模)courseTableRuleConfig.setDatabaseShardingStrategy(new StandardShardingStrategyConfiguration("cid","course_db_alg"));Properties modProp = new Properties();modProp.put("sharding-count",2); // 设置分片数量为2(两个库)result.getShardingAlgorithms().put("course_db_alg",new AlgorithmConfiguration("MOD",modProp));// 配置分表策略:按照cid字段进行分表,使用INLINE表达式算法courseTableRuleConfig.setTableShardingStrategy(new StandardShardingStrategyConfiguration("cid","course_tbl_alg"));// 分表算法表达式:根据cid计算表名后缀(结果为1或2)Properties inlineProp = new Properties();inlineProp.setProperty("algorithm-expression", "course_$->{((cid+1)%4).intdiv(2)+1}");result.getShardingAlgorithms().put("course_tbl_alg",new AlgorithmConfiguration("INLINE",inlineProp));result.getTables().add(courseTableRuleConfig);return result;}// 添加测试课程数据:插入9条记录,观察ID生成和分片效果public void addcourse(DataSource dataSource) throws SQLException {for (int i = 1; i < 10; i++) {long orderId = executeAndGetGeneratedKey(dataSource, "INSERT INTO course (cname, user_id, cstatus) VALUES ('java'," + i + ", '1')");System.out.println("添加课程成功,课程ID:" + orderId);}}// 查询课程数据:根据特定cid查询,测试分片查询功能public void querycourse(DataSource dataSource) throws SQLException {Connection conn = null;try {// 获取ShardingSphere连接(特殊化的Connection实现)conn = dataSource.getConnection();// 创建ShardingSphere语句对象Statement statement = conn.createStatement();String sql = "SELECT cid,cname,user_id,cstatus from course where cid=851198093910081536";// 执行查询,获取分片结果集ResultSet result = statement.executeQuery(sql);while (result.next()) {System.out.println("result:" + result.getLong("cid"));}} catch (SQLException e) {e.printStackTrace();} finally {if (null != conn) {conn.close();}}}// 通用SQL执行方法private void execute(final DataSource dataSource, final String sql) throws SQLException {try (Connection conn = dataSource.getConnection();Statement statement = conn.createStatement()) {statement.execute(sql);}}// 执行SQL并返回生成的主键(用于获取雪花算法生成的ID)private long executeAndGetGeneratedKey(final DataSource dataSource, final String sql) throws SQLException {long result = -1;try (Connection conn = dataSource.getConnection();Statement statement = conn.createStatement()) {statement.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);ResultSet resultSet = statement.getGeneratedKeys();if (resultSet.next()) {result = resultSet.getLong(1); // 获取生成的主键值}}return result;}/*** 表初始化操作*/public void droptable(DataSource dataSource) throws SQLException {execute(dataSource, "DROP TABLE IF EXISTS course_1");execute(dataSource, "DROP TABLE IF EXISTS course_2");}public void createtable(DataSource dataSource) throws SQLException {execute(dataSource, "CREATE TABLE course_1 (cid BIGINT(20) PRIMARY KEY,cname VARCHAR(50) NOT NULL,user_id BIGINT(20) NOT NULL,cstatus varchar(10) NOT NULL);");execute(dataSource, "CREATE TABLE course_2 (cid BIGINT(20) PRIMARY KEY,cname VARCHAR(50) NOT NULL,user_id BIGINT(20) NOT NULL,cstatus varchar(10) NOT NULL);");}
    }
    
  • 代码中最关键的是 ShardingSphereDataSource(标记为 TEST 处),它是整个分库分表功能的中枢

    • 它实现了 JDBC 的 DataSource 接口,因此可以像普通数据源(如 Druid、Hikari)一样,与 Spring Data、MyBatis 等框架无缝集成(符合 JDBC 规范,无需修改上层代码);
    • 当通过它获取连接(getConnection())、执行 SQL 时,ShardingSphere 会在底层自动完成:
      1. SQL 解析(理解 SQL 要操作什么);
      2. 路由(根据分库分表规则,确定该访问哪些真实库表);
      3. SQL 改写(将逻辑表名改为真实表名等);
      4. 执行(在目标库表上执行 SQL);
      5. 结果归并(将多库表的结果合并为一个)。

2.2 基于 ShardingSphereDataSource 的工作方式

  • 实际上,ShardingSphereDataSource 除了拥有分库分表的功能外,还实现了很多自己的扩展功能。其中最常用的,是他能自己解析配置文件。因此, ShardingSphere-JDBC 其实完全可以脱离 SpringBoot 等框架,以通过标准 JDBC 方式独立运行。例:

    在上一章节10.2 ShardingSphere-JDBC分库分表实战与讲解的案例,实际上是通过基于 SpringBoot 的第三方拓展,来实现解析配置文件、创建数据源等功能;

    public class ShardingJDBCDriverTest {@Testpublic void test() throws ClassNotFoundException, SQLException {String jdbcDriver = "org.apache.shardingsphere.driver.ShardingSphereDriver";String jdbcUrl = "jdbc:shardingsphere:classpath:config.yaml";String sql = "select * from sharding_db.course";// 用 Class.forName 加载 ShardingSphereDriverClass.forName(jdbcDriver);// 通过 DriverManager.getConnection 连接,URL 指向配置文件 config.yamltry(Connection connection = DriverManager.getConnection(jdbcUrl);) {// 后续执行 SQL 的流程(createStatement、executeQuery 等)和标准 JDBC 完全一致Statement statement = connection.createStatement();ResultSet resultSet = statement.executeQuery(sql);while (resultSet.next()){System.out.println("course cid= "+resultSet.getLong("cid"));}}}
    }
    
    • 这说明 ShardingSphere 本身是 JDBC 规范的实现,只要提供正确的驱动和配置,就能独立完成分库分表等工作;
  • config.yaml 是 ShardingSphere 的核心配置载体,用 YAML 格式定义分库分表、数据源、全局属性等规则,和之前用 Java 代码配置的逻辑是等价的,只是形式不同。配置文件主要包含以下模块:

    • rules:定义各类规则(如 SHARDING 分库分表规则、TRANSACTION 事务规则、SQL_PARSER SQL 解析规则等)。以 SHARDING 为例,复刻上一章节对course表进行分库分表的功能:
      • 真实表映射(actualDataNodes: m${0..1}.course_${1..2},对应 m0/m1 库的 course_1/course_2 表);
      • 分库策略(按 cid 取模,路由到 m0m1);
      • 分表策略(按 cid 计算,路由到 course_1course_2);
      • 主键生成(雪花算法生成 cid);
    • props:全局属性(如连接池大小、SQL 日志开关、执行器线程数等);
    • databaseName:逻辑数据库名;
    • dataSources:配置真实数据源(如 m0m1 对应的数据库连接信息);
    # 权限规则配置:定义用户权限
    rules:- !AUTHORITY  # 权限规则标识users:- root@%:root  # 用户root,可从任何主机访问,密码root- sharding@:sharding  # 用户sharding,无主机限制,密码shardingprovider:type: ALL_PERMITTED  # 权限提供者类型:所有用户拥有所有权限- !TRANSACTION  # 事务规则配置defaultType: XA  # 默认事务类型:XA分布式事务providerType: Atomikos  # 事务管理器提供者:Atomikos- !SQL_PARSER  # SQL解析器配置sqlCommentParseEnabled: true  # 启用SQL注释解析sqlStatementCache:  # SQL语句缓存配置initialCapacity: 2000  # 初始容量2000条maximumSize: 65535  # 最大容量65535条parseTreeCache:  # 解析树缓存配置initialCapacity: 128  # 初始容量128个maximumSize: 1024  # 最大容量1024个- !SHARDING  # 分片规则配置(核心配置)tables:course:  # 逻辑表course的配置actualDataNodes: m${0..1}.course_${1..2}  # 实际数据节点:两个数据库(m0,m1),每个库两个表(course_1,course_2)databaseStrategy:  # 分库策略standard:shardingColumn: cid  # 分库字段:cidshardingAlgorithmName: course_db_alg  # 分库算法名称tableStrategy:  # 分表策略standard:shardingColumn: cid  # 分表字段:cidshardingAlgorithmName: course_tbl_alg  # 分表算法名称keyGenerateStrategy:  # 主键生成策略column: cid  # 主键列名keyGeneratorName: alg_snowflake  # 主键生成器名称# 分片算法定义shardingAlgorithms:course_db_alg:  # 分库算法type: MOD  # 取模算法props:sharding-count: 2  # 分片数量:2个库course_tbl_alg:  # 分表算法type: INLINE  # 内联表达式算法props:algorithm-expression: course_$->{cid%2+1}  # 表名计算表达式:cid对2取模后加1# 主键生成器定义keyGenerators:alg_snowflake:type: SNOWFLAKE  # 使用雪花算法生成分布式ID# 系统属性配置
    props:max-connections-size-per-query: 1  # 每个查询的最大连接数kernel-executor-size: 16  # 内核线程池大小,默认无限proxy-frontend-flush-threshold: 128  # 代理前端刷新阈值,默认128proxy-hint-enabled: false  # 是否启用hint强制路由sql-show: false  # 是否显示实际执行的SQL语句check-table-metadata-enabled: false  # 是否检查表元数据一致性proxy-backend-query-fetch-size: -1  # 代理后端查询获取大小,-1表示使用JDBC驱动最小值proxy-frontend-executor-size: 0  # 代理前端执行器大小,0由Netty决定proxy-backend-executor-suitable: OLAP  # 代理后端执行器适用类型:OLAP(联机分析处理)proxy-frontend-max-connections: 0  # 代理前端最大连接数,0表示无限制sql-federation-type: NONE  # SQL联邦查询类型:不启用proxy-backend-driver-type: JDBC  # 代理后端驱动类型:JDBCproxy-mysql-default-version: 8.0.20  # MySQL默认版本proxy-default-port: 3307  # 代理服务器默认端口proxy-netty-backlog: 1024  # Netty backlog参数# 逻辑数据库名称(客户端连接时使用的数据库名)
    databaseName: sharding_db# 数据源配置:定义物理数据库连接
    dataSources:m0:  # 第一个数据源标识dataSourceClassName: com.zaxxer.hikari.HikariDataSource  # 使用HikariCP连接池url: jdbc:mysql://192.168.65.212:3306/shardingdb1?serverTimezone=UTC&useSSL=false  # 数据库连接URLusername: root  # 数据库用户名password: root  # 数据库密码connectionTimeoutMilliseconds: 30000  # 连接超时时间30秒idleTimeoutMilliseconds: 60000  # 空闲连接超时时间60秒maxLifetimeMilliseconds: 1800000  # 连接最大生命周期30分钟maxPoolSize: 50  # 最大连接池大小50minPoolSize: 1  # 最小连接池大小1m1:  # 第二个数据源标识dataSourceClassName: com.zaxxer.hikari.HikariDataSourceurl: jdbc:mysql://192.168.65.212:3306/shardingdb2?serverTimezone=UTC&useSSL=falseusername: rootpassword: rootconnectionTimeoutMilliseconds: 30000idleTimeoutMilliseconds: 60000maxLifetimeMilliseconds: 1800000maxPoolSize: 50minPoolSize: 1
    
  • 那么为什么上一章不直接用这个 YAML 配置文件呢?因为 IDEA 没有提示;

  • 最后,这个配置文件,其实是和 ShardingSphere-Proxy 通用的;

3 ShardingSphere 的 SPI 扩展机制

3.1 从主键生成策略入手

  • SPI(Service Provider Interface)是 Java 提供的服务发现机制:允许第三方(或用户)为接口提供实现类,框架能自动加载这些实现类,从而扩展功能;

  • 一个完整的分库分表方案,要配置的信息还是挺多的。我们要理解配置的各种策略是如何从 ShardingSphere 中扩展出来的,就要先找一个比较简单的目标入手。这里,以主键生成策略为例,抽取 ShardingSphere 中重点源码进行解读:

    package org.apache.shardingsphere.sharding.factory;@NoArgsConstructor(access = AccessLevel.PRIVATE)
    public final class KeyGenerateAlgorithmFactory {// 加载所有主键生成策略static {ShardingSphereServiceLoader.register(KeyGenerateAlgorithm.class);// 内部通过 Java 原生的 ServiceLoader.load() 方法,扫描并加载所有实现了 KeyGenerateAlgorithm 接口的类}// 获取主键生成算法实例// newInstance 方法根据配置(如“类型 = SNOWFLAKE”),从加载的实现类中,创建对应的 KeyGenerateAlgorithm 实例public static KeyGenerateAlgorithm newInstance(final AlgorithmConfiguration keyGenerateAlgorithmConfig) {return ShardingSphereAlgorithmFactory.createAlgorithm(keyGenerateAlgorithmConfig, KeyGenerateAlgorithm.class);}// 判断算法是否存在:contains 方法检查“配置的算法类型(如 SNOWFLAKE)”是否有对应的实现类public static boolean contains(final String keyGenerateAlgorithmType) {return TypedSPIRegistry.findRegisteredService(KeyGenerateAlgorithm.class, keyGenerateAlgorithmType).isPresent();}
    }
    
  • 先来看主键生成策略是如何加载的:ShardingSphereServiceLoader.register(KeyGenerateAlgorithm.class);

    /*** ShardingSphere服务加载器 - 基于Java SPI机制的服务发现和注册类* 用于动态加载和缓存ShardingSphere的各种扩展实现*/
    public final class ShardingSphereServiceLoader {// 使用线程安全的ConcurrentHashMap缓存所有已注册的服务// Key: 服务接口的Class对象,Value: 该接口的所有实现类实例集合private static final Map<Class<?>, Collection<Object>> SERVICES = new ConcurrentHashMap<>();/*** 注册指定服务接口的所有实现类* 使用Java SPI机制自动发现并加载META-INF/services目录下的实现类* @param serviceInterface 要注册的服务接口Class对象*/public static void register(final Class<?> serviceInterface) {// 双重检查锁定模式:避免重复加载同一接口的实现类if (!SERVICES.containsKey(serviceInterface)) {// 如果该接口尚未注册,则加载并缓存其所有实现类SERVICES.put(serviceInterface, load(serviceInterface));}}/*** 使用Java SPI机制加载指定接口的所有实现类实例* @param <T> 服务接口泛型类型* @param serviceInterface 要加载的服务接口Class对象* @return 包含所有实现类实例的集合*/private static <T> Collection<Object> load(final Class<T> serviceInterface) {Collection<Object> result = new LinkedList<>();// 使用ServiceLoader加载指定接口的所有实现类// ServiceLoader会自动扫描classpath中META-INF/services/目录下的配置文件for (T each : ServiceLoader.load(serviceInterface)) {result.add(each); // 将每个实现类实例添加到结果集合中}return result;}
    }
    
  • 如果想自己写一个主键生成算法(即KeyGenerateAlgorithm的实现类),只需要通过 SPI 的方式让 ShardingSphere 加载进去就行;

    • 先看看KeyGenerateAlgorithm有哪些实现类,比如NanoIdKeyGenerateAlgorithm,它的源码比较简单:

      /*** NanoId分布式主键生成算法实现类* 实现ShardingSphere的KeyGenerateAlgorithm接口,提供基于NanoId的主键生成功能*/
      public final class NanoIdKeyGenerateAlgorithm implements KeyGenerateAlgorithm {// 配置属性对象,用于接收初始化参数private Properties props;/*** 默认无参构造函数*/public NanoIdKeyGenerateAlgorithm() {}/*** 初始化方法,在算法实例创建后调用* @param props 配置属性,可以包含自定义参数(如:字母表、长度等)*/public void init(Properties props) {this.props = props; // 保存配置属性供后续使用}/*** 生成分布式主键的核心方法* @return 返回生成的NanoId字符串作为主键*/public String generateKey() {// 使用NanoId工具类生成ID:// 1. ThreadLocalRandom.current(): 获取当前线程的随机数生成器(线程安全)// 2. NanoIdUtils.DEFAULT_ALPHABET: 使用默认字母表(大小写字母+数字)// 3. 21: 生成ID的长度为21个字符return NanoIdUtils.randomNanoId(ThreadLocalRandom.current(), NanoIdUtils.DEFAULT_ALPHABET, 21);}/*** 获取算法类型标识* @return 返回算法类型名称"NANOID",用于配置文件中引用*/public String getType() {return "NANOID";}/*** 获取配置属性(自动生成的方法)* @return 返回当前算法的配置属性*/@Generatedpublic Properties getProps() {return this.props;}
      }
      
    • 接下来仿照着自己实现一下:

      /*** 自定义分布式主键生成算法实现类* 实现ShardingSphere的KeyGenerateAlgorithm接口,提供基于时间戳+序列号的主键生成方案*/
      public class MyKeyGeneratorAlgorithm implements KeyGenerateAlgorithm {// 原子长整型计数器,用于生成序列号,保证线程安全private AtomicLong atom = new AtomicLong(0);// 配置属性对象,用于接收初始化参数private Properties props;/*** 生成分布式主键的核心方法* @return 返回生成的Long类型主键,格式为:时间戳 + 序列号*/@Overridepublic Comparable<?> generateKey() {// 获取当前时间LocalDateTime ldt = LocalDateTime.now();// 格式化时间为时分秒毫秒(HHmmssSSS格式,共8位数字)String timestampS = DateTimeFormatter.ofPattern("HHmmssSSS").format(ldt);// 组合时间戳和原子递增的序列号,生成最终主键return Long.parseLong(""+timestampS+atom.incrementAndGet());}/*** 获取配置属性* @return 返回当前算法的配置属性*/@Overridepublic Properties getProps() {return this.props;}/*** 获取算法类型标识* @return 返回算法类型名称"MYKEY",用于配置文件中引用*/public String getType() {return "MYKEY";}/*** 初始化方法,在算法实例创建后调用* @param props 配置属性,可以包含自定义参数*/@Overridepublic void init(Properties props) {this.props = props;}
      }
      
    • 配置 SPI 扩展文件。在项目的classpath/META-INF/services/(这个目录是 Java 的 SPI 机制加载的固定目录)目录下,创建文件:

      • 文件名:接口的全限定名 → org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm

      • 文件内容:自定义实现类的全限定名 → com.nosy.shardingDemo.algorithm.MyKeyGenerateAlgorithm

    • 在 ShardingSphere 的配置中,指定主键生成策略的 type 为自定义的标识:

      spring.shardingsphere.rules.sharding.key-generators.course_cid_alg.type=MYKEY
      
    • 这样,ShardingSphere 就会通过 SPI 机制,加载并使用 MyKeyGenerateAlgorithm 生成主键;

  • 通过 SPI 机制,用户可以不修改 ShardingSphere 源码,就能扩展其功能(比如自定义分片算法、主键生成算法、加密算法等),让框架更贴合业务需求。

3.2 尝试扩展分片算法

  • 在分库分表场景中,ShardingSphere 提供了内置分片算法(如 MODINLINE 等),但业务可能需要自定义分片逻辑(比如更复杂的路由规则)。此时,可通过 SPI 机制扩展分片算法;

  • 扩展分片算法的步骤

    • 在上一章节10.2 ShardingSphere-JDBC分库分表实战与讲解3.4 CLASS_BASED 自定义分片中实现了自定义分片,即MyComplexAlgorithm类。我们是通过 ShardingSphere 提供的 CLASS_BASED 类型的分片算法配置进去的。实际上,我们也可以使用 ShardingSphere 提供的 SPI 机制配置进去;

    • 在项目的 classpath/META-INF/services/ 目录下,创建文件:

      • 文件名:接口的全限定名 → org.apache.shardingsphere.sharding.spi.ShardingAlgorithm
      • 文件内容:自定义分片算法类的全限定名 → com.roy.shardingDemo.algorithm.MyComplexAlgorithm
      • 这样,ShardingSphere 启动时会通过 SPI 机制,自动加载 MyComplexAlgorithm
    • 如果想要能够被配置文件识别,在MyComplexAlgorithm类中,增加实现getType()方法:

      public class MyComplexAlgorithm implements ComplexKeysShardingAlgorithm<Long> {//……@Overridepublic String getType(){return "MYCOMPLEX";}
      }
      
    • 在 ShardingSphere 配置分库分表策略时,指定这个我们自己的实现类:

      spring.shardingsphere.rules.sharding.sharding-algorithms.course_tbl_alg.type=MYCOMPLEX
      
  • 通过 SPI 机制,用户可以不修改 ShardingSphere 源码,就能自定义分片逻辑,让框架适配更复杂的业务场景(比如按“多字段组合、特定业务规则”分片)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/98143.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/98143.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/98143.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

移动语义的里里外外:从 std::move 的幻象到性能的现实

我们都已经听过这样的建议&#xff1a;“使用 std::move 来避免昂贵的拷贝&#xff0c;提升性能。” 这没错&#xff0c;但如果你对它的理解仅止于此&#xff0c;那么你可能正在黑暗中挥舞着一把利剑&#xff0c;既可能披荆斩棘&#xff0c;也可能伤及自身。 移动语义是 C11 带…

selenium完整版一览

selenium 库驱动浏览器selenium库是一种用于Web应用程序测试的工具,它可以驱动浏览器执行特定操作,自动按照脚本代码做出单击、输入、打开、验证等操作,支持的浏览器包括IE、Firefox、Safari、Chrome、Opera等。而在办公领域中如果经常需要使用浏览器操作某些内容,就可以使用se…

[Linux]学习笔记系列 -- lib/kfifo.c 内核FIFO实现(Kernel FIFO Implementation) 高效的无锁字节流缓冲区

文章目录lib/kfifo.c 内核FIFO实现(Kernel FIFO Implementation) 高效的无锁字节流缓冲区历史与背景这项技术是为了解决什么特定问题而诞生的&#xff1f;它的发展经历了哪些重要的里程碑或版本迭代&#xff1f;目前该技术的社区活跃度和主流应用情况如何&#xff1f;核心原理与…

MFC_Install_Create

1. 安装MFC 编写MFC窗口应用程序需要用到Visual Studiohttps://visualstudio.microsoft.com/zh-hans/&#xff0c;然后安装&#xff0c;要选择使用C的桌面开发&#xff0c;再点击右边安装详细信息中的使用C的桌面开发&#xff0c;往下滑&#xff0c;有一个适用于最新的v143生成…

Langchain4j开发之AI Service

学习基于Langchain4j的大模型开发需要学习其中Ai Service的开发模式。里面对大模型做了一层封装&#xff0c;提供一些可以方便调用的api。其中有两种使用Ai Service的方式。一.编程式开发1.首先引入Langchain4的依赖。<dependency><groupId>dev.langchain4j</gr…

认识神经网络和深度学习

什么是神经网络&#xff1f;什么又是深度学习&#xff1f;二者有什么关系&#xff1f;……带着这些疑问&#xff0c;进入本文的学习。什么是神经网络神经网络&#xff08;Neural Network&#xff09;是一种模仿生物神经系统&#xff08;如大脑神经元连接方式&#xff09;设计的…

医疗行业安全合规数据管理平台:构建高效协作与集中化知识沉淀的一体化解决方案

在医疗行业中&#xff0c;数据不仅是日常运营的基础&#xff0c;更是患者安全、服务质量和合规管理的核心载体。随着医疗业务的复杂化和服务模式的多元化&#xff0c;各类机构——从大型医院到科研中心——都面临着海量文档、报告、影像资料和政策文件的管理需求。这些资料往往…

Day25_【深度学习(3)—PyTorch使用(5)—张量形状操作】

reshape() squeeze()unsqueeze()transpose()permute()view() reshape() contiguous() reshape() 一、reshape() 函数保证张量数据不变的前提下改变数据的维度&#xff0c;将其转换成指定的形状。def reshape_tensor():data torch.tensor([[1, 2, 3], [4, 5, 6]])print(data…

第十八篇 开发网页教学:实现画布、绘画、简易 PS 方案

在网页开发领域&#xff0c;画布功能是实现交互创作的重要基础&#xff0c;无论是简单的绘画工具&#xff0c;还是具备基础修图能力的简易 PS 方案&#xff0c;都能为用户带来丰富的视觉交互体验。本篇教学将围绕 “学习 - 实践 - 实操” 的核心思路&#xff0c;从技术原理讲解…

封装形成用助焊剂:电子制造“隐形桥梁”的技术突围与全球产业重构

在5G通信、人工智能、新能源汽车等新兴技术驱动下&#xff0c;全球电子制造业正以年均6.8%的增速重构产业链。作为电子元件焊接的核心辅料&#xff0c;封装形成用助焊剂&#xff08;又称电子封装用助焊剂&#xff09;凭借其“优化焊接质量、提升可靠性、降低制造成本”的核心价…

【完整源码+数据集+部署教程】零件实例分割系统源码和数据集:改进yolo11-GhostHGNetV2

背景意义 研究背景与意义 随着工业自动化和智能制造的迅速发展&#xff0c;零件的高效识别与分割在生产线上的重要性日益凸显。传统的图像处理方法在处理复杂场景时往往面临着准确性不足和实时性差的问题&#xff0c;而深度学习技术的引入为这一领域带来了新的机遇。特别是基于…

墨色规则与血色节点:C++红黑树设计与实现探秘

前言​ 前几天攻克了AVL树&#xff0c;我们已然是平衡二叉树的强者。但旅程还未结束&#xff0c;下一个等待我们的&#xff0c;是更强大、也更传奇的**终极BOSS**——红黑树。它不仅是map和set的强大心脏&#xff0c;更是C STL皇冠上的明珠。准备好了吗&#xff1f;让我们一…

大数据时代时序数据库选型指南:为何 Apache IoTDB 成优选(含实操步骤)

在数字经济加速渗透的今天&#xff0c;工业物联网&#xff08;IIoT&#xff09;、智慧能源、金融交易、城市运维等领域每天产生海量 “带时间戳” 的数据 —— 从工业设备的实时温度、电压&#xff0c;到电网的负荷波动&#xff0c;再到金融市场的每秒行情&#xff0c;这类 “时…

MAZANOKE+cpolar让照片存储无上限

文章目录前言1. 关于MAZANOKE2. Docker部署3. 简单使用MAZANOKE4. 安装cpolar内网穿透5. 配置公网地址6. 配置固定公网地址总结当工具开始理解用户的需求痛点时&#xff0c;MAZANOKE与cpolar这对搭档给出了“轻量化”的解决方案。它不追求浮夸的功能堆砌&#xff0c;却用扎实的…

正则表达式 - 元字符

正则表达式中的元字符是具有特殊含义的字符&#xff0c;它们不表示字面意义&#xff0c;而是用于控制匹配模式。基本元字符. (点号)匹配除换行符(\n)外的任意单个字符示例&#xff1a;a.b 匹配 "aab", "a1b", "a b" 等^ (脱字符)匹配字符串的开始…

suricata源码解读-事务日志

注册事务日志线程模块 void TmModuleTxLoggerRegister (void) {tmm_modules[TMM_TXLOGGER].name "__tx_logger__";tmm_modules[TMM_TXLOGGER].ThreadInit OutputTxLogThreadInit;tmm_modules[TMM_TXLOGGER].Func OutputTxLog;tmm_modules[TMM_TXLOGGER].ThreadExi…

【CSS】层叠上下文和z-index

z-index 的作用范围受“层叠上下文&#xff08;stacking context&#xff09;”影响。&#x1f539; 1. z-index 的基本作用 控制元素在 同一个层叠上下文&#xff08;stacking context&#xff09; 内的堆叠顺序。值越大&#xff0c;显示层级越靠上。&#x1f539; 2. 什么是层…

自动化脚本的降本增效实践

一、自动化脚本的核心价值自动化脚本通过模拟人类操作完成重复性任务&#xff0c;其核心价值体现在三个维度&#xff1a;首先&#xff0c;在时间成本方面&#xff0c;标准化的数据处理流程可缩短90%以上的操作耗时&#xff1b;其次&#xff0c;在人力成本上&#xff0c;单个脚本…

【C语言】第七课 字符串与危险函数​​

C语言中的字符串处理既是基础&#xff0c;也是安全漏洞的重灾区。理解C风格字符串的底层原理及其危险函数的运作方式&#xff0c;对于编写安全代码和进行逆向工程分析至关重要。 &#x1f9e9; C风格字符串的本质 C风格字符串本质上是以空字符\0&#xff08;ASCII值为0&#xf…

Mac安装hadoop

1.在terminal中检查是否安装brew命令 brew --version 如果没有安装&#xff0c;在terminal中执行命令&#xff0c;安装brew /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 安装完成后&#xff0c;再重新打…