Spring Boot + MySQL读写分离实现方案

Spring Boot + MySQL读写分离实现方案

  • 一、MySQL主从复制搭建(一主一从)
    • [1. 主库配置](#1. 主库配置)
    • [2. 从库配置](#2. 从库配置)
  • [二、Spring Boot读写分离实现(推荐Dynamic-Datasource)](#二、Spring Boot读写分离实现(推荐Dynamic-Datasource))
    • [1. 添加Maven依赖(pom.xml)](#1. 添加Maven依赖(pom.xml))
    • [2. 配置文件(application.yml)](#2. 配置文件(application.yml))
    • [3. 代码实现](#3. 代码实现)
      • [a. 定义数据源枚举](#a. 定义数据源枚举)
      • [b. 数据源上下文管理器](#b. 数据源上下文管理器)
      • [c. 使用AOP标注读写操作](#c. 使用AOP标注读写操作)
      • [d. AOP切面实现](#d. AOP切面实现)
      • [e. 在Service层使用](#e. 在Service层使用)
  • 三、Redis和MQ整合方案分析
    • [✅ 优点:](#✅ 优点:)
    • [⚠️ 问题与建议:](#⚠️ 问题与建议:)
    • [🛠️ 优化:](#🛠️ 优化:)
  • 四、总结

一、MySQL主从复制搭建(一主一从)

1. 主库配置

bash 复制代码
# 1. 修改主库配置文件 /etc/my.cnf
[mysqld]
server-id=1 # 唯一ID
log-bin=mysql-bin # 启用二进制日志
binlog-format=mixed # 二进制日志格式

重启MySQL后执行:

sql 复制代码
# 2. 创建复制账号
CREATE USER 'repl'@'%' IDENTIFIED BY 'repl_password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

# 3. 查看二进制日志坐标
SHOW MASTER STATUS;
# 记录File和Position,例如:File 'mysql-bin.000001', Position 154

2. 从库配置

bash 复制代码
# 1. 修改从库配置文件 /etc/my.cnf
[mysqld]
server-id=2 # 必须与主库不同
relay-log=mysql-relay-bin # 中继日志
log-slave-updates=1 # 使从库也能作为其他从库的主库

重启MySQL后执行:

sql 复制代码
# 2. 配置主从连接
CHANGE MASTER TO 
MASTER_HOST='主库IP',
MASTER_USER='repl',
MASTER_PASSWORD='repl_password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;

# 3. 启动复制
START SLAVE;

# 4. 验证
SHOW SLAVE STATUS\G
# 确认Slave_IO_Running和Slave_SQL_Running都是Yes

💡 小贴士:主从复制需要确保网络通畅,主库防火墙开放3306端口,从库能访问主库的3306端口。


二、Spring Boot读写分离实现(推荐Dynamic-Datasource)


1. 添加Maven依赖(pom.xml)

xml 复制代码
<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- MyBatis-Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.2</version>
    </dependency>
    
    <!-- Dynamic-Datasource(核心) -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        <version>3.6.1</version>
    </dependency>
    
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
    
    <!-- 连接池(可选) -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.16</version>
    </dependency>
</dependencies>

2. 配置文件(application.yml)

yaml 复制代码
spring:
  datasource:
    dynamic:
      primary: master # 默认数据源
      datasource:
        master:
          url: jdbc:mysql://主库IP:3306/db_name?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
          username: root
          password: master_password
          driver-class-name: com.mysql.cj.jdbc.Driver
          hikari:
            maximum-pool-size: 10
            minimum-idle: 5
        slave:
          url: jdbc:mysql://从库IP:3306/db_name?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
          username: root
          password: slave_password
          driver-class-name: com.mysql.cj.jdbc.Driver
          hikari:
            maximum-pool-size: 15
            minimum-idle: 8
      strategy:
        # 读写分离策略:读操作走从库,写操作走主库
        # 可选:round_robin(轮询)、random(随机)等
        datasource:
          master: slave

3. 代码实现

a. 定义数据源枚举

java 复制代码
public enum DataSourceType {
    MASTER,  // 主库:写操作
    SLAVE    // 从库:读操作
}

b. 数据源上下文管理器

java 复制代码
public class DataSourceContextHolder {
    private static final ThreadLocal<DataSourceType> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSourceType(DataSourceType dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);
    }

    public static DataSourceType getDataSourceType() {
        return CONTEXT_HOLDER.get() == null ? DataSourceType.MASTER : CONTEXT_HOLDER.get();
    }

    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}

c. 使用AOP标注读写操作

java 复制代码
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    DataSourceType value() default DataSourceType.MASTER;
}

d. AOP切面实现

java 复制代码
@Aspect
@Component
public class DataSourceAspect {
    @Before("@annotation(dataSource)")
    public void before(JoinPoint point, DataSource dataSource) {
        DataSourceType type = dataSource.value();
        DataSourceContextHolder.setDataSourceType(type);
    }

    @After("@annotation(dataSource)")
    public void after(JoinPoint point, DataSource dataSource) {
        DataSourceContextHolder.clearDataSourceType();
    }
}

e. 在Service层使用

java 复制代码
@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    
    // 写操作:默认走主库(也可以显式指定)
    @DataSource(DataSourceType.MASTER)
    public void createOrder(Order order) {
        orderMapper.insert(order);
    }
    
    // 读操作:默认走从库
    @DataSource(DataSourceType.SLAVE)
    public Order getOrderById(Long id) {
        return orderMapper.selectById(id);
    }
}

三、Redis和MQ整合方案分析

项目引入 redis和mq ,针对【热门数据 】的查询可以从redis查询,代码层面往MySQL主库执行增删改操作后往mq发送一条消息,然后由消费者将增删改的数据同步到redis中,当然这样的方案只适合少量的增删改操作,不适合大批量的增删改

✅ 优点:

  • 保证了数据一致性(先写库,再更新缓存)
  • 适合热点数据缓存
  • 降低数据库压力

⚠️ 问题与建议:

  1. 不适合大批量操作:大批量增删改会导致MQ消息量过大,处理延迟高。建议:

    • 对于大批量操作,使用批量同步(如Redis的pipeline)而不是MQ。
    • 或者使用定时任务定期同步热点数据。
  2. 消息可靠性:需要考虑MQ消息丢失、重复消费问题:

    • 使用RocketMQ/RabbitMQ的事务消息。
    • 添加消息重试机制。
    • 消费者 添加 幂等性 处理。
  3. 更适合的场景

    • 用户信息、商品详情等高频查询数据。
    • 不适用于 订单状态、交易流水等 高一致性 要求的数据。

🛠️ 优化:

java 复制代码
@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @DataSource(DataSourceType.MASTER)
    @Transactional
    public Order createOrder(Order order) {
        // 1. 写数据库
        Order savedOrder = orderMapper.insert(order);
        
        // 2. 发送MQ消息(只针对需要缓存的数据)
        if (isCacheableData(order)) {
            rabbitTemplate.convertAndSend("order-cache-topic", 
                new OrderCacheMessage(savedOrder.getId(), "CREATE"));
        }
        
        return savedOrder;
    }
}

// 消费者处理
@Component
public class OrderCacheConsumer {
    @RabbitListener(queues = "order-cache-queue")
    public void process(OrderCacheMessage message) {
        if ("CREATE".equals(message.getAction())) {
            Order order = orderMapper.selectById(message.getOrderId());
            redisTemplate.opsForValue().set("order:" + message.getOrderId(), order);
        }
        // 处理其他操作...
    }
}

四、总结

  1. 读写分离:使用Dynamic-Datasource,配置简单,社区支持好,比Sharding-JDBC更轻量

  2. Redis+MQ方案

    • 适合 热点数据 的缓存更新。
    • 不适用于 大批量操作。
    • 建议对需要缓存的数据做分类,只对高频查询的数据使用MQ同步。
  3. 建议

    • 在查询方法上使用@DataSource(DataSourceType.SLAVE)显式指定,避免默认路由错误。
    • 对于复杂查询,可以考虑在MyBatis中使用@Select指定数据源。
    • 监控主从延迟,避免从库数据不一致。

🌟 小技巧 :在开发阶段,可以在配置中添加dynamic.datasource.log=true,这样能打印出实际使用的数据源,方便排查问题。

相关推荐
fen_fen2 小时前
用户信息表建表及批量插入 100 条数据(MySQL/Oracle)
数据库·mysql·oracle
JH30739 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
qq_124987075312 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_12 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
2301_8187320612 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
汤姆yu16 小时前
基于springboot的尿毒症健康管理系统
java·spring boot·后端
暮色妖娆丶16 小时前
Spring 源码分析 单例 Bean 的创建过程
spring boot·后端·spring
·云扬·17 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
biyezuopinvip17 小时前
基于Spring Boot的企业网盘的设计与实现(任务书)
java·spring boot·后端·vue·ssm·任务书·企业网盘的设计与实现
JavaGuide17 小时前
一款悄然崛起的国产规则引擎,让业务编排效率提升 10 倍!
java·spring boot