MsSql

关注公众号 jb51net

关闭
首页 > 数据库 > MsSql > SQL 读写分离分配

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

作者:灰_灰丶灰

读写分离的分配,即如何在应用程序中将读操作和写操作路由到不同的数据库实例,可以通过几种不同的方法来实现,下面就来介绍SQL实现读写分离的分配的几种方式,感兴趣的可以了解一下

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

应用程序层实现

在应用程序层实现读写分离,通常通过配置多个数据源并在代码中显式地选择适当的数据源。使用 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 示例配置

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 }
)
systemctl start proxysql

数据库层实现

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

总结

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

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

您可能感兴趣的文章:
阅读全文