大数跨境
0
0

SpringBoot 与 Druid 整合,实现电商主从数据库同步系统

SpringBoot 与 Druid 整合,实现电商主从数据库同步系统 Amanda跨境运营
2025-09-23
16

1

需求背景
随着电商行业的规模化发展,某头部电商平台日均订单量突破100 万单,峰值时段(如双十一)每秒产生 3000 + 订单,用户日均商品查询量超 5000 万次。核心业务面临三大数据存储挑战:一是读多写少矛盾突出:商品详情、订单历史等读操作占比超 90%,单库架构下读请求拥堵,导致用户查询响应延迟超 2 秒;二是主从同步延迟风险:传统 MySQL 主从同步依赖 binlog 复制,峰值时段延迟常超 1 秒,出现 “用户下单后查不到订单”“商品库存已扣但详情页仍显示库存充足” 的数据不一致问题;三是连接管理混乱:高并发场景下,传统 C3P0 连接池易出现 “连接耗尽”“空闲连接超时”,导致订单创建失败率超 5%;四是缺乏可视化监控:无法实时感知主从同步状态、SQL 执行效率,出现问题后排查耗时超 30 分钟。
传统电商数据存储方案的局限性显著:
  • MySQL 单库每秒仅能支撑 2000 + 查询,峰值时段读请求排队,用户体验差;
  • 需在代码中硬编码 “读从库、写主库” 逻辑,侵入业务代码,且无法应对从库延迟、故障等异常场景;
  • C3P0 空闲连接检测效率低,高并发下连接复用率不足 60%,服务器资源浪费严重;
  • 缺乏主从同步延迟监控、慢 SQL 统计,故障定位依赖日志检索,耗时且易遗漏关键信息。
为解决上述问题,平台选择SpringBoot + Druid + MySQL 主从方案:Druid 作为高性能数据库连接池,不仅能实现自动化主从路由与读写分离,还提供实时监控、连接池优化、延迟降级等能力,完美适配电商高并发、高可用的数据存储需求。

2

为什么选择 SpringBoot + Druid?
2.1 Druid:突破电商主从同步的核心痛点
2.1.1 自动化主从路由与读写分离
  • Druid 通过 SQL 解析器自动判断操作类型(INSERT/UPDATE/DELETE 为写操作,SELECT 为读操作),写操作路由至主库,读操作路由至从库,无需业务代码侵入;
  • 支持配置多个从库,读请求按轮询 / 权重策略分发,峰值时段可将读压力分散至 5 + 从库,单从库查询压力降低 80%;
  • 内置主从延迟检测,当从库延迟超预设阈值(如 500ms)时,自动将读请求切换至主库,避免数据不一致,降级策略可动态配置。
2.1.2 高性能连接池优化
  • 通过 “空闲连接池 + 活跃连接池” 双池设计,连接复用率达 95% 以上,比 C3P0 提升 30%;
  • 支持动态调整初始连接数(默认 10)、最大连接数(默认 100)、空闲连接超时时间(默认 60 秒),避免 “连接耗尽” 或 “资源浪费”;
  • 内置 SQL 防火墙,拦截恶意注入语句;自动记录执行耗时超 500ms 的慢 SQL,便于优化(如电商商品列表查询慢 SQL 优化后响应从 1.5 秒降至 200ms)。
2.1.3 全维度监控能力
  • 支持配置 “主从延迟超 1 秒”“连接池使用率超 80%”“慢 SQL 次数超 10 次 / 分钟” 等告警规则,通过邮件 / 钉钉实时推送,故障响应时间从 30 分钟缩短至 5 分钟;
  • 可导出 SQL 执行日志、连接池使用趋势,便于离线分析性能瓶颈。
2.2 SpringBoot:简化整合与快速开发
  • Spring 声明式事务下,Druid 自动保证 “事务内所有操作路由至主库”,避免跨库事务导致的数据不一致(如订单创建 + 库存扣减需在同一数据源执行);
  • 支持通过 Spring BeanPostProcessor 扩展 Druid 功能(如自定义主从路由规则、扩展监控指标),适配电商复杂业务场景。
