返回顶部
分享到

SpringBoot实现数据库读写分离的3种方法

java 来源:未知 作者:佚名 发布时间:2025-04-06 22:15:52 人浏览
摘要

一、数据库读写分离概述 在大型应用系统中,随着访问量的增加,数据库常常成为系统的性能瓶颈。为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式。它将数据库读

一、数据库读写分离概述

在大型应用系统中,随着访问量的增加,数据库常常成为系统的性能瓶颈。为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式。它将数据库读操作和写操作分别路由到不同的数据库实例,通常是将写操作指向主库(Master),读操作指向从库(Slave)。

读写分离的主要优势:

  • 分散数据库访问压力,提高系统的整体吞吐量
  • 提升读操作的性能和并发量
  • 增强系统的可用性和容错能力

在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三种主实现方案。

二、方案一:基于AbstractRoutingDataSource实现动态数据源

这种方案是基于Spring提供的AbstractRoutingDataSource抽象类,通过重写其中的determineCurrentLookupKey()方法来实现数据源的动态切换。

2.1 实现原理

AbstractRoutingDataSource的核心原理是在执行数据库操作时,根据一定的策略(通常基于当前操作的上下文)动态地选择实际的数据源。通过在业务层或AOP拦截器中设置上下文标识,让系统自动判断是读操作还是写操作,从而选择对应的数据源。

2.2 具体实现步骤

第一步:定义数据源枚举和上下文持有器

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

// 数据源类型枚举

public enum DataSourceType {

    MASTER, // 主库,用于写操作

    SLAVE   // 从库,用于读操作

}

 

// 数据源上下文持有器

public class DataSourceContextHolder {

    private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();

     

    public static void setDataSourceType(DataSourceType dataSourceType) {

        contextHolder.set(dataSourceType);

    }

     

    public static DataSourceType getDataSourceType() {

        return contextHolder.get() == null ? DataSourceType.MASTER : contextHolder.get();

    }

     

    public static void clearDataSourceType() {

        contextHolder.remove();

    }

}

第二步:实现动态数据源

1

2

3

4

5

6

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override

    protected Object determineCurrentLookupKey() {

        return DataSourceContextHolder.getDataSourceType();

    }

}

第三步:配置数据源

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

@Configuration

public class DataSourceConfig {

     

    @Bean

    @ConfigurationProperties(prefix = "spring.datasource.master")

    public DataSource masterDataSource() {

        return DataSourceBuilder.create().build();

    }

     

    @Bean

    @ConfigurationProperties(prefix = "spring.datasource.slave")

    public DataSource slaveDataSource() {

        return DataSourceBuilder.create().build();

    }

     

    @Bean

    public DataSource dynamicDataSource() {

        DynamicDataSource dynamicDataSource = new DynamicDataSource();

         

        Map<Object, Object> dataSourceMap = new HashMap<>(2);

        dataSourceMap.put(DataSourceType.MASTER, masterDataSource());

        dataSourceMap.put(DataSourceType.SLAVE, slaveDataSource());

         

        // 设置默认数据源为主库

        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());

        dynamicDataSource.setTargetDataSources(dataSourceMap);

         

        return dynamicDataSource;

    }

     

    @Bean

    public SqlSessionFactory sqlSessionFactory() throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        sqlSessionFactoryBean.setDataSource(dynamicDataSource());

         

        // 设置MyBatis配置

        // ...

         

        return sqlSessionFactoryBean.getObject();

    }

}

第四步:实现AOP拦截器,根据方法匹配规则自动切换数据源

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

@Aspect

@Component

public class DataSourceAspect {

     

    // 匹配所有以select、query、get、find开头的方法为读操作

    @Pointcut("execution(* com.example.service.impl.*.*(..))")

    public void servicePointcut() {}

     

    @Before("servicePointcut()")

