互联网大厂后端开发者必看!Spring Boot3 数据库读写分离轻松实现

互联网大厂后端开发者必看!Spring Boot3 数据库读写分离轻松实现

经验文章nimo972025-05-18 15:51:472A+A-

作为互联网大厂的后端开发者,在使用 Spring Boot3 构建高并发、高性能的应用系统时,你是否正面临这样的困境?随着业务规模的不断扩张,数据库的读写压力呈指数级增长,查询响应时间越来越长,系统吞吐量逐渐下降,即使投入大量精力进行代码优化,效果却依旧不尽如人意。其实,这很可能是因为你还没有充分利用数据库读写分离这一关键技术。今天,我们就深入探讨在 Spring Boot3 中如何高效实现数据库的读写分离,彻底攻克性能瓶颈!

数据库读写分离的重要性与应用场景

在互联网大厂的复杂业务场景下,数据的读写操作存在着显著的不均衡性。以电商平台为例,用户浏览商品详情、查看历史订单等读操作的频率,往往是下单、修改订单等写操作的数十倍甚至上百倍。若将所有的读写请求都集中在单一数据库上处理,就如同让一个人同时承担繁重的文件查阅与编辑任务,不仅效率低下,还极易引发性能瓶颈。

而且,单数据库架构存在较高的单点故障风险,一旦数据库出现宕机、磁盘损坏等问题,整个系统的读写服务都将陷入瘫痪,严重影响用户体验和业务正常运转。而通过 Spring Boot3 实现数据库读写分离,将读操作分发到从数据库,写操作交由主数据库处理,不仅能有效分散主数据库的负载压力,提升系统的并发处理能力,还能增强系统的可用性和稳定性,为业务的持续发展提供坚实保障。

Spring Boot3 实现数据库读写分离的详细步骤

主从数据库连接信息配置

在application.yml配置文件中,进行主从数据库连接信息的精细化配置是实现读写分离的基础。以常见的 MySQL 数据库结合 Hikari 连接池为例,具体配置如下:

spring:
  datasource:
    master:
      jdbc-url: jdbc:mysql://localhost:3306/master_db?useUnicode=true&characterEncoding=UTF - 8&serverTimezone=Asia/Shanghai&useSSL=false
      username: root
      password: 123456
      driver - class - name: com.mysql.cj.jdbc.Driver
      type: com.zaxxer.hikari.HikariDataSource
      hikari:
        minimum - idle: 5
        idle - timeout: 60000
        maximum - pool - size: 10
        auto - commit: true
        pool - name: MasterHikariCP
        max - lifetime: 1800000
        connection - timeout: 30000
        connection - test - query: SELECT 1
    slave:
      jdbc-url: jdbc:mysql://localhost:3306/slave_db?useUnicode=true&characterEncoding=UTF - 8&serverTimezone=Asia/Shanghai&useSSL=false
      username: root
      password: 123456
      driver - class - name: com.mysql.cj.jdbc.Driver
      type: com.zaxxer.hikari.HikariDataSource
      hikari:
        minimum - idle: 5
        idle - timeout: 60000
        maximum - pool - size: 10
        auto - commit: true
        pool - name: SlaveHikariCP
        max - lifetime: 1800000
        connection - timeout: 30000
        connection - test - query: SELECT 1

在上述配置中,master节点用于配置主数据库的连接参数,涵盖数据库 URL、登录用户名、密码、驱动类等关键信息,同时对 Hikari 连接池的最小空闲连接数(minimum - idle)、空闲连接超时时间(idle - timeout)、最大连接池大小(maximum - pool - size)等参数进行设置,以优化主数据库的连接性能和资源管理。slave节点则针对从数据库进行类似配置,确保从数据库能够稳定接收和处理读请求 。这些参数可根据实际业务流量、数据库服务器性能等因素进行针对性调整,以达到最佳的性能表现。

数据源配置类创建

为了将配置文件中的数据源信息转化为可被 Spring 容器管理的数据源对象,需要创建 Java 配置类。以
MasterDataSourceConfig.java和
SlaveDataSourceConfig.java为例:

import com.zaxxer.hikari.HikariDataSource;
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;

@Configuration
public class MasterDataSourceConfig {

    @Bean(name = "masterDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return new HikariDataSource();
    }
}
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class SlaveDataSourceConfig {

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return new HikariDataSource();
    }
}

在上述代码中,@Configuration注解表明这是一个配置类,@Bean注解用于将方法返回的数据源对象注册到 Spring 容器中。@ConfigurationProperties注解通过指定prefix属性,将application.yml中对应前缀的数据源配置属性自动绑定到数据源对象上,实现配置信息与数据源的无缝对接。其中,@Primary注解标记的masterDataSource方法返回的主数据源,在多个数据源存在的情况下,会被 Spring 优先选用,确保写操作能够正确路由到主数据库 。