2.3 两者结合的核心优势
  • 读写分离后,主库写压力降低 70%,读请求响应延迟从 2 秒降至 200ms 内;
  • 从库故障时自动切换至其他从库,主从延迟超阈值时降级至主库,订单创建成功率提升至 99.99%;
  • 可视化监控减少 80% 的问题排查时间,连接池自动管理减少 60% 的运维操作;
  • Druid 通过 AOP 实现主从路由,不侵入业务代码,后续升级替换数据源更灵活。

3

系统设计
3.1 整体架构
系统采用四层架构,实现 “业务请求 - 路由分发 - 数据存储 - 监控告警” 的全流程闭环,兼顾高并发与高可用:
  • 电商核心业务模块(订单服务、商品服务、用户服务),提供订单创建、商品查询等功能;
  • SpringBoot + Druid,负责解析请求类型、实现主从路由、管理数据库连接、监控主从状态;
  • MySQL 主从集群(1 主 4 从),主库存储写操作(订单创建、库存扣减),从库存储读操作(商品查询、订单历史);
  • Druid 内置监控面板 + 自定义告警模块,实时监控主从同步延迟、连接池状态、SQL 执行效率,异常时触发告警。
3.2 数据流程
以 “用户下单” 和 “商品查询” 两个核心场景为例,数据流程如下:
3.2.1 订单创建(写操作)
Druid 自动路由至主库,获取数据库连接(从连接池复用空闲连接);
执行 SQL 并提交事务,释放连接回连接池;
MySQL 主库通过 binlog 将数据同步至 4 个从库。
3.2.2 商品查询(读操作)
选择延迟≤500ms 的从库(若所有从库延迟超阈值,自动降级至主库);
从连接池获取对应从库的空闲连接,执行查询并返回结果,释放连接。
3.3 数据模型设计
3.3.1 核心数据库表结构
电商主从库采用完全一致的表结构,核心表如下:
  • 订单表(t_order):存储订单基本信息,主键order_id(雪花算法生成),关键字段包括user_id(用户 ID)、total_amount(订单金额)、order_status(订单状态)、create_time(创建时间);
  • 商品表(t_product):存储商品信息,主键product_id,关键字段包括product_name(商品名称)、price(单价)、stock(库存)、detail(商品详情);
  • 库存表(t_product_stock):独立存储库存,避免商品表读请求影响库存更新,主键stock_id,关键字段包括product_id(商品 ID)、available_stock(可用库存)、lock_stock(锁定库存)。
3.3.2 Druid 数据源模型
Druid 管理两类数据源,通过路由规则动态切换:
  • 对应 MySQL 主库,仅处理写操作,配置最大连接数 100(应对峰值写压力);
  • 对应 4 个 MySQL 从库,仅处理读操作,每个从库配置最大连接数 200(分担读压力);
所有数据源共享连接池配置:初始连接数 10、空闲连接超时 60 秒、连接检测间隔 30 秒、慢 SQL 阈值 500ms。

4

代码实操
4.1 环境准备
4.2 MySQL 主从环境搭建
4.2.1 主库配置(master)
  1. 重启 MySQL 并创建同步账号:
# 重启主库systemctl restart mysqld# 登录MySQLmysql -uroot -proot123# 创建同步账号(允许从库访问)CREATE USER 'slave'@'%' IDENTIFIED BY 'slave123';GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%';FLUSH PRIVILEGES;# 查看主库binlog信息(记录File和Position,从库配置需用)SHOW MASTER STATUS;
4.2.2 从库配置(slave1~slave4)
  1. 重启 MySQL 并配置主从同步:
