Spring Boot + ShardingSphere 实现分库分表 + 读写分离实战

🚀 Spring Boot + ShardingSphere 实现分库分表 + 读写分离(涵盖99%真实场景)

🏷️ 标签:ShardingSphere、分库分表、读写分离、MySQL 主从、Spring Boot 实战

分库分表 vs 读写分离 vs 主从配置与数据库高可用架构区别


📚 目录导航


🔍 一、场景说明

🚨 实际项目中,数据库面临两类瓶颈:

  • 📌 数据量太大 → 单库单表撑不住 → 使用 分库分表 拆解压力
  • 📌 读操作压力太大 → 单库处理不过来 → 使用 读写分离 转移压力

⚡ 本项目整合了这两类方案,构建如下特性系统:

  • 两个逻辑库 ds0ds1
  • ds0 搭建一主两从(主库:ds0,从库:ds0_slave1,ds0_slave2)
  • 表按照用户 ID 分片(user_id % 2)
  • 主写从读,轻松实现读写分离

🧱 二、架构图

在这里插入图片描述

📝 说明:

  • 用户通过 Controller 发起请求
  • ShardingSphere JDBC 根据操作类型选择库、表
  • 如果是写入操作,走 ds0ds1 的主库
  • 如果是读取操作,优先走 ds0_slave1ds0_slave2 从库,减轻主库压力

⚙️ 三、核心配置(application.yml)

🧩 我们通过配置 ShardingSphere 的两类规则:shardingreadwrite-splitting 实现业务目标。

spring:
  shardingsphere:
    datasource:
      names: ds0, ds1, ds0_slave1, ds0_slave2
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        jdbc-url: jdbc:mysql://mysql-master:3307/testdb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
        username: root
        password: root
      ds0_slave1:
        type: com.zaxxer.hikari.HikariDataSource
        jdbc-url: jdbc:mysql://mysql-slave1:3308/testdb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
        username: root
        password: root
      ds0_slave2:
        type: com.zaxxer.hikari.HikariDataSource
        jdbc-url: jdbc:mysql://mysql-slave2:3309/testdb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
        username: root
        password: root
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        jdbc-url: jdbc:mysql://mysql-master:3306/testdb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
        username: root
        password: root123456
    rules:
      sharding:
        tables:
          t_user:
            # 注意:此处使用物理库名(ds0、ds1)来定义 actual-data-nodes
            actual-data-nodes: ds$->{0..1}.t_user_$->{0..1}
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: database_inline
            table-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: table_inline
            key-generate-strategy:
              column: id
              key-generator-name: snowflake
        sharding-algorithms:
          database_inline:
            type: INLINE
            props:
              algorithm-expression: ds${user_id % 2}
          table_inline:
            type: INLINE
            props:
              algorithm-expression: t_user_${user_id % 2}
        key-generators:
          snowflake:
            type: SNOWFLAKE

      readwrite-splitting:
        data-sources:
          rw_ds0:
            static-strategy:
              write-data-source-name: ds0
              read-data-source-names: [ds0_slave1,ds0_slave2]
            load-balancer-name: round_robin
          rw_ds1:
            static-strategy:
              write-data-source-name: ds1
              read-data-source-names: [ds1]
            load-balancer-name: round_robin
        load-balancers:
          round_robin:
            type: ROUND_ROBIN
    props:
      sql-show: true

logging:
  level:
    org.apache.shardingsphere: DEBUG

1. 数据源配置 (datasource)

定义了 4 个 MySQL 数据源(2 个主库 ds0/ds1 和 2 个从库 ds0_slave1/ds0_slave2)。

