PostgreSQL 使用 ShardingSphere 实现更强大的分库分表 + 读写分离

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.6k人参与

该文章已生成可运行项目,

你已经掌握了 Spring Boot + PostgreSQL 读写分离 的基础实现,现在我们来进入 进阶阶段:使用 Apache ShardingSphere 实现 分库分表 + 读写分离 的企业级解决方案。

为什么选择 ShardingSphere?

  • 开源(Apache 顶级项目)
  • 无侵入:兼容 JPA、MyBatis、原生 JDBC
  • 支持读写分离、分库分表、加密、影子库等
  • 提供 ShardingSphere-JDBC(轻量级)和 ShardingSphere-Proxy(透明网关)两种模式

🚀 一、架构目标

我们将实现:

                   +---------------------+
                   |   Application       |
                   |   ShardingSphere-JDBC |
                   +----------+----------+
                              |
         +---------------------+----------------------+
         |                                            |
+--------v---------+                     +------------v------------+
|   master_db_0     |<--- 流复制 --------|   replica_db_0 (read)     |
|   (write)         |                   |                           |
|   192.168.1.10:5432 |                   |   192.168.1.11:5432       |
+-------------------+                   +---------------------------+

+--------v---------+                     +------------v------------+
|   master_db_1     |<--- 流复制 --------|   replica_db_1 (read)     |
|   (write)         |                   |                           |
|   192.168.1.12:5432 |                   |   192.168.1.13:5432       |
+-------------------+                   +---------------------------+
  • 读写分离:写走主库,读走从库
  • 分库分表:按 user_id 模 2 分到两个库,每库一张表
  • 自动路由:SQL 自动路由到正确库和表

📦 二、前置条件

  1. 4 台 PostgreSQL 实例(可用 Docker 模拟)
    • master_db_0:192.168.1.10:5432
    • replica_db_0:192.168.1.11:5432
    • master_db_1:192.168.1.12:5432
    • replica_db_1:192.168.1.13:5432
  2. 主从复制已配置完成
  3. Spring Boot 项目

📁 三、项目结构

src/
├── main/
│   ├── java/
│   │   └── com/example/sharding/
│   │       ├── ShardingSphereConfig.java
│   │       ├── service/UserService.java
│   │       └── model/User.java
│   └── resources/
│       └── application.yml

✅ 四、Maven 依赖(ShardingSphere-JDBC)

<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
    </dependency>

    <!-- ShardingSphere-JDBC 核心依赖 -->
    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
        <version>5.3.2</version>
    </dependency>
</dependencies>

✅ 五、application.yml 配置(核心)

# application.yml
spring:
  shardingsphere:
    # ========== 数据源配置 ==========
    datasource:
      names: master0,slave0,master1,slave1

      # 主库 0
      master0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.1.10:5432/myapp_db
        username: myapp_dev
        password: MyAppDevPass!2025

      # 从库 0
      slave0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.1.11:5432/myapp_db
        username: myapp_dev
        password: MyAppDevPass!2025

      # 主库 1
      master1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.1.12:5432/myapp_db
        username: myapp_dev
        password: MyAppDevPass!2025

      # 从库 1
      slave1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: org.postgresql.Driver
        jdbc-url: jdbc:postgresql://192.168.1.13:5432/myapp_db
        username: myapp_dev
        password: MyAppDevPass!2025

    # ========== 读写分离配置 ==========
    rules:
      # 读写分离
      readwrite-splitting:
        data-sources:
          # 逻辑数据源名称:ds0
          ds0:
            write-data-source-name: master0
            read-data-source-names: slave0
            load-balancer-name: round_robin
          # 逻辑数据源名称:ds1
          ds1:
            write-data-source-name: master1
            read-data-source-names: slave1
            load-balancer-name: round_robin

      # 分库分表规则
      sharding:
        # 逻辑表配置
        tables:
          t_user:
            # 实际表:ds0.t_user_0, ds0.t_user_1, ds1.t_user_0, ds1.t_user_1
            actual-data-nodes: ds${0..1}.t_user_${0..1}
            # 分库策略:按 user_id 模 2 决定用 ds0 还是 ds1
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: database-inline
            # 分表策略:按 id 模 2 决定用 _0 还是 _1
            table-strategy:
              standard:
                sharding-column: id
                sharding-algorithm-name: table-inline

        # 分片算法
        sharding-algorithms:
          database-inline:
            type: INLINE
            props:
              algorithm-expression: ds${user_id % 2}
          table-inline:
            type: INLINE
            props:
              algorithm-expression: t_user_${id % 2}

        # 默认策略
        default-database-strategy:
          none: # 不分库(用于其他表)
        default-table-strategy:
          none: # 不分表

    # ========== 全局配置 ==========
    props:
      sql-show: true  # 显示实际执行的 SQL(调试用)

✅ 六、实体类 User.java

// com/example/sharding/model/User.java
package com.example.sharding.model;

import jakarta.persistence.*;
import java.util.UUID;

@Entity
@Table(name = "t_user")
public class User {

    @Id
    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID id;