# 重启从库systemctl restart mysqld# 登录MySQLmysql -uroot -proot123# 配置主库信息(File和Position为主库SHOW MASTER STATUS结果)CHANGE MASTER TO  MASTER_HOST='192.168.1.100',  # 主库IP  MASTER_USER='slave',  MASTER_PASSWORD='slave123',  MASTER_LOG_FILE='mysql-bin.000001',  MASTER_LOG_POS=154;# 启动从库同步START SLAVE;# 验证同步状态(Slave_IO_Running和Slave_SQL_Running需为Yes)SHOW SLAVE STATUS\G;
4.3 项目依赖配置(pom.xml)
<dependencies>    <!-- SpringBoot核心 -->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-jdbc</artifactId>    </dependency>    <!-- Druid依赖 -->    <dependency>        <groupId>com.alibaba</groupId>        <artifactId>druid-spring-boot-starter</artifactId>        <version>1.2.20</version>    </dependency>    <!-- MySQL驱动 -->    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <version>5.1.49</version>        <scope>runtime</scope>    </dependency>    <!-- MyBatis-Plus(简化ORM操作) -->    <dependency>        <groupId>com.baomidou</groupId>        <artifactId>mybatis-plus-boot-starter</artifactId>        <version>3.5.3.1</version>    </dependency>    <!-- Lombok -->    <dependency>        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>        <optional>true</optional>    </dependency>    <!-- 测试 -->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>        <scope>test</scope>    </dependency></dependencies>
4.4 配置文件(application.yml)
spring:  application:    name: ecommerce-db-sync  # Druid主从数据源配置  datasource:    type: com.alibaba.druid.pool.DruidDataSource    druid:      # 主数据源(master)      master:        url: jdbc:mysql://192.168.1.100:3306/ecommerce?useSSL=false&serverTimezone=UTC        username: root        password: root123        driver-class-name: com.mysql.jdbc.Driver      # 从数据源(slave1~slave4)      slaves:        - url: jdbc:mysql://192.168.1.101:3306/ecommerce?useSSL=false&serverTimezone=UTC          username: root          password: root123          driver-class-name: com.mysql.jdbc.Driver        - url: jdbc:mysql://192.168.1.102:3306/ecommerce?useSSL=false&serverTimezone=UTC          username: root          password: root123          driver-class-name: com.mysql.jdbc.Driver        - url: jdbc:mysql://192.168.1.103:3306/ecommerce?useSSL=false&serverTimezone=UTC          username: root          password: root123          driver-class-name: com.mysql.jdbc.Driver        - url: jdbc:mysql://192.168.1.104:3306/ecommerce?useSSL=false&serverTimezone=UTC          username: root          password: root123          driver-class-name: com.mysql.jdbc.Driver      # 连接池配置(全局生效)      initial-size: 10          # 初始连接数      max-active: 200           # 最大连接数(主库100,从库200,通过代码区分)      min-idle: 5               # 最小空闲连接数      max-wait: 60000           # 获取连接超时时间(毫秒)      time-between-eviction-runs-millis: 3000  # 连接检测间隔(毫秒)      min-evictable-idle-time-millis: 60000    # 空闲连接超时时间(毫秒)      validation-query: SELECT 1 FROM DUAL     # 连接有效性检测SQL      test-while-idle: true                     # 空闲时检测连接有效性      test-on-borrow: false                     # 借连接时不检测(提升性能)      test-on-return: false                     # 还连接时不检测(提升性能)      pool-prepared-statements: true            # 开启预编译语句池      max-pool-prepared-statement-per-connection-size: 20  # 预编译语句池大小      # 主从路由配置      slave-delay-threshold: 500  # 从库延迟阈值(毫秒),超阈值则降级至主库      # 监控配置      stat-view-servlet:        enabled: true            # 开启监控面板        url-pattern: /druid/*    # 监控面板访问路径        login-username: druid    # 监控面板用户名        login-password: druid123 # 监控面板密码        reset-enable: false      # 禁用重置按钮      web-stat-filter:        enabled: true            # 开启Web请求统计        url-pattern: /*          # 统计所有请求        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"  # 排除静态资源# MyBatis-Plus配置mybatis-plus:  mapper-locations: classpath:mapper/**/*.xml  type-aliases-package: com.example.ecommerce.entity  configuration:    map-underscore-to-camel-case: true  # 下划线转驼峰    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印SQL日志(开发环境)# 日志配置logging:  level:    com.example.ecommerce.mapper: DEBUG  # Mapper接口日志级别    com.alibaba.druid: INFO              # Druid日志级别
4.5 核心代码实现
4.5.1 数据源上下文工具类(DataSourceContextHolder.java)
package com.example.ecommerce.config.druid;import lombok.AccessLevel;import lombok.NoArgsConstructor;/** * 数据源上下文工具类:管理当前线程的数据源标识 */@NoArgsConstructor(access = AccessLevel.PRIVATE)public class DataSourceContextHolder {    // 线程本地变量:存储数据源标识(master/slave)    private static final ThreadLocal<StringCONTEXT_HOLDER = new ThreadLocal<>();    // 数据源标识常量    public static final String MASTER = "master";    public static final String SLAVE = "slave";    /**     * 设置数据源标识     */    public static void setDataSource(String dataSource) {        CONTEXT_HOLDER.set(dataSource);    }    /**     * 获取数据源标识     */    public static String getDataSource() {        return CONTEXT_HOLDER.get() == null ? MASTER : CONTEXT_HOLDER.get();    }    /**     * 清除数据源标识(避免线程复用导致的数据源错乱)     */    public static void clearDataSource() {        CONTEXT_HOLDER.remove();    }}
4.5.2 动态数据源路由类(DynamicDataSource.java)
package com.example.ecommerce.config.druid;import com.alibaba.druid.pool.DruidDataSource;import com.example.ecommerce.service.MysqlSlaveStatusService;import lombok.RequiredArgsConstructor;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import org.springframework.stereotype.Component;import javax.sql.DataSource;import java.util.Map;import java.util.Random;/** * 动态数据源路由:根据上下文标识路由至主库或从库 */@Component@RequiredArgsConstructorpublic class DynamicDataSource extends AbstractRoutingDataSource {    // 从库状态服务:检测从库延迟与可用性(后续实现)    private final MysqlSlaveStatusService slaveStatusService;    // 随机数生成器:从库负载均衡(轮询)    private final Random random = new Random();    /**     * 核心方法:确定当前数据源标识     */    @Override    protected Object determineCurrentLookupKey() {        String dataSource = DataSourceContextHolder.getDataSource();        // 若标识为slave,需检测从库状态(延迟、可用性)        if (DataSourceContextHolder.SLAVE.equals(dataSource)) {            // 1. 获取所有可用且延迟≤阈值的从库            Map<StringDruidDataSource> availableSlaves = slaveStatusService.getAvailableSlaves();            if (availableSlaves.isEmpty()) {                // 无可用从库,降级至主库                return DataSourceContextHolder.MASTER;            }            // 2. 从库负载均衡(轮询选择一个从库)            String[] slaveKeys = availableSlaves.keySet().toArray(new String[0]);            return slaveKeys[random.nextInt(slaveKeys.length)];        }        // 标识为master,直接路由至主库        return DataSourceContextHolder.MASTER;    }}
4.5.3 Druid 数据源配置类(DruidConfig.java)
package com.example.ecommerce.config.druid;import com.alibaba.druid.pool.DruidDataSource;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import javax.sql.DataSource;import java.util.HashMap;import java.util.List;import java.util.Map;/** * Druid数据源配置:初始化主从数据源,配置动态路由 */@Configuration@ConfigurationProperties(prefix = "spring.datasource.druid")@Datapublic class DruidConfig {    // 主数据源配置    private DruidDataSourceProperties master;    // 从数据源配置列表    private List<DruidDataSourceProperties> slaves;    // 从库延迟阈值(毫秒)    private long slaveDelayThreshold;    /**     * 初始化主数据源     */    @Bean    public DataSource masterDataSource() {        DruidDataSource dataSource = new DruidDataSource();        dataSource.setUrl(master.getUrl());        dataSource.setUsername(master.getUsername());        dataSource.setPassword(master.getPassword());        dataSource.setDriverClassName(master.getDriverClassName());        // 主库连接池配置(最大连接数100)        dataSource.setMaxActive(100);        return dataSource;    }    /**     * 初始化从数据源(多个)     */    @Bean    public Map<StringDataSourceslaveDataSources() {        Map<StringDataSource> slaveMap = new HashMap<>();        for (int i = 0; i < slaves.size(); i++) {            DruidDataSourceProperties slaveProp = slaves.get(i);            DruidDataSource dataSource = new DruidDataSource();            dataSource.setUrl(slaveProp.getUrl());            dataSource.setUsername(slaveProp.getUsername());            dataSource.setPassword(slaveProp.getPassword());            dataSource.setDriverClassName(slaveProp.getDriverClassName());            // 从库连接池配置(最大连接数200)            dataSource.setMaxActive(200);            // 从库标识:slave1~slave4            String slaveKey = "slave" + (i + 1);            slaveMap.put(slaveKey, dataSource);        }        return slaveMap;    }    /**     * 配置动态数据源(主从路由),设置为默认数据源     */    @Bean    @Primary    public DataSource dynamicDataSource(DataSource masterDataSource, Map<String, DataSource> slaveDataSources) {        DynamicDataSource dynamicDataSource = new DynamicDataSource(slaveStatusService());        // 配置默认数据源(主库)        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);        // 配置所有数据源(主库+从库)        Map<ObjectObject> targetDataSources = new HashMap<>();        targetDataSources.put(DataSourceContextHolder.MASTER, masterDataSource);        targetDataSources.putAll(slaveDataSources);        dynamicDataSource.setTargetDataSources(targetDataSources);        return dynamicDataSource;    }    /**     * 从库状态服务:检测从库延迟与可用性     */    @Bean    public MysqlSlaveStatusService slaveStatusService() {        return new MysqlSlaveStatusService(slaves, slaveDelayThreshold);    }    /**     * Druid数据源属性类(映射配置文件中的主从数据源信息)     */    @Data    public static class DruidDataSourceProperties {        private String url;        private String username;        private String password;        private String driverClassName;    }}
4.5.4 从库状态检测服务(MysqlSlaveStatusService.java)
定期检测从库同步延迟与可用性,提供 “可用从库列表”:
package com.example.ecommerce.config.druid;import com.alibaba.druid.pool.DruidDataSource;import com.example.ecommerce.config.druid.DruidConfig.DruidDataSourceProperties;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;import java.sql.Connection;import java.sql.ResultSet;import java.sql.Statement;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/** * 从库状态服务:检测从库同步延迟与可用性,缓存可用从库 */@Service@Slf4j@RequiredArgsConstructorpublic class MysqlSlaveStatusService {    // 从库配置列表    private final List<DruidDataSourceProperties> slaveProps;    // 从库延迟阈值(毫秒)    private final long slaveDelayThreshold;    // 可用从库缓存(key:slave1~slave4,value:Druid数据源)    private final Map<String, DruidDataSource> availableSlaves = new ConcurrentHashMap<>();    /**     * 初始化:创建从库Druid数据源     */    @PostConstruct    public void initSlaveDataSources() {        for (int i = 0; i < slaveProps.size(); i++) {            DruidDataSourceProperties prop = slaveProps.get(i);            DruidDataSource dataSource = new DruidDataSource();            dataSource.setUrl(prop.getUrl());            dataSource.setUsername(prop.getUsername());            dataSource.setPassword(prop.getPassword());            dataSource.setDriverClassName(prop.getDriverClassName());            dataSource.setMaxActive(200);            String slaveKey = "slave" + (i + 1);            availableSlaves.put(slaveKey, dataSource);            log.info("初始化从库数据源:{},URL:{}", slaveKey, prop.getUrl());        }        // 初始化时检测一次从库状态        checkSlaveStatus();    }    /**     * 定时检测从库状态(每10秒执行一次)     */    @Scheduled(fixedRate = 10000)    public void checkSlaveStatus() {        Map<String, DruidDataSource> tempSlaves = new HashMap<>(availableSlaves);        tempSlaves.forEach((slaveKey, dataSource) -> {            try (Connection conn = dataSource.getConnection();                 Statement stmt = conn.createStatement();                 ResultSet rs = stmt.executeQuery("SHOW SLAVE STATUS")) {                if (rs.next()) {                    // 1. 检测从库同步是否正常(Slave_IO_Running和Slave_SQL_Running需为Yes)                    String ioRunning = rs.getString("Slave_IO_Running");                    String sqlRunning = rs.getString("Slave_SQL_Running");                    if (!"Yes".equals(ioRunning) || !"Yes".equals(sqlRunning)) {                        availableSlaves.remove(slaveKey);                        log.warn("从库{}同步异常:Slave_IO_Running={}, Slave_SQL_Running={}",                                slaveKey, ioRunning, sqlRunning);                        return;                    }                    // 2. 检测从库延迟(Seconds_Behind_Master)                    long delay = rs.getLong("Seconds_Behind_Master");                    if (delay * 1000 > slaveDelayThreshold) {                        availableSlaves.remove(slaveKey);                        log.warn("从库{}延迟超阈值:{}ms(阈值:{}ms),暂时移除",                                slaveKey, delay * 1000, slaveDelayThreshold);                        return;                    }                    // 3. 延迟正常,若之前被移除则重新加入                    if (!availableSlaves.containsKey(slaveKey)) {                        availableSlaves.put(slaveKey, dataSource);                        log.info("从库{}恢复可用:延迟{}ms", slaveKey, delay * 1000);                    }                }            } catch (Exception e) {                // 连接异常,移除从库                availableSlaves.remove(slaveKey);                log.error("从库{}连接异常,暂时移除", slaveKey, e);            }        });    }    /**     * 获取可用从库列表     */    public Map<String, DruidDataSource> getAvailableSlaves() {        return new HashMap<>(availableSlaves);    }}
4.5.5 读写分离 AOP 切面(DataSourceAop.java)
package com.example.ecommerce.config.druid;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;/** * 读写分离AOP切面:拦截Service方法,自动设置数据源标识 */@Aspect@Component@Slf4jpublic class DataSourceAop {    // 切入点:拦截所有Service方法    @Pointcut("execution(* com.example.ecommerce.service.*.*(..))")    public void servicePointcut() {}    /**     * 环绕通知:根据方法名判断读写操作,设置数据源标识     */    @Around("servicePointcut()")    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {        String methodName = joinPoint.getSignature().getName();        try {            // 1. 根据方法名判断操作类型(写操作:add/create/update/delete/remove;读操作:get/query/list/select)            if (methodName.matches("^add.*|^create.*|^update.*|^delete.*|^remove.*")) {                // 写操作:设置数据源为master                DataSourceContextHolder.setDataSource(DataSourceContextHolder.MASTER);                log.debug("方法{}为写操作,路由至主库", methodName);            } else {                // 读操作:设置数据源为slave                DataSourceContextHolder.setDataSource(DataSourceContextHolder.SLAVE);                log.debug("方法{}为读操作,路由至从库", methodName);            }            // 2. 执行目标方法            return joinPoint.proceed();        } finally {            // 3. 清除数据源标识(避免线程复用导致的错乱)            DataSourceContextHolder.clearDataSource();        }    }}
4.5.6 业务服务实现(订单服务为例)
package com.example.ecommerce.service;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.example.ecommerce.entity.Order;import com.example.ecommerce.mapper.OrderMapper;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import java.util.List;/** * 订单服务:写操作(创建订单)路由至主库,读操作(查询订单)路由至从库 */@Service@Slf4j@RequiredArgsConstructorpublic class OrderService extends ServiceImpl<OrderMapper, Order> {    private final OrderMapper orderMapper;    private final ProductStockService stockService;    /**     * 创建订单(写操作:路由至主库)     */    @Transactional(rollbackFor = Exception.class)    public boolean createOrder(Order order) {        // 1. 扣减商品库存(写操作,路由至主库)        boolean stockDeduct = stockService.deductStock(order.getProductId(), order.getQuantity());        if (!stockDeduct) {            log.error("订单{}创建失败:商品库存不足", order.getOrderId());            return false;        }        // 2. 插入订单记录(写操作,路由至主库)        int count = orderMapper.insert(order);        log.info("订单{}创建成功,已路由至主库", order.getOrderId());        return count > 0;    }    /**     * 查询用户订单列表(读操作:路由至从库)     */    public List<Order> getUserOrders(Long userId) {        List<Order> orders = orderMapper.selectByUserId(userId);        log.info("查询用户{}订单列表,已路由至从库,共{}条记录", userId, orders.size());        return orders;    }}
4.5.7 控制器实现(订单控制器为例)
package com.example.ecommerce.controller;import com.example.ecommerce.entity.Order;import com.example.ecommerce.service.OrderService;import lombok.RequiredArgsConstructor;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import java.util.List;/** * 订单控制器:提供订单创建与查询接口 */@RestController@RequestMapping("/api/order")@RequiredArgsConstructorpublic class OrderController {    private final OrderService orderService;    /**     * 创建订单(POST请求:写操作)     */    @PostMapping("/create")    public ResponseEntity<String> createOrder(@RequestBody Order order) {        boolean success = orderService.createOrder(order);        if (success) {            return new ResponseEntity<>("订单创建成功,订单ID:" + order.getOrderId(), HttpStatus.OK);        }        return new ResponseEntity<>("订单创建失败:库存不足"HttpStatus.BAD_REQUEST);    }    /**     * 查询用户订单列表(GET请求:读操作)     */    @GetMapping("/user/{userId}")    public ResponseEntity<List<Order>> getUserOrders(@PathVariable Long userId) {        List<Order> orders = orderService.getUserOrders(userId);        return new ResponseEntity<>(orders, HttpStatus.OK);    }}
4.5.8 启动类
package com.example.ecommerce;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableScheduling;/** * 电商主从数据库同步系统启动类 */@SpringBootApplication@MapperScan("com.example.ecommerce.mapper")  // 扫描Mapper接口@EnableScheduling  // 启用定时任务(从库状态检测)public class EcommerceDbSyncApplication {    public static void main(String[] args) {        SpringApplication.run(EcommerceDbSyncApplication.class, args);        System.out.println("SpringBoot与Druid电商主从同步系统启动成功!");    }}

5

测试验证
5.1 主从路由功能测试
5.1.1 写操作路由验证
测试工具:Postman
POST http://localhost:8080/api/order/create
GET http://localhost:8080/api/order/user/10001
请求体:
{  "orderId": "1695567890123",  "userId": 10001,  "productId": 20001,  "quantity": 2,  "totalAmount": 199.8,  "orderStatus": 0,  "createTime": "2025-09-24T14:30:00"}
验证步骤:
查看应用日志,显示 “方法 createOrder 为写操作,路由至主库”;
SELECT * FROM t_order WHERE order_id = '1695567890123'
等待 5 秒(主从同步时间),登录从库执行相同 SQL,确认数据已同步。
5.1.2 读操作路由验证
查看应用日志,显示 “方法 getUserOrders 为读操作,路由至从库”;
http://localhost:8080/druid/datasource.html
systemctl stop mysqld
5.2 主从延迟降级测试
  • 测试步骤:
    1. 在主库执行INSERT INTO t_product (product_id, product_name, price, stock) VALUES (20002, '测试商品', 99.9, 100)
    2. 手动延迟从库同步(在从库执行STOP SLAVE;,等待 10 秒后执行START SLAVE;);
    3. 立即调用商品查询接口(GET http://localhost:8080/api/product/20002);
  • 预期结果:
    1. 应用日志显示 “从库 slave1 延迟超阈值:10000ms(阈值:500ms),暂时移除”;
    2. 读请求自动降级至主库,查询结果包含新插入的 “测试商品”;
    3. 从库同步恢复后(延迟≤500ms),后续读请求自动切回从库。
5.3 性能测试
5.3.1 测试工具:JMeter
5.3.2 测试场景:
  • 1000 并发用户查询商品详情(读操作),持续 5 分钟;
  • 200 并发用户创建订单(写操作),持续 5 分钟。
5.3.3 核心指标验证:
结论:读写分离后,读请求响应时间从 2 秒降至 180ms,订单创建成功率从 95% 提升至 99.99%,满足电商高并发需求。
5.4 监控功能测试
  • 查看主从数据源连接数、空闲连接数、活跃连接数,确认无 “连接耗尽”;
  • 查看慢 SQL 列表,确认无执行耗时超 500ms 的 SQL;
  • 查看 “从库延迟” 图表,确认延迟超阈值时触发告警(如钉钉消息)。

6

总结与扩展
6.1 核心价值总结
本文通过SpringBoot 与 Druid 的深度整合,构建了电商主从数据库同步系统,解决了传统方案的四大核心痛点:
  • 读请求分散至 4 个从库,主库写压力降低 70%,读响应延迟从 2 秒降至 180ms;
  • 从库延迟超阈值时自动切主库,避免 “下单后查不到订单” 的数据不一致问题;
  • Druid 连接复用率达 95%,比 C3P0 节省 30% 的服务器资源;
  • 可视化监控 + 实时告警,故障排查时间从 30 分钟缩短至 5 分钟。
6.2 扩展方向
  • 结合 ShardingSphere 与 Druid,实现 “分库分表 + 主从同步”,应对电商订单量超 10 亿的场景;
  • 引入 MGR(MySQL Group Replication)实现多主架构,Druid 支持多主路由,进一步提升写性能;
  • 将 Druid 监控数据接入 Prometheus+Grafana,实现监控数据长期存储与可视化大屏展示;
  • 基于用户地域(如华东用户路由至华东从库)、SQL 复杂度(复杂统计查询路由至专用从库)优化路由策略;
  • 集成 Keepalived,主库故障时自动切换至备用主库,Druid 实时感知主库变化,避免写操作中断。
通过这套系统,电商平台可实现数据存储的 “高性能、高可用、可监控”,为双十一、618 等大促活动提供稳定的数据支撑,同时降低运维成本,提升用户体验。

后端专属技术群
我们致力于创建一个高质量的技术交流社区,欢迎编程开发者和技术招聘HR专业人士加入。同时,我们也鼓励大家分享自己公司的内部推荐机会,互相协作,共同提升!

文明发言,以交流技术职位内推行业探讨为主,添加备注888

广告人士勿入,切勿轻信私聊,防止被骗

商务合作/软件著作权申请/项目推广/项目开发
联系微信:LONGJIANG_116


往期链接
多门店会员系统来袭!基于SpringBoot的扫码入会+积分裂变+卡券核销,小程序+收银台打通线上线下,私域流量运营+精准营销
一套系统,完美支持共享自助棋牌室、共享自助台球室、共享自助KTV等多种运营场景
智慧社区系统重磅来袭!基于SpringBoot超强功能直接拉满,管理效率翻倍,乡村振兴黑科技,一站式解决难题
100%完整源码!5000+实战验证的SpringBoot停车充电平台,适配国内99%停车场需求
从用例设计到报告分析!推荐一款SpringBoot分布式服务、功能测试和性能测试一体的自动化测试平台
推荐一款基于SpringBoot的大型在线票务系统,真实还原百万级并发、高吞吐真实业务场景
推荐一款开源的SpringBoot适配民营及公立一二级医院His系统,支持单体医院、集团化运营及区域医疗协同
推荐一款广泛适用于小学、初中、高中、中职和教培机构的SpringBoot智能排课系统,专为教学科研场景打造
高颜值! 推荐一款Jdk17 + SpringBoot3 + SpringCloud 微服务架构的运动场馆预约小程序
推荐一款开源、功能强大的自习室预约平台,带手机端

【声明】内容源于网络
0
0
Amanda跨境运营
跨境分享集 | 每天一点跨境见解
内容 42460
粉丝 3
Amanda跨境运营 跨境分享集 | 每天一点跨境见解
总阅读238.0k
粉丝3
内容42.5k