在Spring Boot应用中,MySQL数据库的读写分离和分库分表是两种常见的优化手段,用于提升数据库性能、扩展性和高可用性。以下是对这两种方案的详细讲解,包括实现原理、具体步骤以及代码示例。
一、读写分离
1. 什么是读写分离?
读写分离是一种通过主从复制技术将数据库的读操作和写操作分离到不同的实例上的策略:
主库(Master):负责处理写操作(INSERT、UPDATE、DELETE)。
从库(Slave):负责处理读操作(SELECT),并定期同步主库的数据。
这种架构可以有效减轻主库的压力,提高系统的吞吐量。
2. 实现原理
主从复制:MySQL支持基于二进制日志(binlog)的主从复制机制。主库将所有写操作记录到binlog中,从库通过拉取binlog进行数据同步。
负载均衡:通过中间件或应用程序层逻辑,将读请求路由到从库,写请求路由到主库。
3. 实现方案
3.1 使用中间件(如ShardingSphere、MyCat)
中间件可以自动完成读写分离,开发者无需修改业务代码。
ShardingSphere 示例
引入依赖:
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>5.1.0</version> </dependency>
配置 application.yml:
spring: shardingsphere: datasource: names: master,slave1,slave2 master: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://master-host:3306/db_name username: root password: root slave1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://slave1-host:3306/db_name username: root password: root slave2: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://slave2-host:3306/db_name username: root password: root rules: replica-query: data-sources: ds: primary-data-source-name: master replica-data-source-names: slave1,slave2 load-balancer-name: round_robin load-balancers: round_robin: type: ROUND_ROBIN
在代码中正常使用JPA或MyBatis,ShardingSphere会自动完成读写分离。
3.2 手动实现读写分离
如果不想使用中间件,可以通过自定义注解和动态数据源切换来实现。
实现步骤
配置多个数据源:
在 application.yml 中配置主库和从库:spring: datasource: master: url: jdbc:mysql://master-host:3306/db_name username: root password: root slave: url: jdbc:mysql://slave-host:3306/db_name username: root password: root
创建动态数据源:
@Configuration public class DataSourceConfig { @Bean(name = "masterDataSource") @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "slaveDataSource") @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } @Primary @Bean(name = "dynamicDataSource") public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", masterDataSource); targetDataSources.put("slave", slaveDataSource); AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceType(); } }; routingDataSource.setDefaultTargetDataSource(masterDataSource); routingDataSource.setTargetDataSources(targetDataSources); return routingDataSource; } }
切换数据源上下文:
public class DataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setDataSourceType(String type) { contextHolder.set(type); } public static String getDataSourceType() { return contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } }
AOP拦截读写操作:
@Aspect @Component public class DataSourceAspect { @Before("@annotation(org.springframework.transaction.annotation.Transactional)") public void switchToMaster() { DataSourceContextHolder.setDataSourceType("master"); } @Before("execution(* com.example.service.*.*(..)) && !@annotation(org.springframework.transaction.annotation.Transactional)") public void switchToSlave() { DataSourceContextHolder.setDataSourceType("slave"); } @After("execution(* com.example.service.*.*(..))") public void clearDataSource() { DataSourceContextHolder.clearDataSourceType(); } }
二、分库分表
1. 什么是分库分表?
分库分表是将一个大表的数据拆分到多个数据库或多个表中,以解决单表数据量过大导致的性能问题。
分库:将数据分布到多个物理数据库中。
分表:将数据分布到多个物理表中。
2. 实现原理
水平拆分:按某种规则(如用户ID、时间戳)将数据分散到不同的库或表中。
垂直拆分:按业务模块将数据分散到不同的库中。
3. 实现方案
3.1 使用中间件(如ShardingSphere)
ShardingSphere 提供了强大的分库分表功能。
ShardingSphere 示例
引入依赖(同上)。
配置 application.yml:
spring: shardingsphere: datasource: names: ds0,ds1 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://host1:3306/db0 username: root password: root ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://host2:3306/db1 username: root password: root rules: sharding: tables: user: actual-data-nodes: ds$->{0..1}.user_$->{0..1} table-strategy: standard: sharding-column: user_id sharding-algorithm-name: user-inline key-generate-strategy: column: user_id key-generator-name: snowflake sharding-algorithms: user-inline: type: INLINE props: algorithm-expression: user_$->{user_id % 2} key-generators: snowflake: type: SNOWFLAKE
在代码中正常使用JPA或MyBatis,ShardingSphere会自动完成分库分表。
3.2 手动实现分库分表
手动实现需要在业务代码中编写分库分表逻辑,复杂度较高,通常不推荐。
三、总结
读写分离:适合解决主库写压力过大的问题,可以通过中间件或手动实现。
分库分表:适合解决单表数据量过大的问题,建议优先使用中间件(如ShardingSphere)。
最佳实践:结合缓存、索引优化等手段,进一步提升系统性能。
希望以上内容对你有所帮助!
评论区