MySQL的读写分离是一种数据库架构模式,它通过在主服务器上执行写操作,将更新实时地同步到一个或多个从服务器,并在从服务器上执行读操作。这种架构模式可以显著提高应用程序的性能和可伸缩性。主服务器处理所有的写操作,因此主服务器的性能必须足够强大,以满足所有写操作的要求。从服务器只处理读操作,因此可以使用较便宜的硬件,使得应用程序可以水平扩展读操作的吞吐量。在读写分离的架构中,应用程序可以通过连接到主服务器或从服务器来执行读和写操作。这种架构可以提高应用程序的性能和可靠性,因为它可以处理更多的请求并提供更好的容错能力。
总而言之,mysql读写分离是因为数据库写入效率要低于读取效率,一般系统中数据读取频率高于写入频率,单个数据库实例在写入的时候会影响读取性。为了将较消耗资源的增删改操作与消耗资源较少的读取操作做出区分,用不同的服务器节点分别进行处理,从而减轻服务器消耗。
Mysql的读写分离通常是通过主从复制的方式进行的,使Master节点操作写请求,Slave节点操作读请求。
实现 MySQL 读写分离有多种方式,以下是一个基于 SpringBoot 的示例:
1.在 pom.xml 中添加 MySQL 和 Druid 的依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
2.配置数据源和连接池
在 application.yml 中配置主数据源和从数据源的连接信息,可以使用 Druid 数据源,具体配置如下:
spring:
datasource:
# 主数据源
master:
url: jdbc:mysql://localhost:3306/master_db?useUnicode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
# Druid 配置
type: com.alibaba.druid.pool.DruidDataSource
initial-size: 1
max-active: 10
max-wait: 60000
min-idle: 3
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall,log4j
# 从数据源
slave:
url: jdbc:mysql://localhost:3306/slave_db?useUnicode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
# Druid 配置
type: com.alibaba.druid.pool.DruidDataSource
initial-size: 1
max-active: 10
max-wait: 60000
min-idle: 3
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall,log4j
3.配置读写分离
在 SpringBoot 应用启动类中添加如下代码:
@Configuration
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@Qualifier("masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@Qualifier("slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
// 配置多数据源
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("masterDataSource", masterDataSource());
dataSourceMap.put("slaveDataSource", slaveDataSource());
dynamicDataSource.setTargetDataSources(dataSourceMap);
return dynamicDataSource;
}
}
在 DynamicDataSource 类中维护数据源的路由逻辑,根据当前请求的读写类型来选择哪个数据源,代码如下:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceTypeHolder.get();
}
}
4.通过注解实现读写分离
编写一个注解 @DataSource,用于标识当前方法的读写类型,根据方法上的注解来设置数据源的类型,在切面中将数据源的类型存到线程本地变量中,具体代码如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DataSourceType value() default DataSourceType.MASTER;
}
public enum DataSourceType {
MASTER,
SLAVE
}
@Component
@Aspect
@Slf4j
public class DataSourceAspect {
@Pointcut("@annotation(com.example.demo.datasource.annotation.DataSource)")
public void dataSourcePointCut() {
}
@Before("dataSourcePointCut()")
public void before(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
DataSource dataSource = signature.getMethod().getAnnotation(DataSource.class);
if (dataSource == null) {
// 设置为主数据源
DataSourceTypeHolder.set(DataSourceType.MASTER);
log.info("set datasource is " + DataSourceType.MASTER);
} else {
// 根据注解设置数据源
DataSourceTypeHolder.set(dataSource.value());
log.info("set datasource is " + dataSource.value());
}
}
@After("dataSourcePointCut()")
public void after(JoinPoint joinPoint) {
DataSourceTypeHolder.clear();
}
}
public class DataSourceTypeHolder {
private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();
public static void set(DataSourceType dataSourceType) {
contextHolder.set(dataSourceType);
}
public static DataSourceType get() {
return contextHolder.get();
}
public static void clear() {
contextHolder.remove();
}
}
以上就是一个基于 SpringBoot 的 MySQL 读写分离示例,通过使用注解来标识当前方法的读写类型,实现了数据源的动态切换。