ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ShardingSphere-JDBC 5.5.0 + Spring Boot 分库分表配置实战与避坑指南

ShardingSphere-JDBC 5.5.0 + Spring Boot 分库分表配置实战与避坑指南 ShardingSphere-JDBC 5.5.0 搭配 Spring Boot 做分库分表网上的资料不少但绝大多数要么停留在 4.x 老配置要么只贴一段配置就完事根本没说清楚为什么这样配、踩了哪些坑。我这次在项目里从 0 到 1 把基础配置完整跑通了一遍包括数据分片、分布式主键、读写分离这几块期间踩了几个比较隐蔽的坑最终稳定运行在测试环境。这篇就把整个配置过程、原理和实测中遇到的问题一次性交代清楚适合刚接触 ShardingSphere-JDBC、想快速在 Spring Boot 项目里落地基础分片能力的同学参考。1. 为什么选 ShardingSphere-JDBC以及 5.5.0 的版本兼容前提这一节先解决两个问题第一为什么在众多分库分表方案里选 ShardingSphere-JDBC第二5.5.0 这个版本跟 Spring Boot、JDK 之间的版本匹配这直接关系到你后面能不能顺利启动。1.1 在客户端模式、代理模式和 ShardingSphere-JDBC 之间怎么选ShardingSphere 生态里目前有三种形态JDBC 客户端模式、Proxy 代理模式以及混合模式。JDBC 模式是直接以 jar 包形式嵌入应用数据源被 ShardingSphere 包装一层应用拿到的还是标准DataSource对业务代码几乎零侵入Proxy 则是独立部署一个服务应用连它就像连 MySQL 一样但对运维多了一套部署和监控成本。我个人在中小型项目里更倾向 JDBC 模式原因很直接应用本身就是分布式架构多实例部署已经是常态JDBC 模式不需要额外维护代理集群应用层直连数据库链路短、性能损耗小定位问题也更直观。ShardingSphere-JDBC 本质上就是一套增强版数据源它在DataSource和Connection之间做了一层路由和改写业务层的JdbcTemplate、MyBatis-Plus、Spring Data JPA 都无感知。1.2 版本匹配JDK、Spring Boot、ShardingSphere 三者必须对齐这是最容易在第一步就翻车的地方。ShardingSphere 5.5.0 要求 JDK 8 以上但如果你用的是 Spring Boot 3.x那必须配 JDK 17而 Spring Boot 2.7.x 则可以继续用 JDK 8。我最初在 Spring Boot 3.2 JDK 21 的组合下直接引入 5.5.0启动时报了反射访问相关的 IllegalAccessError后来定位是版本兼容边界问题。另外提醒一下命名习惯artifactId 是shardingsphere-jdbc-core不是sharding-jdbc-core后者是 4.x 的老名字。4.x 和 5.x 的配置结构也完全不同网上很多教程还是spring.shardingsphere.sharding.tables...这套5.x 已经改成了spring.shardingsphere.rules.sharding.tables...这个细节后面会专门讲到。2. 工程初始化与数据源配置这一步最容易产生混乱基础配置里的第一步不是写分片规则而是先把工程和连接弄干净。数据源配置如果一开始就写乱后面所有排查都会变得很痛苦。2.1 用 Spring Initializr 创建工程依赖只需要加一个我用的组合是 Spring Boot 2.7.18 JDK 8 ShardingSphere-JDBC 5.5.0这个组合在稳定性上经过了大量生产验证也是我建议你先照抄的组合。创建工程后pom.xml 里核心依赖就一行dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core/artifactId version5.5.0/version /dependency不需要额外引入 Spring Boot Starter因为shardingsphere-jdbc-core内部通过 Spring 的自动装配机制识别配置并注入。引入这个依赖后原来配置的spring.datasource.*就不再生效数据源会被 ShardingSphere 接管所以你在 yml 里要改用spring.shardingsphere.datasource.*来配置真实数据库连接。如果项目中同时存在其他数据源初始化逻辑比如自定义的 Druid 配置类要特别小心。ShardingSphere 在启动时会尝试包装所有数据源自定义DataSource可能导致循环依赖或者被重复代理我建议先用默认的 HikariCP 把链路跑通再考虑替换连接池。2.2 准备测试库表两个库、两张分片表这一步很关键分片规则需要真实的库表来验证。我在本机创建了两个库ds0和ds1每个库里都有一张t_order_0和t_order_1结构完全一致CREATE TABLE t_order_0 ( order_id BIGINT PRIMARY KEY, user_id BIGINT NOT NULL, order_amount DECIMAL(10,2), create_time DATETIME );为什么要建两张分片表这是为了验证按用户 ID 取模路由的效果同一个用户的所有订单都进同一张表不同用户分散到不同表。实际生产环境往往需要 8 张 16 张甚至更多但基础配置阶段 2 库 × 2 表足够把原理跑明白。提示表结构里分片键user_id一定要建索引因为分片条件下跨库跨表的查询条件最终会落到user_id ?上没有索引会导致全表扫描这个在基础配置阶段就要养成习惯。3. yml 配置文件逐段拆解5.5.0 的 rules 结构必须吃透配置是整篇博文的核心我把完整的 yml 贴出来然后逐段解释每部分的作用和容易出错的地方。这份配置我在本地和测试环境都验证过你可以直接作为模板使用。3.1 完整配置一览spring: shardingsphere: datasource: names: ds0,ds1 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/ds0?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/ds1?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 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 key-generate-strategy: column: order_id key-generator-name: snowflake binding-tables: - t_order sharding-algorithms: db-inline: type: HASH_MOD props: sharding-count: 2 table-inline: type: HASH_MOD props: sharding-count: 2 key-generators: snowflake: type: SNOWFLAKE props: sql-show: true这份配置看起来不复杂但每一行都有讲究我逐个拆开讲。3.2 数据源部分jdbc-url 与 url 的微妙差别spring.shardingsphere.datasource.names定义了逻辑数据源名称多个数据源用逗号分隔。每个数据源配置里最需要注意的是 HikariCP 连接池的参数名应该是jdbc-url而不是url。如果你从 4.x 迁移过来或者习惯了普通 Spring Boot 配置很容易写成url结果启动时数据源初始化失败报错信息却指向连接参数问题排查起来相当绕。还有一个细节是时区参数serverTimezoneAsia/Shanghai一定要带上否则 MySQL 8 驱动版本在插入时间字段时会报时区异常。字符集建议也加上characterEncodingutf8避免中文乱码。type参数写的是 HikariCP。ShardingSphere 5.x 支持通过这个参数指定连接池类型默认也是 HikariCP不写也能跑但显式写出来更清晰。生产环境如果想用 Druid这里改成com.alibaba.druid.pool.DruidDataSource并引入对应依赖即可但要注意 Druid 和 ShardingSphere 在某些版本下存在 SQL 解析兼容问题基础测试阶段别急着换。3.3 分片规则表、库、算法三者怎么挂钩rules.sharding.tables下配置的是逻辑表名。这里有个重要的概念逻辑表是你在 SQL 里写的表名t_order物理表是真正落在数据库里的表t_order_0、t_order_1。ShardingSphere 拦截到对t_order的 SQL 后根据分片规则改写成具体物理表。actual-data-nodes用的是行表达式ds$-{0..1}.t_order_$-{0..1}。它表示物理数据节点分布在 ds0 和 ds1 两个库中每库各有一张t_order_0和t_order_1。行表达式里$-{0..1}是枚举语法也可以写成$-{[0,1]}效果一致。这里必须先定义sharding-algorithms再在database-strategy和table-strategy里通过sharding-algorithm-name引用。5.x 中算法定义统一放到rules.sharding.sharding-algorithms下类型用HASH_MOD它表示哈希取模。sharding-count是取模数量这里为 2。为什么用HASH_MOD而不是MODMOD对分片键直接取模HASH_MOD会先对分片键做哈希再取模如果分片键是字符串类型哈希分布更均匀long 类型的 userId 两者差异不大但统一用 HASH_MOD 可以避免后续改字段类型带来的路由变化。database-strategy和table-strategy都配置为standard标准策略并指定分片键user_id。这里有个设计原则库路由和表路由建议使用同一个分片键。如果库用 user_id 分表却用 order_id 分插入时一次要同时计算两个路由理解成本和维护成本都会翻倍。基础场景别搞复杂一个键打天下等吃透了再考虑多分片键。3.4 分布式主键为什么不直接用数据库自增分片之后每个库每张表都自增的话主键必然重复。ShardingSphere 提供了内置的SNOWFLAKE雪花算法生成器。配置里key-generate-strategy指定主键列order_id和生成器snowflake。注意实体类的orderId字段要定义为Long不是Integer因为雪花算法生成的是 64 位长整型用 Integer 存会溢出报错。这个坑我身边同事踩过值得单独提出来。提示props.sql-show: true开启 SQL 日志调试阶段强烈建议打开你可以在控制台看到每条 SQL 被改写成什么物理表这是理解分片逻辑最好的工具。生产环境记得关掉避免日志量过大和敏感信息泄露。4. 分片原理与路由策略读透这些你才能灵活调整配置很多人配置能跑通但遇到业务场景变化就不知道怎么改原因是对分片原理没吃透。这一节不写代码但比代码更重要。4.1 哈希取模背后的数学逻辑HASH_MOD的核心是确定散列值后对分片数量取模。以user_id 20240312为例先计算哈希值比如某个整数与sharding-count2取模得到 0 或 1结果 0 则路由到 ds0结果 1 则路由到 ds1表策略同样对 user_id 取模决定是 t_order_0 还是 t_order_1这样组合下来固定 user_id 的订单永远落在固定的库、固定的表。这里有一个可以自己验证的恒等式(hash(user_id) % 2) * 2 (hash(user_id) % 2)最终得到的 0~3 之间的编号恰好对应 2 库 × 2 表四种组合。理解这个结构你就明白为什么sharding-count必须和数据节点数量匹配——如果库有 2 个、表有 2 张但库策略的sharding-count配成 4就会有一部分数据路由到不存在的 ds2直接报错找不到数据源。4.2 standard、inline 和 complex三种策略的应用边界5.x 里最常用的标准策略是standard它要求配置sharding-column和sharding-algorithm-name。标准策略支持全路由、单片路由、范围查询路由如果查询条件里有分片键等值条件直接定位到单库单表如果没有分片键则全库全表广播。inline是另一种算法类型通过 Groovy 表达式实现简单取模或行表达式路由适合分片键简单且逻辑固定的场景。complex策略用于多个分片键的复合路由比如同时按 user_id 和 order_id 分片需要自定义ComplexKeysShardingAlgorithm。基础配置阶段不要碰 complex它需要写 Java 类实现接口复杂度上了一个台阶。4.3 绑定表是避免笛卡尔积的关键配置里的binding-tables很多人不理解我详细说一下。假设你后续有订单表和订单明细表两张表都按 user_id 分片。如果没配置绑定表分片键相同的两张表在关联查询时ShardingSphere 会把所有分片表组合都纳入连接计算也就是笛卡尔积导致查询结果膨胀甚至错误。配置绑定表之后ShardingSphere 会保证两张表中分片键对应的数据落在同一物理节点上关联查询只在绑定的库表之间执行。基础配置阶段可能还涉及不到关联查询但从一开始就把绑定表关系的习惯建立起来后面加表不踩坑。4.4 广播表和单表规则什么时候需要它们分片配置里还有一种常见情况有些表不需要分片比如省份字典表、配置表。ShardingSphere 5.x 中未在分片规则中的表默认会走逻辑数据源但 5.5.0 对单表有一个处理策略默认忽略未配置的表或者通过rules.sharding.tables之外的机制访问。如果你有字典表需要关联查询建议配置广播表rules: sharding: broadcast-tables: - t_dict广播表会全库全表同步每次写操作都广播到所有库里。这样关联查询字典表时ShardingSphere 知道所有库都有这张表不会报表不存在。5. 实测中的避坑记录三个隐蔽问题与完整排查链路这一节是我最想分享的内容。配置看起来很短但跑起来遇到的问题不少我记录的这些每一个都是真实经历按排查链路给出方便你遇到类似问题时照着定位。5.1 坑一4.x 配置残留导致 5.x 启动静默失败现象服务能启动但日志里没有任何 ShardingSphere 初始化信息打开sql-show也不打印分片 SQL查数据却查不到。排查链路先确认依赖版本mvn dependency:tree | grep sharding发现引入的是 5.5.0 没错检查 yml 前缀发现自己把老项目的配置直接复制过来用的是spring.shardingsphere.sharding.tables...查 5.x 文档确认配置结构变成了spring.shardingsphere.rules.sharding.tables...修正后重启日志出现ShardingSphere 5.5.0 ... initialized和分片 SQL问题解决这个坑的隐蔽性在于5.x 兼容了 4.x 的 datasource 配置前缀但对 rules 结构并不完全兼容导致部分配置被静默忽略服务还能继续用spring.datasource兜底启动。建议引入 5.5.0 后先把properties里sql-show打开看启动日志有没有分片规则初始化记录没有就说明配置没被识别。5.2 坑二Spring Boot 3.x JDK 21 的反射兼容问题现象应用启动时抛InaccessibleObjectException提示Unable to make field ... accessible指向 ShardingSphere 内部通过反射设置字段。排查链路先看堆栈前几行确认是反射访问问题不是业务代码问题尝试加 JVM 参数--add-opens java.base/java.langALL-UNNAMED等加了好几个仍不行查看 GitHub issues发现 5.5.0 官方对 JDK 21 的完整支持存在边界问题最终放弃硬扛把项目降级为 Spring Boot 2.7.18 JDK 8 组合一次通过这里想提醒大家技术选型阶段不要盲目追求最新版本。Spring Boot 3.x 是重要的升级但如果当前团队对分片方案不熟落地这类基础中间件时优先选择经过更多生产验证的组合能省下大量排查时间。先把功能跑通后续再规划升级路径。5.3 坑三同库关联查询出现笛卡尔积现象对订单表和订单明细表做 JOIN 查询返回的数据量是预期的 4 倍而且有明显重复。排查链路打开sql-show观察实际执行的物理 SQL 数量发现 ShardingSphere 把 2 库 × 2 表 × 2 表组合后执行了多次查询显然没有正确的绑定关系检查 yml发现两张表的binding-tables没有配置表路由策略的 sharding-column 也不完全一致把两张表加入绑定表同时统一分片键和分片算法重启后 JOIN 查询只在同库同表组合中执行数据量恢复正常排查这类问题的通用方法是先看控制台打印的路由 SQL 数量如果出现超过预期组合数的执行基本就是绑定表缺失或分片键不一致导致的笛卡尔积。5.4 坑四MyBatis-Plus 与分布式主键的叠加冲突现象使用 MyBatis-Plus 插入数据时order_id始终为空数据库主键没生成。排查链路最初以为是 ShardingSphere 的雪花算法没生效但日志显示 SQL 已经经过改写检查实体类发现TableId(type IdType.AUTO)MyBatis-Plus 认为主键由数据库自增插入时不会携带 order_id把TableId(type IdType.INPUT)或直接去掉 IdType 配置让主键值由 ShardingSphere 生成插入后 order_id 正确生成且全库唯一这里需要理解一个协调机制ShardingSphere 的主键生成发生在 SQL 改写阶段它会在 INSERT SQL 中补充主键列。但 MyBatis-Plus 的IdType.AUTO会导致框架层面的主键策略覆盖掉外部补充逻辑所以显式设为INPUT让值从外部传入是最稳妥的方案。6. 事务边界与进阶测试确认分片效果没有你想的那么简单配置跑通只是开始接下来我会讲两个关键验证项事务边界和读写分离这两个决定你能否把配置推向生产。6.1 本地事务的边界跨库事务默认是不保证的ShardingSphere-JDBC 默认支持的是本地事务即通过 Spring 的Transactional管理。理解关键点在于如果一次事务只涉及单库单表那么事务完整生效一旦 SQL 被路由到多个库本地事务只能保证单个库内的 ACID跨库的原子性是无法保证的。举个例子一次插入操作把一条数据写到 ds0.t_order_0另一条写到 ds1.t_order_1此时 d0 写成功、ds1 写失败。在本地事务下ds0 的写入会被提交ds1 的写入会回滚或报错整个操作并不是原子的。如果业务必须跨库原子操作需要引入分布式事务方案。ShardingSphere 提供了两种对接方式XA 强一致事务引入shardingsphere-transaction-xa-core通过ShardingSphereTransactionType(TransactionType.XA)标注适用于短事务、对一致性要求极高的场景Seata 柔性事务引入 Seata 相关依赖和配置适用于长事务、高并发场景牺牲强一致性换取性能和可用性基础配置阶段我的建议是先别引入分布式事务把分片逻辑验证清楚再说。很多业务根本不需要跨库事务只要在设计分片键时保证同一用户的所有数据落在同一库同一表事务就始终是单库事务问题自然规避。比如订单主表和明细表按同一个 user_id 分片它们永远在一个库里事务天然安全。6.2 读写分离和分片能否叠加以及怎么验证ShardingSphere-JDBC 5.5.0 支持分片 读写分离联合配置也就是分片的每个片里都有一个主库一个从库。配置结构是在每个数据源组内配置读写分离规则rules: readwrite-splitting: >INSERT INTO t_order (order_id, user_id, order_amount) VALUES (234567890123456789, 1001, 99.00);因为使用行表达式和 HASH_MOD 路由SQL 日志里应出现Actual SQL: ds0 ::: INSERT INTO t_order_1 ...说明这条数据被路由到了 ds0 库的 t_order_1 表。据此可以快速确认路由是否符合预期。注意如果日志里出现的是Actual SQL: ds0 ::: INSERT INTO t_order_0 ...这不一定代表配置错误可能是HASH_MOD(1001) % 2 1但内部还有哈希步骤的原因。此时关注点是路由结果是否一致也就是说同一个 user_id 多次插入路由目标应该完全一致。如果同一个 user_id 一会儿进 t_order_0一会儿进 t_order_1那才是配置问题多半是行表达式或算法配置不稳定。6.4 下一步可以扩展的配置方向基础配置跑通后按我个人的实践顺序接下来可以逐步扩展这些内容多分片键升级为 complex 策略自定义分片算法满足按时间 用户 ID 同时分片的业务分片审计配置rules.sharding.audit限制不带分片键的全库扫描 SQL强制业务层养成良好的查询习惯数据迁移扩容时从 2 库扩到 4 库sharding-count调整带来的数据重分布问题需要配合迁移工具或双写方案监控埋点通过 ShardingSphere 的 metrics 接口对接 Prometheus追踪每个分片节点的读写压力为后续容量规划提供数据支撑这些方向每个都是一个大主题等基础配置吃透了再去碰不迟。务必记住一条红线分片键一旦上线业务代码里绝不能再出现 UPDATE/DELETE 不带分片键的写法否则 ShardingSphere 只能全库全表扫描改写性能直接崩掉。7. 附一份可以直接抄的完整 pom 依赖清单如果上面的每一步都顺利走到这里说明基础链路已经打通了。最后给你一份我在测试环境验证过的完整依赖清单避免你在依赖管理上反复试错dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core/artifactId version5.5.0/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency如果遇到 mybatis-plus 和 shardingsphere 同时引入时的依赖冲突报错重点检查 mybatis-plus-boot-starter 版本建议用 3.5.5 及以上老版本里的 mybatis-spring 和 Spring Boot 2.7 之间存在兼容问题。从 0 到 1 跑通 ShardingSphere-JDBC 5.5.0 基础配置后我最大的体会是这个框架的入门门槛其实不高真正的门槛在于你对自己业务数据特征的理解——分片键选什么、分片数量定多少、绑定表有哪些、事务边界在哪里。配置本身只是把这些决策翻译成 yml 而已。如果你刚开始接触别急着一次配置所有表和算法先拿一张业务量最大的表、两个库、两个分片跑通整条链路确认路由、日志、主键、事务各个方面都符合预期再横向推广。这样的节奏踩坑最少成长反而最快。
返回列表