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,这样能打印出实际使用的数据源,方便排查问题。

相关推荐
ApacheSeaTunnel27 分钟前
实战演示 | 基于 Apache SeaTunnel 与 Apache DolphinScheduler 实现 MySQL 到 Doris 离线定时增量同步
大数据·mysql·开源·doris·数据集成·seatunnel·数据同步
DARLING Zero two♡41 分钟前
【MySQL数据库】数据类型与表约束
数据库·mysql
活宝小娜2 小时前
mysql详细安装教程
数据库·mysql·adb
Database_Cool_2 小时前
什么是数据仓库物化视图?AnalyticDB MySQL 实时物化视图能力解析
人工智能·mysql·阿里云
星落zx2 小时前
Spring Boot 多模型集成:优雅调用全球主流大模型
人工智能·spring boot·chatgpt
Database_Cool_2 小时前
即席查询(Ad-Hoc)数据库选型:AnalyticDB MySQL 秒级 Ad-Hoc 分析方案
数据库·mysql
一杯奶茶¥3 小时前
水果销售网站 CRM客户信息管理系统 超市管理系 酒店管理系统 健身房管理系统 在线音乐网站 校园招聘系统
java·vue.js·spring boot·mysql·spring·java项目
用户800391387833 小时前
使用 Gemini 解决 MySQL 常见问题
mysql
zhangjin11203 小时前
adb install和 pm install 的区别是什么?
adb
进阶的小名4 小时前
Spring Boot SSE + Nginx 配置:解决 EventSource 不实时返回、连接超时、流式响应被缓冲问题
spring boot·后端·nginx