动态路由数据源构建

动态路由数据源是实现数据库读写分离的核心组件,它能够根据业务需求动态切换数据源。创建DynamicDataSource类继承自AbstractRoutingDataSource:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.HashMap;
import java.util.Map;

public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final String MASTER = "master";
    private static final String SLAVE = "slave";

    public DynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(MASTER, masterDataSource);
        targetDataSources.put(SLAVE, slaveDataSource);

        setTargetDataSources(targetDataSources);
        setDefaultTargetDataSource(masterDataSource);
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

同时,创建DataSourceContextHolder类,利用ThreadLocal机制存储当前线程所需的数据源类型:

public class DataSourceContextHolder {

    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

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

    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }

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

在DynamicDataSource类中,构造函数接收主数据源和从数据源,并将它们存储在targetDataSources映射中。determineCurrentLookupKey方法通过调用
DataSourceContextHolder.getDataSourceType()获取当前线程对应的数据源类型标识,以此来决定实际使用的数据源。ThreadLocal的使用确保了每个线程都能独立维护自己的数据源类型,避免了多线程环境下数据源切换的冲突问题。

动态数据源注册

在 Spring 配置类中,将动态路由数据源注册到 Spring 容器,使其能够被应用程序调用:

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean
    public DynamicDataSource dynamicDataSource(
            @Qualifier("masterDataSource") DataSource masterDataSource,
            @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        return new DynamicDataSource(masterDataSource, slaveDataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}

上述代码中,dynamicDataSource方法通过注入主数据源和从数据源,创建并返回DynamicDataSource实例,完成动态数据源的注册。同时,transactionManager方法为动态数据源配置事务管理器,确保在进行数据库操作时,事务的原子性、一致性、隔离性和持久性(ACID 特性)能够得到保障,避免因数据读写操作导致的数据不一致问题。

数据库操作切面拦截

为了实现读写操作的自动路由,创建切面类
DataSourceAnnotationAspect,通过自定义注解来区分读写操作:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAnnotationAspect {

    @Around("@annotation(com.example.annotation.SlaveDataSource)")
    public Object read(ProceedingJoinPoint point) throws Throwable {
        try {
            DataSourceContextHolder.setDataSourceType("slave");
            return point.proceed();
        } finally {
            DataSourceContextHolder.clearDataSourceType();
        }
    }

    @Around("!@annotation(com.example.annotation.SlaveDataSource)")
    public Object write(ProceedingJoinPoint point) throws Throwable {
        try {
            DataSourceContextHolder.setDataSourceType("master");
            return point.proceed();
        } finally {
            DataSourceContextHolder.clearDataSourceType();
        }
    }
}

这里的自定义注解@SlaveDataSource用于标记读操作方法:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SlaveDataSource {
}


DataSourceAnnotationAspect切面类中,@Around注解定义的通知方法会在目标方法执行前后进行拦截。当检测到方法上带有@SlaveDataSource注解时,将当前线程的数据源类型设置为slave,使该方法的数据库操作路由到从数据库;反之,若方法上没有该注解,则将数据源类型设置为master,确保写操作能够正确发送到主数据库。在操作完成后,通过
DataSourceContextHolder.clearDataSourceType()方法清除当前线程的数据源类型设置,避免对后续操作产生干扰。

排除默认数据源自动配置

为了避免 Spring Boot 默认的数据源自动配置与我们自定义的数据源配置产生冲突,需要在 Spring Boot 应用主类上添加@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class})注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class YourApplication {
    public static void main(String[] args) {
        SpringApplication.run(YourApplication.class, args);
    }
}

通过排除默认的数据源自动配置,确保 Spring 容器按照我们自定义的配置方式创建和管理数据源,从而准确实现数据库的读写分离功能。

总结

通过以上详细的步骤,我们全面掌握了在 Spring Boot3 中实现数据库读写分离的技术要点。从主从数据库连接配置到动态数据源构建,再到读写操作的切面拦截,每一个环节都紧密相连,共同构建起高效的数据读写分离架构。

在实际项目开发中,建议你结合具体的业务场景和性能需求,对各项配置参数进行精细化调整和优化。同时,注意监控主从数据库的运行状态,及时处理可能出现的数据同步延迟、连接池资源耗尽等问题。

如果你在实践过程中遇到任何疑问,或者有更优秀的实现方案和优化技巧,欢迎在评论区留言分享,我们一起探讨交流,共同提升后端开发技术水平!也别忘了点赞、收藏这篇文章,方便随时查阅,还可以转发给身边的后端开发者小伙伴,让更多人受益于数据库读写分离技术的强大威力!

点击这里复制本文地址 以上内容由nimo97整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

尼墨宝库 © All Rights Reserved.  蜀ICP备2024111239号-7