    public void switchDataSource(JoinPoint point) {

        // 获取方法名

        String methodName = point.getSignature().getName();

         

        // 根据方法名判断是读操作还是写操作

        if (methodName.startsWith("select") ||

            methodName.startsWith("query") ||

            methodName.startsWith("get") ||

            methodName.startsWith("find")) {

            // 读操作使用从库

            DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE);

        } else {

            // 写操作使用主库

            DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);

        }

    }

     

    @After("servicePointcut()")

    public void restoreDataSource() {

        // 清除数据源配置

        DataSourceContextHolder.clearDataSourceType();

    }

}

第五步:配置文件application.yml

1

2

3

4

5

6

7

8

9

10

11

12

spring:

  datasource:

    master:

      jdbc-url: jdbc:mysql://master-db:3306/test?useSSL=false

      username: root

      password: root

      driver-class-name: com.mysql.cj.jdbc.Driver

    slave:

      jdbc-url: jdbc:mysql://slave-db:3306/test?useSSL=false

      username: root

      password: root

      driver-class-name: com.mysql.cj.jdbc.Driver

第六步:使用注解方式灵活控制数据源(可选增强)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

// 定义自定义注解

@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface DataSource {

    DataSourceType value() default DataSourceType.MASTER;

}

 

// 修改AOP拦截器,优先使用注解指定的数据源

@Aspect

@Component

public class DataSourceAspect {

     

    @Pointcut("@annotation(com.example.annotation.DataSource)")

    public void dataSourcePointcut() {}

     

    @Before("dataSourcePointcut()")

    public void switchDataSource(JoinPoint point) {

        MethodSignature signature = (MethodSignature) point.getSignature();

        Method method = signature.getMethod();

         

        DataSource dataSource = method.getAnnotation(DataSource.class);

        if (dataSource != null) {

            DataSourceContextHolder.setDataSourceType(dataSource.value());

        }

    }

     

    @After("dataSourcePointcut()")

    public void restoreDataSource() {

        DataSourceContextHolder.clearDataSourceType();

    }

}

 

// 在Service方法上使用

@Service

public class UserServiceImpl implements UserService {

     

    @Override

    @DataSource(DataSourceType.SLAVE)

    public List<User> findAllUsers() {

        return userMapper.selectAll();

    }

     

    @Override

    @DataSource(DataSourceType.MASTER)

    public void createUser(User user) {

        userMapper.insert(user);

    }

}

2.3 优缺点分析

优点:

  • 实现简单,不依赖第三方组件
  • 侵入性小,对业务代码影响较小
  • 灵活性高,可以根据业务需求灵活切换数据源
  • 支持多数据源扩展,不限于主从两个库

缺点:

  • 需要手动指定或通过约定规则判断读写操作

适用场景:

  • 中小型项目,读写请求分离明确
  • 对中间件依赖要求低的场景
  • 临时性能优化,快速实现读写分离

三、方案二:基于ShardingSphere-JDBC实现读写分离

ShardingSphere-JDBC是Apache ShardingSphere项目下的一个子项目,它通过客户端分片的方式,为应用提供了透明化的读写分离和分库分表等功能。

3.1 实现原理

ShardingSphere-JDBC通过拦截JDBC驱动,重写SQL解析与执行流程来实现读写分离。它能够根据SQL语义自动判断读写操作,并将读操作负载均衡地分发到多个从库。

3.2 具体实现步骤

第一步:添加依赖

1

2

3

4

5

6

7

8

9

10

<dependency>

    <groupId>org.apache.shardingsphere</groupId>

    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>

    <version>5.2.1</version>

</dependency>

<dependency>

    <groupId>com.mysql</groupId>

    <artifactId>mysql-connector-j</artifactId>

    <scope>runtime</scope>

</dependency>

第二步:配置文件application.yml

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

