SQL实现读写分离的分配的几种方式

 更新时间:2025年04月24日 10:28:58   作者:灰_灰丶灰  
读写分离的分配,即如何在应用程序中将读操作和写操作路由到不同的数据库实例,可以通过几种不同的方法来实现,下面就来介绍SQL实现读写分离的分配的几种方式,感兴趣的可以了解一下

读写分离的分配,即如何在应用程序中将读操作和写操作路由到不同的数据库实例,可以通过几种不同的方法来实现。这些方法可以在应用程序层、数据库层或使用中间件来完成。以下是几种常见的实现方法:

应用程序层实现

在应用程序层实现读写分离,通常通过配置多个数据源并在代码中显式地选择适当的数据源。使用 AOP(面向切面编程)来自动选择数据源是一种常见的方法。

具体实现步骤

  • 配置多数据源:配置一个主数据源(用于写操作)和多个从数据源(用于读操作)。
  • 实现路由逻辑:通过 AOP 或其他方式在代码中选择适当的数据源。
  • 使用自定义注解:标记需要路由到不同数据源的方法。

以下是详细的实现示例:

配置文件

在 application.yml 中配置主库和从库的信息。

# application.yml
spring:
  datasource:
    master:
      url: jdbc:mysql://master-db:3306/mydb
      username: root
      password: root
    slaves:
      - url: jdbc:mysql://slave-db1:3306/mydb
        username: root
        password: root
      - url: jdbc:mysql://slave-db2:3306/mydb
        username: root
        password: root

数据源配置

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {

    @Autowired
    private MasterDataSourceProperties masterProperties;

    @Autowired
    private SlaveDataSourceProperties slaveProperties;

    @Bean
    @Primary
    public DataSource dataSource() {
        AbstractRoutingDataSource routingDataSource = new ReplicationRoutingDataSource();

        HikariDataSource masterDataSource = new HikariDataSource();
        masterDataSource.setJdbcUrl(masterProperties.getUrl());
        masterDataSource.setUsername(masterProperties.getUsername());
        masterDataSource.setPassword(masterProperties.getPassword());

        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource);

        for (int i = 0; i < slaveProperties.getSlaves().size(); i++) {
            SlaveProperties slave = slaveProperties.getSlaves().get(i);
            HikariDataSource slaveDataSource = new HikariDataSource();
            slaveDataSource.setJdbcUrl(slave.getUrl());
            slaveDataSource.setUsername(slave.getUsername());
            slaveDataSource.setPassword(slave.getPassword());
            targetDataSources.put("slave" + i, slaveDataSource);
        }

        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(masterDataSource);

        return routingDataSource;
    }
}

路由数据源

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {

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

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

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

    @Override
    protected Object determineCurrentLookupKey() {
        return contextHolder.get();
    }
}

数据源选择器

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAspect {

    @Before("@annotation(com.example.annotation.Master)")
    public void setWriteDataSourceType() {
        ReplicationRoutingDataSource.setDataSourceType("master");
    }

    @Before("@annotation(com.example.annotation.Slave) || execution(* com.example.service..*.find*(..))")
    public void setReadDataSourceType() {
        ReplicationRoutingDataSource.setDataSourceType("slave0"); // 可实现负载均衡策略
    }
}

自定义注解

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 Master {
}

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

示例服务

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Master
    @Transactional
    public void saveUser(User user) {
        userRepository.save(user);
    }

    @Slave
    public User findUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

使用中间件

使用中间件来实现读写分离也是一种常见的方法。中间件通常位于应用程序和数据库之间,负责根据操作类型将请求路由到适当的数据库实例。常见的中间件包括 MySQL 的 ProxySQL 和 MariaDB 的 MaxScale。

ProxySQL 示例配置

  • 安装 ProxySQL:可以通过包管理器安装 ProxySQL。
  • 配置 ProxySQL:在 proxysql.cnf 文件中配置主从数据库。
datadir="/var/lib/proxysql"

admin_variables=
{
    admin_credentials="admin:admin"
    mysql_ifaces="0.0.0.0:6032"
}

mysql_variables=
{
    threads=4
    max_connections=1024
}

mysql_servers =
(
    { address="master-db", port=3306, hostgroup=0, max_connections=1000, weight=1 },
    { address="slave-db1", port=3306, hostgroup=1, max_connections=1000, weight=1 },
    { address="slave-db2", port=3306, hostgroup=1, max_connections=1000, weight=1 }
)

mysql_users =
(
    { username="proxyuser", password="proxypassword", default_hostgroup=0, transaction_persistent=1 }
)

