ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot 整合 ShardingSphere 5.2.1 分库分表实战与避坑指南

Spring Boot 整合 ShardingSphere 5.2.1 分库分表实战与避坑指南 简介本资源面向需要在SpringBoot项目中实现分表不分库的Java开发者提供ShardingSphere 5.2.1最新版的完整整合示例。内容围绕单库水平分表场景展开涵盖分片规则配置、分片算法选择、数据源与事务管理等核心环节适合具备一定SpringBoot基础、希望解决单表数据量过大导致查询性能瓶颈的中高级开发者参考。压缩包共16个文件约15KB以9个Java源码为主体配合2个yml配置文件、2个xml映射文件以及SQL建表脚本、自定义分片算法文件和gitignore等辅助内容结构精简便于直接导入IDE运行调试。目前已有1950人学习下载。通过该示例可快速理解ShardingSphere在SpringBoot中的依赖引入、规则构建与Bean注册流程掌握范围分片、哈希分片及自定义算法的落地方式并参考分页查询、数据迁移与分布式事务等注意事项为实际业务中的分表改造提供可复用的配置模板与排错思路。1. Spring Boot 整合 ShardingSphere 5.2.1分库分表这件事为什么配置对了数据还是写错库单库单表撑到几百万行以后最先崩的往往不是数据库本身而是那条慢查询把连接池占满接着整个服务雪崩。分库分表就是在这个节点被提上日程的而 ShardingSphere 5.2.1 是很多人从 4.x 升级过来时绕不开的一个版本——它把配置模型从sharding-jdbc-spring-boot-starter的纯 YAML 拆成了spring.shardingsphere.rules这套新结构网上大量 4.x 的教程直接照抄会启动报错。这篇笔记讲的就是在 Spring Boot 项目里把 ShardingSphere 5.2.1 跑通依赖怎么引、数据源和分片规则怎么写、分片键选错会怎样、读写分离和分片叠加时有哪些坑。适合已经会写 Spring Boot 但第一次碰分库分表的后端也适合从 4.x 迁移过来被配置结构卡住的人。下面所有配置和代码都是可复现的最小集你照着改库名表名就能跑。2. ShardingSphere 5.2.1 的配置模型与依赖选型为什么 4.x 的 YAML 直接抄会启动失败2.1 5.x 把「数据源」和「规则」拆开了4.x 时代分片规则、读写分离、数据源全都平铺在spring.shardingsphere下面一个sharding-jdbc-spring-boot-starter全包。到了 5.xShardingSphere 做了一次比较大的重构数据源归数据源规则归规则规则下面再按tables、binding-tables、broadcast-tables这些维度组织。这个变化带来的直接后果是——你从 4.x 教程里复制过来的spring.shardingsphere.sharding.tables.xxx.actual-data-nodes这种写法在 5.2.1 里根本不会被解析启动时要么报找不到数据源要么规则静默失效SQL 全打到默认库上。我一般会先确认一件事你用的是 ShardingSphere-JDBC 还是 ShardingSphere-Proxy。这篇只讲 JDBC 这种嵌在应用里的方式因为它对 Spring Boot 项目改动最小不需要额外部署中间件。JDBC 模式下ShardingSphere 是以一个增强版 DataSource 的形式存在的你的 MyBatis、JPA 拿到的还是普通 DataSource但底层 SQL 已经被改写过。5.2.1 的配置结构大致是这样几层spring.shardingsphere.datasource下面定义物理数据源每个数据源就是一个普通的 HikariCP 配置spring.shardingsphere.rules.sharding下面定义分片规则包括分片键、算法、实际节点spring.shardingsphere.rules.readwrite-splitting定义读写分离spring.shardingsphere.props放一些全局开关比如是否打印改写后的 SQL这个分层是理解后面所有配置的基础。很多人配完发现不生效八成是把规则写到了数据源那一层或者actual-data-nodes的表达式写错了。2.2 依赖怎么引别把 starter 和 jdbc-core 一起引Maven 里最稳的引法是只引一个 starter让它自己带核心包dependency groupIdorg.apache.shardingsphere/groupId artifactIdsharding-jdbc-spring-boot-starter/artifactId version5.2.1/version /dependency这里有个血泪经验不要同时引sharding-jdbc-spring-boot-starter和sharding-jdbc-core也不要把shardingsphere-jdbc-core-spring-boot-starter和老的sharding-jdbc-spring-boot-starter混着引。5.x 之后 artifactId 改过名两套包同时存在时Spring Boot 的自动配置会打架表现是启动日志里出现两个 ShardingSphere 的 DataSource Bean或者规则只加载了一半。如果你是从 4.x 升上来先把旧依赖彻底删干净再引新的。另外注意 Spring Boot 版本。ShardingSphere 5.2.1 对 Spring Boot 2.6 的兼容性比较好如果你项目还在 2.3 以下建议先升 Spring Boot否则可能出现spring.autoconfigure顺序问题导致数据源初始化失败。数据库驱动按你实际用的引MySQL 就引mysql-connector-java注意 8.x 驱动类名是com.mysql.cj.jdbc.Driver。2.3 一个能跑起来的最小数据源配置先不急着上分片把两个物理数据源配好确认能连上spring: shardingsphere: datasource: names: ds0,ds1 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db0?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db1?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root props: sql-show: true几个参数必须说清楚。names是逻辑数据源名的列表后面规则里引用ds0、ds1就是引用这里。type指定连接池实现不写默认也是 Hikari但显式写出来更稳。jdbc-url而不是url——这是 HikariCP 的约定写成url在部分版本下会报找不到驱动。sql-show: true是调试期必开项它会把逻辑 SQL、改写后的真实 SQL、路由到的数据源全打出来排查分片问题时没有它基本是黑匣子。配完这一步先写个最简单的select 1或者查一张不分片的表确认两个库都能连。这一步不过后面分片规则再对也没意义。3. 分片规则落地分片键、算法和 actual-data-nodes 怎么配才不写错库3.1 分片键选错数据会均匀地写错分片键sharding-column是整个分库分表里最需要想清楚的决策。它决定了数据往哪个库哪张表走也决定了你未来哪些查询能走分片、哪些会全路由。选分片键的核心原则是高频查询条件里出现、且区分度足够、且不会频繁更新。订单场景最常见的分片键是user_id或order_id。如果你按user_id分片那么「查某个用户的所有订单」能精准路由到一个库但「按订单号查订单」就会全路由因为订单号里不含 user_id 信息。反过来按order_id分片按用户查就全路由。这是没有银弹的只能按业务主查询路径选。我见过最典型的翻车是拿create_time当分片键。时间字段区分度看似够但热点全压在最新那个分片上历史分片几乎没写入等于没分。而且时间范围查询会命中多个分片路由复杂度陡增。除非你是日志类、冷热分离明确的场景否则别用时间做分片键。3.2 分片算法inline 够用但别踩取模的坑5.2.1 内置了INLINE、MOD、HASH_MOD、RANGE、COMPLEX等算法。最常用的是INLINE它用 Groovy 表达式算分片spring: shardingsphere: rules: sharding: tables: t_order: actual-data-nodes: ds$-{0..1}.t_order$-{0..1} database-strategy: standard: sharding-column: user_id sharding-algorithm-name: db-inline table-strategy: standard: sharding-column: user_id sharding-algorithm-name: table-inline sharding-algorithms: db-inline: type: INLINE props: algorithm-expression: ds$-{user_id % 2} table-inline: type: INLINE props: algorithm-expression: t_order$-{user_id % 2}actual-data-nodes的表达式ds$-{0..1}.t_order$-{0..1}展开后是ds0.t_order0、ds0.t_order1、ds1.t_order0、ds1.t_order1四个物理表。注意$-{0..1}这个语法是 5.x 的写法4.x 是$-{0..1}也支持但外层结构不同。algorithm-expression里的user_id必须和sharding-column一致写成别的字段名不会报错但路由结果全错。这里有个玄学问题INLINE表达式里如果用了user_id % 2当 user_id 是字符串类型时会抛异常。分片键的 Java 类型和数据库类型要匹配字符串分片键建议用HASH_MOD或者自己写StandardShardingAlgorithm。3.3 用 Java 配置类替代 YAML 的时机YAML 配分片规则在规则简单时很清爽但一旦涉及复合分片、自定义算法、动态数据源YAML 会变得极难维护。5.2.1 支持用ShardingSphereDataSource手动构建Configuration public class ShardingConfig { Bean public DataSource shardingDataSource() throws SQLException { // 物理数据源 MapString, DataSource dataSourceMap new HashMap(); dataSourceMap.put(ds0, createDataSource(jdbc:mysql://127.0.0.1:3306/order_db0)); dataSourceMap.put(ds1, createDataSource(jdbc:mysql://127.0.0.1:3306/order_db1)); // 分片规则 ShardingRuleConfiguration ruleConfig new ShardingRuleConfiguration(); ruleConfig.getTables().add(new ShardingTableRuleConfiguration( t_order, ds$-{0..1}.t_order$-{0..1})); // 自定义算法 Properties props new Properties(); props.setProperty(algorithm-expression, ds$-{user_id % 2}); ruleConfig.getShardingAlgorithms().put(db-inline, new ShardingSphereAlgorithmConfiguration(INLINE, props)); return ShardingSphereDataSourceFactory.createDataSource( dataSourceMap, Collections.singleton(ruleConfig), new Properties()); } private DataSource createDataSource(String url) { HikariDataSource ds new HikariDataSource(); ds.setJdbcUrl(url); ds.setUsername(root); ds.setPassword(root); ds.setDriverClassName(com.mysql.cj.jdbc.Driver); return ds; } }逻辑说明ShardingSphereDataSourceFactory.createDataSource接收物理数据源 Map、规则集合、全局属性三部分。ShardingTableRuleConfiguration的第二个参数就是actual-data-nodes。自定义算法通过ShardingSphereAlgorithmConfiguration注册type 写INLINE或你实现的算法 SPI 名。参数说明Collections.singleton(ruleConfig)这里只放了分片规则如果你还要读写分离需要把ReadwriteSplittingRuleConfiguration也加进去。全局Properties里可以放sql-show。用 Java 配置的好处是算法可以注入 Spring Bean比如你要根据配置中心动态改分片数YAML 做不到。3.4 分片后自增主键怎么处理分片后每张物理表的自增主键会各自从 1 开始导致主键冲突。ShardingSphere 提供了SNOWFLAKE和UUID两种分布式主键spring: shardingsphere: rules: sharding: tables: t_order: key-generate-strategy: column: order_id key-generator-name: snowflake key-generators: snowflake: type: SNOWFLAKE props: worker-id: 1worker-id在多实例部署时必须每个实例不同否则会生成重复 ID。我一般用机器 IP 末段或者注册中心分配的实例序号来设。如果你用的是数据库自增记得把key-generate-strategy去掉同时保证业务不依赖全局唯一自增。4. 读写分离叠加分片主从路由和分片路由谁先谁后4.1 读写分离规则单独配别和分片混在一层读写分离在 5.2.1 里是独立规则和分片平级spring: shardingsphere: rules: readwrite-splitting: >// 强制当前线程后续查询走主库 HintManager hintManager HintManager.getInstance(); hintManager.setWriteRouteOnly(); try { // 这里的查询会走主库 orderMapper.selectById(orderId); } finally { hintManager.close(); }逻辑说明setWriteRouteOnly()让当前线程的读操作也路由到写库。close()必须放在 finally 里否则 ThreadLocal 不清理线程池复用时会污染下一个请求。参数说明HintManager是线程绑定的异步线程里不生效。如果你在Async方法里查需要在新线程里重新获取。另外HintManager和分片规则可以叠加它只影响读写路由不影响分片路由。4.3 事务里的读写路由一个事务里如果先写后读ShardingSphere 默认会把整个事务的读也路由到主库这是对的。但如果你在事务里手动setWriteRouteOnly又没关事务结束后这个线程后续的读还是走主库。我一般会在事务模板或者 AOP 切面里统一管理HintManager的生命周期避免手动开关漏掉。还有一个坑跨库事务。ShardingSphere 5.2.1 支持本地事务和柔性事务Seata 集成但默认是本地事务。如果你一个事务里写了 ds0 又写了 ds1本地事务只能保证各自库的回滚跨库一致性要自己兜。生产上要么避免跨库写要么上 Seata别指望 ShardingSphere 自动帮你搞定分布式事务。5. 避坑与排查分片配置不生效时先看这五条5.1 现象SQL 全打到 ds0ds1 一条数据没有原因actual-data-nodes表达式写错或者database-strategy的sharding-column和算法表达式里的字段名不一致。ShardingSphere 在表达式解析失败时不一定报错可能静默退化成单库路由。解决打开sql-show: true看日志里Actual SQL那行路由到了哪个库。如果永远是 ds0检查algorithm-expression里的变量名是否和sharding-column完全一致包括大小写。Groovy 表达式对变量名敏感。5.2 现象启动报Cannot find data source named ds0原因spring.shardingsphere.datasource.names里声明的名字和规则里引用的名字对不上或者数据源配置的层级写错了比如把jdbc-url写成了url。解决先确认names列表和actual-data-nodes里的前缀一致。再确认每个数据源下面的type、driver-class-name、jdbc-url三个必填项都在。5.2.1 对数据源配置的校验比 4.x 严缺一个就启动失败。5.3 现象分页查询 count 结果不对原因ShardingSphere 改写分页 SQL 时count语句和limit语句的路由可能不一致尤其是order by字段不在分片键上时。5.2.1 默认会做归并但归并后的分页在跨多分片时是内存分页数据量大时性能很差。解决分页查询尽量带上分片键让它路由到单分片。如果必须跨分片分页考虑用LIMIT offset, size配合分片键范围查询或者上 ES 做查询分离。别在分片表上做深分页这是设计问题不是配置问题。5.4 现象HintManager不生效读还是走从库原因HintManager是 ThreadLocal 的如果你在 AOP 切面里设置但实际查询在另一个线程比如 MyBatis 的异步执行器、或者AsyncThreadLocal 传不过去。解决确认设置和查询在同一个线程。异步场景需要在异步线程内部重新HintManager.getInstance().setWriteRouteOnly()。另外检查是否有其他切面提前close()了。5.5 现象升级到 5.2.1 后原来的自定义分片算法报 SPI 找不到原因5.x 的 SPI 接口和 4.x 不兼容。4.x 的PreciseShardingAlgorithm在 5.x 里包名和签名都变了直接拿旧实现类注册会报No ShardingAlgorithm。解决按 5.2.1 的StandardShardingAlgorithm接口重写并在META-INF/services下注册org.apache.shardingsphere.sharding.spi.ShardingAlgorithm。注册文件里的类名写全限定名别写错包。6. 验证分片是否真的生效三个我常用的检查手段配完分片最怕的是「看起来生效了其实全路由」。我一般用三个手段交叉验证。第一个是sql-show日志。开启后每次查询会打三行Logic SQL是原始 SQLActual SQL是改写后带真实表名的 SQLRoute是路由到的数据源和表。如果Actual SQL里表名还是t_order而不是t_order0说明分片规则没生效。如果Route里列出了所有分片说明这次查询是全路由检查你的查询条件有没有带分片键。第二个是直接查物理库。分片生效后数据会按规则散到不同库表。你手动连 ds0 和 ds1分别select count(*)如果两边都有数据且分布大致均匀说明写入路由对了。如果一边为空回去看 5.1。第三个是单元测试里断言路由结果。ShardingSphere 提供了ShardingSphereStatement可以拿到路由上下文但更简单的方式是写一个测试插入 10 条不同 user_id 的数据然后分别用 ds0、ds1 的物理连接查断言每个库都有数据。这个测试能挡住大部分配置回归。Test public void testShardingRoute() { for (int i 0; i 10; i) { Order order new Order(); order.setUserId((long) i); order.setAmount(new BigDecimal(100)); orderMapper.insert(order); } // 物理库 ds0 应该有 user_id 为偶数的数据 Long countDs0 jdbcTemplateDs0.queryForObject( select count(*) from t_order0, Long.class); assertThat(countDs0).isGreaterThan(0); }逻辑说明这个测试绕过了 ShardingSphere 的逻辑数据源直接用物理数据源查能验证数据真的落到了预期分片。参数说明jdbcTemplateDs0是你手动建的、指向 ds0 物理库的 JdbcTemplate不要用注入的 ShardingSphere DataSource。最后一个习惯每次改分片规则先在一个干净的测试库上跑一遍全量插入和查询确认路由日志符合预期再上预发。分片配置的坑大多在「表达式写错但不报错」这一类靠肉眼看 YAML 很难发现只能靠日志和物理库对账。希望帮到你。本文还有配套的精品资源点击获取
返回列表