spring:

  shardingsphere:

    mode:

      type: Memory

    datasource:

      names: master,slave1,slave2

      master:

        type: com.zaxxer.hikari.HikariDataSource

        driver-class-name: com.mysql.cj.jdbc.Driver

        jdbc-url: jdbc:mysql://master-db:3306/test?useSSL=false

        username: root

        password: root

      slave1:

        type: com.zaxxer.hikari.HikariDataSource

        driver-class-name: com.mysql.cj.jdbc.Driver

        jdbc-url: jdbc:mysql://slave1-db:3306/test?useSSL=false

        username: root

        password: root

      slave2:

        type: com.zaxxer.hikari.HikariDataSource

        driver-class-name: com.mysql.cj.jdbc.Driver

        jdbc-url: jdbc:mysql://slave2-db:3306/test?useSSL=false

        username: root

        password: root

    rules:

      readwrite-splitting:

        data-sources:

          readwrite_ds:

            type: Static

            props:

              write-data-source-name: master

              read-data-source-names: slave1,slave2

            load-balancer-name: round_robin

        load-balancers:

          round_robin:

            type: ROUND_ROBIN

    props:

      sql-show: true # 开启SQL显示,方便调试

第三步:创建数据源配置类

1

2

3

4

5

6

7

8

9

10

11

12

13

@Configuration

public class DataSourceConfig {

     

    // 无需额外配置,ShardingSphere-JDBC会自动创建并注册DataSource

     

    @Bean

    @ConfigurationProperties(prefix = "mybatis")

    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        sqlSessionFactoryBean.setDataSource(dataSource);

        return sqlSessionFactoryBean;

    }

}

第四步:强制主库查询的注解(可选)

在某些场景下,即使是查询操作也需要从主库读取最新数据,ShardingSphere提供了hint机制来实现这一需求。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

// 定义主库查询注解

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface MasterRoute {

}

 

// 创建AOP切面拦截器

@Aspect

@Component

public class MasterRouteAspect {

     

    @Around("@annotation(com.example.annotation.MasterRoute)")

    public Object aroundMasterRoute(ProceedingJoinPoint joinPoint) throws Throwable {

        try {

            HintManager.getInstance().setWriteRouteOnly();

            return joinPoint.proceed();

        } finally {

            HintManager.clear();

        }

    }

}

 

// 在需要主库查询的方法上使用注解

@Service

public class OrderServiceImpl implements OrderService {

     

    @Autowired

    private OrderMapper orderMapper;

     

    @Override

    @MasterRoute

    public Order getLatestOrder(Long userId) {

        // 这里的查询会路由到主库

        return orderMapper.findLatestByUserId(userId);

    }

}

3.3 优缺点分析

优点:

  • 自动识别SQL类型,无需手动指定读写数据源
  • 支持多从库负载均衡
  • 提供丰富的负载均衡算法(轮询、随机、权重等)
  • 完整的分库分表能力,可无缝扩展
  • 对应用透明,业务代码无需修改

缺点:

  • 引入额外的依赖和学习成本
  • 配置相对复杂
  • 性能有轻微损耗(SQL解析和路由)

适用场景:

  • 中大型项目,有明确的读写分离需求
  • 需要负载均衡到多从库的场景
  • 未来可能需要分库分表的系统

四、方案三:基于MyBatis插件实现读写分离

MyBatis提供了强大的插件机制,允许在SQL执行的不同阶段进行拦截和处理。通过自定义插件,可以实现基于SQL解析的读写分离功能。

4.1 实现原理

MyBatis允许拦截执行器的query和update方法,通过拦截这些方法,可以在SQL执行前动态切换数据源。这种方式的核心是编写一个拦截器,分析即将执行的SQL语句类型(SELECT/INSERT/UPDATE/DELETE),然后根据SQL类型切换到相应的数据源。

4.2 具体实现步骤

第一步:定义数据源和上下文(与方案一类似)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

public enum DataSourceType {

    MASTER, SLAVE

}

 

public class DataSourceContextHolder {

    private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();

     

    public static void setDataSourceType(DataSourceType dataSourceType) {

        contextHolder.set(dataSourceType);

    }

     

    public static DataSourceType getDataSourceType() {

        return contextHolder.get() == null ? DataSourceType.MASTER : contextHolder.get();

    }

     