mysql_query_rules =
(
    { rule_id=1, match_pattern="^SELECT", destination_hostgroup=1, apply=1 }
)
  • 启动 ProxySQL:使用 systemctl 或其他方式启动 ProxySQL。
systemctl start proxysql

数据库层实现

有些数据库本身提供了读写分离的功能。例如,MySQL 的复制机制允许配置一个主数据库和多个从数据库,然后通过连接池或驱动程序实现读写分离。

总结

读写分离的实现方法有多种,可以根据具体需求和技术栈选择适合的方法。在应用程序层实现读写分离较为灵活,可以精细控制读写操作的路由逻辑;使用中间件实现读写分离则可以简化应用程序的逻辑,但需要额外维护中间件的配置和管理;在数据库层实现读写分离可以利用数据库本身的功能,减少对应用程序的改动。

到此这篇关于SQL实现读写分离的分配的几种方式的文章就介绍到这了,更多相关SQL 读写分离分配内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SQL Server还原完整备份和差异备份的操作过程

    SQL Server还原完整备份和差异备份的操作过程

    这篇文章主要介绍了SQL Server 还原 完整备份和差异备份的详细操作,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-09-09
  • SQL 研究 相似的数据类型

    SQL 研究 相似的数据类型

    数据类型在精度,范围上有较大的差别。选择合适的类型可以减少table和index的大小,进而减少IO的开销,提高效率。本文介绍基本的数值类型及其之间的细小差别。
    2009-07-07
  • SQLServer:探讨EXEC与sp_executesql的区别详解

    SQLServer:探讨EXEC与sp_executesql的区别详解

    本篇文章是对EXEC与sp_executesql的区别进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • SQL Server误区30日谈 第12天 TempDB的文件数和需要和CPU数目保持一致

    SQL Server误区30日谈 第12天 TempDB的文件数和需要和CPU数目保持一致

    TempDB的文件没有必要分布在多个存储器之间。如果你看到PAGELATCH类型的等待,即使你进行了分布也不会改善性能,而如果PAGEIOLATCH型的等待,或许你需要多个存储器,但这也不是必然-有可能你需要讲整个TempDB迁移到另一个存储系统,而不是仅仅为TempDB增加一个文件
    2013-01-01
  • 如何恢复数据库的账号 登录名/用户名等

    如何恢复数据库的账号 登录名/用户名等

    当重装数系统/数据库之后,如何恢复数据库的账号 登录名/用户名 孤立用户 缩小ldf日志文件 修改sqlserver2000端口
    2013-08-08
  • LINQ to SQL:处理char(1)字段的方式会引起全表扫描问题

    LINQ to SQL:处理char(1)字段的方式会引起全表扫描问题

    1.相关内容: 在SQL Server 2000中,如果数据库的排序规则为Chinese_PRC_CI_AS,那么查询时是不分大小写的,例如下列这二条SQL语句,查询的结果是一样的。
    2008-03-03
  • SQL Server实现自动循环归档分区数据脚本详解

    SQL Server实现自动循环归档分区数据脚本详解

    最近在工作中遇到了关于sql server的一个问题,通过查找相关的资料终于解决了,所以下面这篇文章主要给大家介绍了关于SQL Server如何实现自动循环归档分区数据脚本的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-09-09
  • SQL Server数据库创建远程服务器备份计划(SQL Server2016)

    SQL Server数据库创建远程服务器备份计划(SQL Server2016)

    最近项目系统做安全加固,以前是本地备份,现在需要做远程内网服务器数据库备份,后期也有可能做异地备份,下面以SQL Server2016 内网服务器数据库备份为例给大家详细讲解SQL Server数据库创建远程服务器备份计划,感兴趣的朋友一起看看吧
    2023-10-10
  • EXEC(EXECUTE)函数访问INSERTED或DELETED的内部临时触发表

    EXEC(EXECUTE)函数访问INSERTED或DELETED的内部临时触发表

    近段时间,MS SQL方面,一直需要开发动态方面的存储过程或是触发器以及表函数。因为程序设计一开始就是让用户动态添或是删除一个表的字段,然而这个表的相关存储过程或是触发器以及为报表准备的表函数也会随之这个表的字段变化而变化
    2012-01-01
  • SqlServer将查询结果转换为XML和JSON

    SqlServer将查询结果转换为XML和JSON

    这篇文章主要介绍了SqlServer将查询结果转换为XML和JSON的相关资料,需要的朋友可以参考下
    2017-07-07

最新评论