分库分表-支付服务
阅读此文前,建议小伙伴先阅读分库分表的前置知识,对分库分表 和 shardingsphere 有了大概的理解后,在继续阅读本文
介绍
在开始介绍前,需要先知道大麦网中数据库表的关系是怎么样,关于数据库表设计的详细介绍,小伙伴可跳转到相关文档
配置
引入 ShardingSphere 的相关依赖
xml
<properties>
<shardingsphere.version>5.3.2</shardingsphere.version>
</properties>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>${shardingsphere.version}</version>
<exclusions>
<exclusion>
<artifactId>logback-classic</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>
</exclusions>
</dependency>根据规则进行分库分表的规则配置
ShardingSphere 官网的规则配置说明:
支付服务相关配置:
yaml
spring:
datasource:
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
url: jdbc:shardingsphere:classpath:shardingsphere-program.yamlshardingsphere-pay.yaml配置:
yaml
dataSources:
# 第一个支付库
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:3306/damai_pay_0?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: root
# 第二个支付库
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:3306/damai_pay_1?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: root
rules:
# 分库分表规则
- !SHARDING
tables:
# 对d_pay_bill表进行分库分表
d_pay_bill:
# 库为damai_pay_0 damai_pay_1 表为d_pay_bill_0 至 d_pay_bill_1
actualDataNodes: ds_${0..1}.d_pay_bill_${0..1}
# 分库策略
databaseStrategy:
standard:
# 使用out_order_no作为分片键
shardingColumn: out_order_no
# 用out_order_no列使用hash取模作为分库算法
shardingAlgorithmName: databasePayHashModModel
# 分表策略
tableStrategy:
standard:
# 使用out_order_no作为分片键
shardingColumn: out_order_no
# 用out_order_no列使用hash取模作为分表算法
shardingAlgorithmName: tablePayHashModModel
# 对d_refund_bill表进行分库分表
d_refund_bill:
# 库为damai_pay_0 damai_pay_1 表为d_refund_bill_0 至 d_refund_bill_1
actualDataNodes: ds_${0..1}.d_refund_bill_${0..1}
# 分库策略
databaseStrategy:
standard:
# 使用out_order_no作为分片键
shardingColumn: out_order_no
# 用out_order_no列使用hash取模作为分库算法
shardingAlgorithmName: databaseRefundHashModModel
# 分表策略
tableStrategy:
standard:
# 使用out_order_no作为分片键
shardingColumn: out_order_no
# 用out_order_no列使用hash取模作为分表算法
shardingAlgorithmName: tableRefundHashModModel
# 具体的算法(采用基因位分离策略:分库用低位bit0,分表用次低位bit1,避免数据倾斜)
shardingAlgorithms:
# d_pay_bill表分库算法 - 使用out_order_no的hashCode低位(bit0)
databasePayHashModModel:
type: INLINE
props:
algorithm-expression: ds_${Math.abs(out_order_no.hashCode()) % 2}
# d_pay_bill表分表算法 - 使用out_order_no的hashCode次低位(bit1)
tablePayHashModModel:
type: INLINE
props:
algorithm-expression: d_pay_bill_${(Math.abs(out_order_no.hashCode()).intdiv(2)) % 2}
# d_refund_bill表分库算法 - 使用out_order_no的hashCode低位(bit0)
databaseRefundHashModModel:
type: INLINE
props:
algorithm-expression: ds_${Math.abs(out_order_no.hashCode()) % 2}
# d_refund_bill表分表算法 - 使用out_order_no的hashCode次低位(bit1)
tableRefundHashModModel:
type: INLINE
props:
algorithm-expression: d_refund_bill_${(Math.abs(out_order_no.hashCode()).intdiv(2)) % 2}
props:
# 打印真实sql
sql-show: true分片算法详解(核心重点)
问题背景:为什么传统 HASH_MOD 算法会导致数据倾斜?
如果分库和分表都使用相同的 hashCode % 2 算法:
groovy
// 错误的配置方式
分库: ds_${Math.abs(out_order_no.hashCode()) % 2}
分表: d_pay_bill_${Math.abs(out_order_no.hashCode()) % 2}导致的问题:
plain
表_0 表_1
库_0 (ds_0) ✔️ ✖️ ← 偶数库的奇数表永远没数据
库_1 (ds_1) ✖️ ✔️ ← 奇数库的偶数表永远没数据解决方案:intdiv 基因位分离策略
groovy
// 正确的配置方式
分库: ds_${Math.abs(out_order_no.hashCode()) % 2} // 使用 bit0
分表: d_pay_bill_${(Math.abs(out_order_no.hashCode()).intdiv(2)) % 2} // 使用 bit1intdiv 是什么?
intdiv 是 Groovy 的整数除法,等价于除以2取整数部分:
groovy
hashCode.intdiv(2) 等价于 hashCode / 2 (取整数部分)核心原理:使用 hashCode 的不同二进制位
plain
hashCode 的二进制表示: ... bit3 bit2 bit1 bit0
│ │ │ │
│ │ │ └─── hashCode % 2 → 分库
│ │ └─────── (hashCode/2) % 2 → 分表hashCode % 2:提取最低位 (bit0),用于决定分库(hashCode.intdiv(2)) % 2:先除以2,再取最低位,即提取次低位 (bit1),用于决定分表
完整数据分布表
| hashCode % 4 | bit0 (hashCode%2) | bit1 ((hashCode/2)%2) | 分库 | 分表 | 路由结果 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | ds_0 | _0 | ds_0.d_pay_bill_0 |
| 1 | 1 | 0 | ds_1 | _0 | ds_1.d_pay_bill_0 |
| 2 | 0 | 1 | ds_0 | _1 | ds_0.d_pay_bill_1 |
| 3 | 1 | 1 | ds_1 | _1 | ds_1.d_pay_bill_1 |
结果:所有4个分片都有数据,均匀分布!
plain
表_0 表_1
库_0 (ds_0) ✔️ ✔️ ← 偶数库的奇数表和偶数表都有数据
库_1 (ds_1) ✔️ ✔️ ← 奇数库的奇数表和偶数表都有数据为什么支付和退款使用相同的分片键?
支付账单和退款账单都使用 out_order_no 作为分片键,好处:
- 关联查询优化:同一订单的支付和退款记录在同一个库中
- 事务一致性:便于在同一分片内处理支付和退款的事务
架构图
plain
┌─────────────────────────────────────────────────────────────┐
│ Pay Service │
├─────────────────────────────────────────────────────────────┤
│ ShardingSphere │
│ (基因位分离 - hashCode路由) │
├──────────────────────────┬──────────────────────────────────┤
│ damai_pay_0 │ damai_pay_1 │
├──────────────────────────┼──────────────────────────────────┤
│ d_pay_bill_0 │ d_pay_bill_0 │
│ d_pay_bill_1 │ d_pay_bill_1 │
│ d_refund_bill_0 │ d_refund_bill_0 │
│ d_refund_bill_1 │ d_refund_bill_1 │
└──────────────────────────┴──────────────────────────────────┘数据分布示例
假设有以下订单号:
ORD202401001→ hashCode=123456ORD202401002→ hashCode=789012
| 订单号 | hashCode | 分库 | 分表 |
|---|---|---|---|
| ORD202401001 | 123456 | ds_0 (偶数) | d_pay_bill_0 |
| ORD202401002 | 789012 | ds_0 (偶数) | d_pay_bill_0 |
注:实际路由结果取决于订单号的实际 hashCode 值
更新: 2026-01-23 10:10:21
原文: https://www.yuque.com/u22210564/ykdrdh/gnvtmbv0csf2x9os