    public static void clearDataSourceType() {

        contextHolder.remove();

    }

}

 

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override

    protected Object determineCurrentLookupKey() {

        return DataSourceContextHolder.getDataSourceType();

    }

}

第二步:实现MyBatis拦截器

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

@Intercepts({

    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),

    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),

    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})

})

@Component

public class ReadWriteSplittingInterceptor implements Interceptor {

     

    @Override

    public Object intercept(Invocation invocation) throws Throwable {

        Object[] args = invocation.getArgs();

        MappedStatement ms = (MappedStatement) args[0];

         

        try {

            // 判断是否为事务

            boolean isTransactional = TransactionSynchronizationManager.isActualTransactionActive();

             

            // 如果是事务,则使用主库

            if (isTransactional) {

                DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);

                return invocation.proceed();

            }

             

            // 根据SQL类型选择数据源

            if (ms.getSqlCommandType() == SqlCommandType.SELECT) {

                // 读操作使用从库

                DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE);

            } else {

                // 写操作使用主库

                DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);

            }

             

            return invocation.proceed();

        } finally {

            // 清除数据源配置

            DataSourceContextHolder.clearDataSourceType();

        }

    }

     

    @Override

    public Object plugin(Object target) {

        if (target instanceof Executor) {

            return Plugin.wrap(target, this);

        }

        return target;

    }

     

    @Override

    public void setProperties(Properties properties) {

        // 可以从配置文件加载属性

    }

}

第三步:配置数据源和MyBatis插件

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

@Configuration

public class DataSourceConfig {

     

    @Bean

    @ConfigurationProperties(prefix = "spring.datasource.master")

    public DataSource masterDataSource() {

        return DataSourceBuilder.create().build();

    }

     

    @Bean

    @ConfigurationProperties(prefix = "spring.datasource.slave")

    public DataSource slaveDataSource() {

        return DataSourceBuilder.create().build();

    }

     

    @Bean

    public DataSource dynamicDataSource() {

        DynamicDataSource dynamicDataSource = new DynamicDataSource();

         

        Map<Object, Object> dataSourceMap = new HashMap<>(2);

        dataSourceMap.put(DataSourceType.MASTER, masterDataSource());

        dataSourceMap.put(DataSourceType.SLAVE, slaveDataSource());

         

        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());

        dynamicDataSource.setTargetDataSources(dataSourceMap);

         

        return dynamicDataSource;

    }

     

    @Bean

    public SqlSessionFactory sqlSessionFactory(@Autowired ReadWriteSplittingInterceptor interceptor) throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        sqlSessionFactoryBean.setDataSource(dynamicDataSource());

         

        // 添加MyBatis插件

        sqlSessionFactoryBean.setPlugins(new Interceptor[]{interceptor});

         

        // 其他MyBatis配置

        // ...

         

        return sqlSessionFactoryBean.getObject();

    }

}

第四步:强制主库查询注解(可选)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

@Configuration

public class DataSourceConfig {

     

    @Bean

    @ConfigurationProperties(prefix = "spring.datasource.master")

    public DataSource masterDataSource() {

        return DataSourceBuilder.create().build();

    }

     

    @Bean

    @ConfigurationProperties(prefix = "spring.datasource.slave")

    public DataSource slaveDataSource() {

        return DataSourceBuilder.create().build();

    }

     

    @Bean

    public DataSource dynamicDataSource() {

        DynamicDataSource dynamicDataSource = new DynamicDataSource();

         

        Map<Object, Object> dataSourceMap = new HashMap<>(2);

        dataSourceMap.put(DataSourceType.MASTER, masterDataSource());

        dataSourceMap.put(DataSourceType.SLAVE, slaveDataSource());

         

        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());

        dynamicDataSource.setTargetDataSources(dataSourceMap);

         