配置项说明
spring.shardingsphere.datasource.names数据源名称列表:ds0, ds1, ds0_slave1, ds0_slave2
ds0.jdbc-url主库 ds0 的 JDBC 连接 URL(端口 3307)
ds0.username主库 ds0 用户名(root
ds0.password主库 ds0 密码(root
ds0_slave1.jdbc-url从库 ds0_slave1 的 JDBC URL(端口 3308)
ds0_slave2.jdbc-url从库 ds0_slave2 的 JDBC URL(端口 3309)
ds1.jdbc-url主库 ds1 的 JDBC URL(端口 3306,与 ds0 不同实例)

2. 分片规则 (sharding)

配置表 t_user 的分片策略和分布式 ID 生成。

分片表配置
配置项作用示例值说明
tables.t_user.actual-data-nodes定义物理节点ds$->{0..1}.t_user_$->{0..1}表达式生成所有物理表,如 ds0.t_user_0ds1.t_user_1
tables.t_user.database-strategy.standard.sharding-column分库列user_id根据 user_id 计算数据存储的库。
tables.t_user.database-strategy.standard.sharding-algorithm-name分库算法名称database_inline引用 sharding-algorithms 中定义的算法。
tables.t_user.table-strategy.standard.sharding-column分表列user_id根据 user_id 计算数据存储的表。
tables.t_user.table-strategy.standard.sharding-algorithm-name分表算法名称table_inline引用 sharding-algorithms 中定义的算法。
tables.t_user.key-generate-strategy.column主键列id指定自动生成主键的列。
tables.t_user.key-generate-strategy.key-generator-name主键生成器名称snowflake使用 Snowflake 算法生成分布式 ID。
分片算法
配置项作用示例值说明
sharding-algorithms.database_inline.type算法类型INLINE使用行表达式(Inline)分片算法。
sharding-algorithms.database_inline.props.algorithm-expression分库表达式ds${user_id % 2}根据 user_id % 2 计算库索引(0 或 1)。
sharding-algorithms.table_inline.type算法类型INLINE使用行表达式分片算法。
sharding-algorithms.table_inline.props.algorithm-expression分表表达式t_user_${user_id % 2}根据 user_id % 2 计算表索引(0 或 1)。
分布式 ID
配置项作用示例值说明
key-generators.snowflake.type主键生成器类型SNOWFLAKE使用 Snowflake 算法生成分布式唯一 ID。

3. 读写分离规则 (readwrite-splitting)

配置读写分离数据源和负载均衡策略。

数据源 rw_ds0
配置项说明
write-data-source-name写库数据源:ds0(主库)
read-data-source-names读库数据源列表:[ds0_slave1, ds0_slave2](两个从库)
load-balancer-name负载均衡算法:round_robin(轮询)
数据源 rw_ds1
配置项说明
write-data-source-name写库数据源:ds1(主库)
read-data-source-names读库数据源列表:([ds1]),表示仅使用写库读
load-balancer-name负载均衡算法:round_robin(未实际生效)
负载均衡器
配置项说明
round_robin.type算法类型:ROUND_ROBIN(轮询调度)

4. 属性配置 (props)

配置项说明
sql-show: true打印 SQL 日志(便于调试)

配置逻辑总结

spring:
  shardingsphere:
    datasource:
      names: ds0, ds1, ds0_slave1, ds0_slave2
  • 定义三个数据源

    • ds0: 主库
    • ds0_slave: 从库(ds0 复制)
    • ds1: 第二个分片主库
    rules:
      sharding:
        tables:
          t_user:
            actual-data-nodes: rw_ds$->{0..1}.t_user_$->{0..1}
  • t_user 分片规则:共有 2 库 x 2 表 结构
            table-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: user_inline
  • 按照 user_id 做分片(水平拆表)
        sharding-algorithms:
          user_inline:
            type: INLINE
            props:
              algorithm-expression: t_user_${user_id % 2}
  • 表名后缀 = user_id % 2,如 t_user_0t_user_1

      readwrite-splitting:
        data-sources:
          rw_ds0:
            static-strategy:
              write-data-source-name: ds0
              read-data-source-names: [ds0_slave1,ds0_slave1]
  • 配置 rw_ds0 为读写分离库
  • ds0 负责写入,ds0_slave1,ds0_slave2 负责读取
          rw_ds1:
            static-strategy:
              write-data-source-name: ds1
              read-data-source-names: [ds1]
  • ds1 暂无从库,只支持主库写读

潜在问题

  1. rw_ds1 无读库

    • 配置中 rw_ds1.read-data-source-namesds1,可能导致读请求全部发往主库 ds1,增加压力。
  2. 分片与读写分离结合

    • 实际数据节点 rw_ds0.t_user_0rw_ds1.t_user_1 的分片逻辑需确保数据均匀分布。

🗃️ 四、数据库建表SQL

db0db1 上执行以下语句,创建两个分表:

CREATE TABLE t_user_0 (
  id BIGINT PRIMARY KEY,
  username VARCHAR(100),
  user_id INT
);

CREATE TABLE t_user_1 LIKE t_user_0;

📌 ds0_slave1, ds0_slave2 是主库 ds0 的复制库,MySQL 自动同步,无需手动建表。


👨‍💻 五、关键代码

✅ 项目概览

sharding-demo/
├── src/
│   ├── main/
│   │   ├── java/com/example/shardingdemo/
│   │   │   ├── controller/
│   │   │   ├── entity/
│   │   │   ├── mapper/
│   │   │   ├── ShardingDemoApplication.java
│   └── resources/
│       ├── application.yml
├── pom.xml

✅ User 实体类

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User {
    private Long id;
    private String username;
    private Integer userId; // 分片键
}

✅ Mapper 接口

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.shardingdemo.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {

}

✅ Controller 控制器

import com.example.shardingdemo.entity.User;
import com.example.shardingdemo.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
    private final UserMapper userMapper;

    @PostMapping("/add")
    public String addUser(@RequestParam String name, @RequestParam Integer userId) {
        User user = new User(System.currentTimeMillis(), name, userId);
        userMapper.insert(user);
        return "User added.";
    }

    @GetMapping("/list")
    public List<User> selectList() {
        return userMapper.selectList(null);
    }

    @GetMapping("/selectById")
    public User selectById(Long id){
        return userMapper.selectById(id);
    }

}

🧪 六、测试验证

✅ 添加用户

http://localhost:8080/user/add?name=Alice&userId=11

控制台输出:

Actual SQL: rw_ds1 ::: INSERT INTO t_user_1 ...

✅ 说明:

  • userId = 11,落到 rw_ds1(ds1)
  • 且表名为 t_user_1,符合 % 2 = 1 的路由逻辑

🧠 七、总结与建议

特性说明
💡 分库分表扩展写能力,解决单表瓶颈
💡 读写分离减轻主库压力,提高系统吞吐
✅ 可扩展性新增库或表只需扩展路由规则,无需修改业务代码
✅ 高性能多线程批量插入、查询等场景提升明显
🔒 安全性通过主从架构,规避读阻塞或死锁导致系统不可用

📢 建议配合 Docker + MySQL 主从配置部署验证,效果最佳。


❤️ 如果你觉得这篇文章有帮助:

  • 点赞 👍
  • 收藏 ⭐
  • 留言 💬
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值