    @Column(name = "user_id", nullable = false)
    private Long userId;  // 用于分库的字段

    @Column(name = "username", length = 50)
    private String username;

    @Column(name = "email", length = 100)
    private String email;

    // 构造函数、getter、setter
    public User() {}

    public User(Long userId, String username, String email) {
        this.userId = userId;
        this.username = username;
        this.email = email;
    }

    // getter 和 setter 省略
    public UUID getId() { return id; }
    public void setId(UUID id) { this.id = id; }

    public Long getUserId() { return userId; }
    public void setUserId(Long userId) { this.userId = userId; }

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

✅ 七、Service 层测试

// com/example/sharding/service/UserService.java
package com.example.sharding.service;

import com.example.sharding.model.User;
import com.example.sharding.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 创建用户
     * 根据 userId % 2 路由到 ds0 或 ds1
     * 根据 id % 2 决定存入 t_user_0 或 t_user_1
     */
    @Transactional
    public User createUser(Long userId, String username, String email) {
        User user = new User(userId, username, email);
        return userRepository.save(user);
    }

    /**
     * 查询所有用户
     * 自动从对应的从库读取
     */
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    /**
     * 按 ID 查询
     */
    public User getUserById(String id) {
        return userRepository.findById(id).orElse(null);
    }
}

✅ 八、Repository

// com/example/sharding/repository/UserRepository.java
package com.example.sharding.repository;

import com.example.sharding.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.UUID;

@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
}

✅ 九、工作流程说明

操作路由逻辑
save(user)userId=100100 % 2 = 0 → 路由到 ds0(master0)
id % 2 → 决定存 t_user_0t_user_1
findAll()ds0.slave0ds1.slave1 读取
findById(id)根据 id % 2 确定表,自动从对应从库读取

✅ 十、验证 SQL 路由

启用 sql-show: true 后,你会看到类似日志:

[INFO ] ShardingSphere-SQL - Logic SQL: INSERT INTO t_user ...
[INFO ] ShardingSphere-SQL - Actual SQL: ds0 ::: INSERT INTO t_user_0 ...
[INFO ] ShardingSphere-SQL - Actual SQL: ds0 ::: SELECT * FROM t_user_0 ...

✅ 十一、ShardingSphere 优势总结

特性说明
🔌 无侵入原有 JPA/MyBatis 代码无需修改
🧩 灵活分片支持自定义分片算法(Java 类)
🔄 读写分离自动读从库,写主库
🔐 数据加密敏感字段自动加解密
🧹 影子库测试 SQL 走影子库,不影响生产
📊 监控集成 Prometheus、OpenTelemetry

✅ 十二、进阶建议

场景推荐方案
更复杂分片逻辑实现 StandardShardingAlgorithm 接口
多租户tenant_id 分库
全文搜索结合 Elasticsearch 使用
高可用使用 ShardingSphere-Proxy + Keepalived
自动化运维使用 ShardingSphere-Scaling 在线数据迁移

🎁 附加:Docker 快速搭建主从复制(可选)

# 启动主库
docker run -d --name pg-master \
  -e POSTGRES_PASSWORD=123456 \
  -p 5432:5432 \
  postgres:15

# 启动从库(需配置流复制)
# 可使用 docker-compose + repmgr 实现

✅ 总结

你现在已经掌握了:

  • ShardingSphere-JDBC 的集成方式
  • 分库分表 + 读写分离的配置
  • 基于 user_idid 的路由策略
  • 无侵入式接入现有项目

这套方案适用于 高并发、大数据量、读多写少 的系统,如电商、社交、内容平台。

本文章已经生成可运行项目
以下是 ShardingSphere 5.4.1 连接 PostgreSQL 进行无需分库分表读写分离的 YAML 配置示例: ```yaml mode: type: Standalone repository: type: File props: path: ./conf overwrite: false dataSources: ds_master: url: jdbc:postgresql://master_host:5432/your_database username: your_username password: your_password connectionTimeoutMilliseconds: 30000 idleTimeoutMilliseconds: 60000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 1 maintenanceIntervalMilliseconds: 30000 ds_slave_0: url: jdbc:postgresql://slave_host_0:5432/your_database username: your_username password: your_password connectionTimeoutMilliseconds: 30000 idleTimeoutMilliseconds: 60000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 1 maintenanceIntervalMilliseconds: 30000 rules: - !READWRITE_SPLITTING dataSources: readwrite_ds: staticStrategy: writeDataSourceName: ds_master readDataSourceNames: - ds_slave_0 loadBalancerName: round_robin loadBalancers: round_robin: type: ROUND_ROBIN ``` 在上述配置中: - `mode` 部分配置了 ShardingSphere 的运行模式为单机模式,数据存储在文件中。 - `dataSources` 部分定义了主数据源 `ds_master` 和从数据源 `ds_slave_0`,需要根据实际情况修改数据库的连接信息。 - `rules` 部分配置了读写分离规则,`readwrite_ds` 是读写分离数据源的名称,`staticStrategy` 指定了写操作使用主数据源,读操作使用从数据源,`loadBalancerName` 指定了负载均衡算法为轮询。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值