        return dynamicDataSource;

    }

     

    @Bean

    public SqlSessionFactory sqlSessionFactory(@Autowired ReadWriteSplittingInterceptor interceptor) throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        sqlSessionFactoryBean.setDataSource(dynamicDataSource());

         

        // 添加MyBatis插件

        sqlSessionFactoryBean.setPlugins(new Interceptor[]{interceptor});

         

        // 其他MyBatis配置

        // ...

         

        return sqlSessionFactoryBean.getObject();

    }

}

4.3 优缺点分析

优点:

  • 自动识别SQL类型,无需手动指定数据源
  • 可灵活扩展,支持复杂的路由规则
  • 基于MyBatis原生插件机制,无需引入额外的中间件

缺点:

  • 仅适用于使用MyBatis的项目
  • 需要理解MyBatis插件机制
  • 没有内置的负载均衡能力,需要额外开发
  • 可能与其他MyBatis插件产生冲突
  • 事务管理较为复杂

适用场景:

  • 纯MyBatis项目
  • 定制化需求较多的场景
  • 对第三方中间件有限制的项目
  • 需要对读写分离有更精细控制的场景

五、三种方案对比与选型指南

5.1 功能对比

功能特性 方案一:AbstractRoutingDataSource 方案二:ShardingSphere-JDBC 方案三:MyBatis插件
自动识别SQL类型 ? 需要手动或通过规则指定 ? 自动识别 ? 自动识别
多从库负载均衡 ? 需要自行实现 ? 内置多种算法 ? 需要自行实现
与分库分表集成 ? 不支持 ? 原生支持 ? 需要额外开发
开发复杂度 ?? 中等 ? 较低 ??? 较高
配置复杂度 ? 较低 ??? 较高 ?? 中等

5.2 选型建议

选择方案一(AbstractRoutingDataSource)的情况:

  • 项目规模较小,读写分离规则简单明确
  • 对第三方依赖敏感,希望减少依赖
  • 团队对Spring原生机制较为熟悉
  • 系统处于早期阶段,可能频繁变动

选择方案二(ShardingSphere-JDBC)的情况:

  • 中大型项目,有复杂的数据库访问需求
  • 需要多从库负载均衡能力
  • 未来可能需要分库分表
  • 希望尽量减少代码侵入
  • 对开发效率要求较高

选择方案三(MyBatis插件)的情况:

  • 项目完全基于MyBatis架构
  • 团队对MyBatis插件机制较为熟悉
  • 有特定的定制化需求
  • 希望对SQL路由有更细粒度的控制
  • 对框架依赖有严格限制

六、实施读写分离的最佳实践

6.1 数据一致性处理

从库数据同步存在延迟,这可能导致读取到过期数据的问题。处理方法:

  • 提供强制主库查询的选项:对于需要最新数据的查询,提供从主库读取的机制
  • 会话一致性:同一会话内的读写操作使用相同的数据源
  • 延迟检测:定期检测主从同步延迟,当延迟超过阈值时暂停从库查询

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

// 实现延迟检测的示例

@Component

@Slf4j

public class ReplicationLagMonitor {

     

    @Autowired

    private JdbcTemplate masterJdbcTemplate;

     

    @Autowired

    private JdbcTemplate slaveJdbcTemplate;

     

    private AtomicBoolean slaveTooLagged = new AtomicBoolean(false);

     

    @Scheduled(fixedRate = 5000) // 每5秒检查一次

    public void checkReplicationLag() {

        try {

            // 在主库写入标记

            String mark = UUID.randomUUID().toString();

            masterJdbcTemplate.update("INSERT INTO replication_marker(marker, create_time) VALUES(?, NOW())", mark);

             

            // 等待一定时间,给从库同步的机会

            Thread.sleep(1000);

             

            // 从从库查询该标记

            Integer count = slaveJdbcTemplate.queryForObject(

                "SELECT COUNT(*) FROM replication_marker WHERE marker = ?", Integer.class, mark);

             

            // 判断同步延迟

            boolean lagged = (count == null || count == 0);

            slaveTooLagged.set(lagged);

  

            if (lagged) {

                log.warn("Slave replication lag detected, routing read operations to master");

            } else {

                log.info("Slave replication is in sync");

            }

        } catch (Exception e) {

            log.error("Failed to check replication lag", e);

            slaveTooLagged.set(true); // 发生异常时,保守地认为从库延迟过大

        } finally{

            // 删除标记数据

            masterJdbcTemplate.update("DELETE FROM replication_marker WHERE marker = ?", mark);

        }

    }

     

    public boolean isSlaveTooLagged() {

        return slaveTooLagged.get();

    }

}

6.2 事务管理

读写分离环境下的事务处理需要特别注意:

  • 事务内操作都走主库:确保事务一致性
  • 避免长事务:长事务会长时间锁定主库资源
  • 区分只读事务:对于只读事务,可以考虑路由到从库

6.4 监控与性能优化

  • 监控读写比例:了解系统的读写比例,优化资源分配
  • 慢查询监控:监控各数据源的慢查询
  • 连接池优化:根据实际负载调整连接池参数

1

2

3

4

5

6

7

8

9

10

11

# HikariCP连接池配置示例

spring:

  datasource:

    master:

      # 主库偏向写操作,连接池可以适当小一些

      maximum-pool-size: 20

      minimum-idle: 5

    slave:

      # 从库偏向读操作,连接池可以适当大一些

      maximum-pool-size: 50

      minimum-idle: 10

七、总结

在实施读写分离时,需要特别注意数据一致性、事务管理和故障处理等方面的问题。

通过合理的架构设计和细致的实现,读写分离可以有效提升系统的读写性能和可扩展性,为应用系统的高可用和高性能提供有力支持。

无论选择哪种方案,请记住读写分离是一种


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • SpringBoot实现数据库读写分离的3种方法
    一、数据库读写分离概述 在大型应用系统中,随着访问量的增加,数据库常常成为系统的性能瓶颈。为了提高系统的读写性能和可用性,读
  • SpringBoot启动报错的11个高频问题排查与解决终极
    1. 依赖冲突:NoSuchMethodError 的终极解法 错误现象: java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotationUtils.isAnnotationInherited 原因分析
  • SpringBoot2.6.x与Swagger3兼容问题及解决方法
    报错: Failed to start bean documentationPluginsBootstrapper; nested exception is java.lang.NullPointerException 解决: ? 如果项目中未引入spring-boot-starter-actuator,
  • Java使用多线程处理未知任务数
    知道任务个数,你可以定义好线程数规则,生成线程数去跑 代码说明: 1.虚拟线程池: 使用 Executors.newVirtualThreadPerTaskExecutor() 创建虚拟线程
  • Java高级-反射&动态代理介绍
    1. 反射 1.1 反射的概述 专业的解释(了解一下): 是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法; 对于任意一个
  • 使用EasyPoi实现多Sheet页导出的代码
    因多次遇到导出多Sheet页的需求,故记录下来,以备后续参考使用 一、Pom依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 !-- 集成easypoi组件 .导出excel ht
  • idea导入若依项目教程

    idea导入若依项目教程
    IDEA导入若依管理系统 项目官网地址:https://gitee.com/y_project/RuoYi-Vue 前提 系统需求: JDK = 1.8 MySQL = 5.5 Maven = 3.0 redis 必须启动(可以下载一个
  • Java中实现订单超时自动取消功能(最新推荐)

    Java中实现订单超时自动取消功能(最新推荐)
    在开发中,我们会遇到需要延时任务的业务场景,例如:用户下单之后未在规定的时间内支付成功,该订单会自动取消; 用户注册成功15分
  • 阿里巴巴TransmittableThreadLocal使用介绍
    ThreadLocal在上下文的数据传输上非常的方便和简洁。 工业实践中,比较常用的有三个,ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal,那
  • SpringBoot使用Jackson介绍
    概述 Springboot配置JackSon处理类属性,JavaBean序列化为JSON格式,常用框架:阿里fastjson,谷歌gson、Jackson等。 ① 性能:Jackson Fastjson Gson 同个结
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计