- 高并发读取场景:当应用程序有大量的读取操作,但写入操作相对较少时,读写分离可以将读取操作分散到多个数据库,以提高系统的读取性能和并发性能。
- 数据报告和分析场景:在需要生成复杂报告或数据分析的场景中,读写分离可以将读写分开,以确保报告和分析任务不会影响主库的写入性能。
- 全球分布式架构:对于全球应用程序,阅读和写作分离可以将阅读操作路由从数据库更接近用户,减少网络延迟,改善用户体验。
- 灾难容量和高可用性:通过将数据复制到多个数据库,读写分离可以提供灾难容量和高可用性。当主库出现故障时,可以快速切换到可用数据库,以确保业务的可持续运行。
- 减少数据库负载:通过将读取操作分散到图书馆,读写分离可以降低主图书馆的负载压力,提高主图书馆的写入性能和吞吐量。需要注意的是,读写分离并不适用于所有场景。对于需要高数据一致性的应用程序,可能需要额外的同步机制来确保主图书馆和从图书馆之间的数据一致性。此外,读写分离还需要考虑数据库同步延迟和数据更新策略。
- 提高性能和可扩展性:通过将读取操作分配给只负责读取的从库,可以将读取操作的负载均衡分散到多个数据库实例中,从而提高读取性能和系统的可扩展性。
- 降低主库压力:将读取操作从主库转移到主库,可以降低主库的负载压力,使主库专注于写作操作的处理。这可以提高主库的写入性能和吞吐量。
- 提高数据的可用性和容灾能力:系统的容灾能力可以通过使用多个从库进行数据复制来提高。当主库出现故障时,可以快速切换到从库,保证业务的可持续运行。
- 灵活的数据分发:阅读和写作分离允许根据应用程序的需要将不同类型的阅读操作分发到特定的图书馆。例如,您可以将只读查询路由转移到更接近用户的图书馆,以减少网络延迟,并提供更好的用户体验。
- 减少数据库锁竞争:读写分离可以减少主库上的并发写作操作,从而降低数据库锁竞争的可能性,提高整个系统的并发能力。
一般来说,MySQL读写分离可以优化读写操作的负载分布,提高性能和可用性,降低主库压力,提供灵活的数据分发,从而提高应用程序的性能和可伸缩性,提高用户体验。一般来说,它是降压和提高性能
三、读写分离分析技术选型- SpringBoot
- MybatisPlus
- MySQL
- 配置主从数据库
- 创建数据源配置类
- 创建动态数据源
- 配置MyBatis-Plus
- 事务管理配置
四、实现具体代码我自己的理解是,首先在服务器上从复制开始,以确保数据的一致性。然后通过SpringBoot和MybatisPlus配置
DataSource
(master和slave)创建动态数据源类,例如DynamicDataSource
,继承自AbstractRoutingDataSource
。创建一个SqlSessionFactory
bean,并将数据源设置为动态数据源。创建一个DataSourceTransactionManager bean
,并将数据源设置为动态数据源。通过AOP+自定义注释,可以在业务中配置读写。
我的mysql是通过docker在云服务器上部署的,所以下面将使用这种方法,但整体逻辑是相似的
First. MySQL主从复制前提: Docker已经建立了两个数据库,一个是主,一个是从。版本相同,端口不同,都启动了二进制日志(Binary Log),同时,主库与从库之间需要可靠的网络连接。
① 修改my.cnf主库和从库my.cnf中的server-id
MySQL实例的独特性需要不同的保证。这个配置在[mysqld]
模块中。如下图所示:
首先,您需要查看当前数据库中写入的二进制日志文件的名称和位置。
show master status;
进行配置:
CHANGE MASTER TO MASTER_HOST=120.46.129.14, MASTER_PORT=3389, MASTER_USER='root', MASTER_PASSWORD='root_886YJ', MASTER_LOG_FILE='mysql_bin.000003', MASTER_LOG_POS=154;
检查是否处于状态
SHOW SLAVE STATUS\G
两个Yes出现了,即成功
Second. 前提准备- 创建数据表作为测试
DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `user_id` bigint(20) NOT NULL COMMENT 用户id, `user_name` varchar(255) DEFAULT '' COMMENT "用户名", `user_phone` varchar(50) DEFAULT '' COMMENT 用户手机, `address` varchar(255) DEFAULT '' COMMENT '住址', `weight` int(3) NOT NULL DEFAULT '1' COMMENT 权重,大者优先, `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创造时间, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `user` VALUES ('1196978513958141952', '测试1', '18826334748', "广州市海珠区", '1', '2019-11-20 10:28:51', '2019-11-22 14:28:26'); INSERT INTO `user` VALUES ('1196978513958141953', '测试2', '18826274230', 广州市天河区, '2', '2019-11-20 10:29:37', '2019-11-22 14:28:14'); INSERT INTO `user` VALUES ('1196978513958141954', '测试3', '18826273900', 广州市天河区, '1', '2019-11-20 10:30:19', '2019-11-22 14:28:30');
- 创建SpringBoot项目,创建相应的实体类,配置pom文件,填写yml文件
User实体类
import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableId;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.util.Date;/** * @author LukeZhang */@Data@AllArgsConstructor@NoArgsConstructorpublic class User { @TableId(type = IdType.AUTO) private Long userId; private String userName; private String userPhone; private String address; private Integer weight; private Date createdAt; private Date updatedAt;}
pom配置-依赖部分
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- Druid连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.22</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> </dependencies>
yml配置
server: port: 8002spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 datasource: type: com.alibaba.druid.pool.DruidDataSource master: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://<yourip>:<yourport>/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true username: <yourusername> password: <yourpassword> slave: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://<yourip>:<yourport>/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true username: <yourusername> password: <yourpassword>
创建mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.example.rwdemo.domain.User;/** * @author LukeZhang * @date 2023/6/14 16:35 */public interface UserMapper extends BaseMapper<User> {}
Third. 实现动态数据源
- 枚举类方便区分主从
import lombok.Getter;/** * @author LukeZhang */@Getterpublic enum DynamicDataSourceEnum { MASTER("master"), SLAVE("slave"); private String dataSourceName; DynamicDataSourceEnum(String dataSourceName) { this.dataSourceName = dataSourceName; }}
- 创建相应的实体类接收参数MasterDatabase
/** * @author LukeZhang */import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;@Configuration@ConfigurationProperties(prefix = "spring.datasource.master")@Data@AllArgsConstructor@NoArgsConstructorpublic class Master { private String url; private String driverClassName; private String username; private String password;}
SlaveDataBase
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;/** * @author LukeZhang */@Configuration@ConfigurationProperties(prefix = "spring.datasource.slave")@Data@AllArgsConstructor@NoArgsConstructorpublic class Slave { private String url; private String driverClassName; private String username; private String password;}
- 配置动态数据源
/** * @author LukeZhang * @date 2023/6/14 16:37 */import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;import com.example.rwdemo.domain.DynamicDataSourceEnum;import com.example.rwdemo.holder.DynamicDataSource;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.annotation.MapperScan;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import javax.sql.DataSource;import java.util.HashMap;import java.util.Map;@Configuration@MapperScan("com.example.rwdemo.mapper")public class DataSourceConfig { @Autowired private Master master; @Autowired private Slave slave; @Bean(name = "masterDataSource") public DataSource masterDataSource() { return DataSourceBuilder.create().url(master.getUrl()).driverClassName(master.getDriverClassName()).password(master.getPassword()).username(master.getUsername()).build(); } @Bean(name = "slaveDataSource") public DataSource slaveDataSource() { return DataSourceBuilder.create() .url(slave.getUrl()).driverClassName(slave.getDriverClassName()).username(slave.getUsername()).password(slave.getPassword()) .build(); } /** * 从动态配置开始 */ @Bean public DynamicDataSource dynamicDb(@Qualifier("masterDataSource") DataSource masterDataSource, @Autowired(required = false) @Qualifier("slaveDataSource") DataSource slaveDataSource) { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DynamicDataSourceEnum.MASTER.getDataSourceName(), masterDataSource); if (slaveDataSource != null) { targetDataSources.put(DynamicDataSourceEnum.SLAVE.getDataSourceName(), slaveDataSource); } dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(masterDataSource); return dynamicDataSource; } @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDb") DataSource dynamicDataSource) throws Exception { MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean(); sessionFactoryBean.setDataSource(dynamicDataSource); sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*Mapper.xml")); return sessionFactoryBean.getObject(); } @Bean(name = "transactionManager") public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDb") DataSource dynamicDataSource) { return new DataSourceTransactionManager(dynamicDataSource); }}
- 通过aop方便实现
注意获取信息
import com.example.rwdemo.domain.DynamicDataSourceEnum;import java.lang.annotation.*;/** * @author LukeZhang * @date 2023/6/14 16:40 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@Documentedpublic @interface DataSourceSelector { DynamicDataSourceEnum value() default DynamicDataSourceEnum.MASTER; boolean clear() default true;}
编写AOP配置
import com.example.rwdemo.annotation.DataSourceSelector;import com.example.rwdemo.holder.DataSourceContextHolder;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import java.lang.reflect.Method;/** * @author LukeZhang */@Slf4j @Aspect @Order(value = 1) @Component public class DataSourceContextAop { @Around("@annotation(com.example.rwdemo.annotation.DataSourceSelector)") public Object setDynamicDataSource(ProceedingJoinPoint pjp) throws Throwable { boolean clear = true; try { Method method = this.getMethod(pjp); DataSourceSelector dataSourceImport = method.getAnnotation(DataSourceSelector.class); clear = dataSourceImport.clear(); DataSourceContextHolder.set(dataSourceImport.value().getDataSourceName()); log.info(=========数据源切换到:{}, dataSourceImport.value().getDataSourceName()); return pjp.proceed(); } finally { if (clear) { DataSourceContextHolder.clear(); } } } private Method getMethod(JoinPoint pjp) { MethodSignature signature = (MethodSignature)pjp.getSignature(); return signature.getMethod(); } }
Fourth. 编写服务和测试
- 编写
UserService
import com.example.rwdemo.annotation.DataSourceSelector;import com.example.rwdemo.domain.DynamicDataSourceEnum;import com.example.rwdemo.domain.User;import com.example.rwdemo.mapper.UserMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;/** * @author LukeZhang * @date 2023/6/14 16:45 */@Servicepublic class UserService { @Autowired private UserMapper userMapper; @DataSourceSelector(value = DynamicDataSourceEnum.MASTER) public int update(Long userId) { User user = new User(); user.setUserId(userId); user.setUserName(老薛); return userMapper.updateById(user); } @DataSourceSelector(value = DynamicDataSourceEnum.SLAVE) public User find(Long userId) { return userMapper.selectById(userId); }}
- 编写测试
import com.example.rwdemo.domain.User;import com.example.rwdemo.mapper.UserMapper;import com.example.rwdemo.service.UserService;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;/** * @author LukeZhang * @date 2023/6/14 16:45 */@SpringBootTestclass UserServiceTest { @Autowired UserService userService; @Autowired UserMapper userMapper; @Test void find() { User user = userService.find(1196978513958141952L); System.out.println("id:" + user.getUserId()); System.out.println("name:" + user.getUserName()); System.out.println("phone:" + user.getUserPhone()); } @Test void update() { Long userId = 1196978513958141952L; int update = userService.update(userId); System.out.println(update执行”+update); } @Test public void test() { List<User> users = userMapper.selectList(null); users.forEach( user -> { System.out.println(user.toString()); } ); }}
参考
读写分离就这么简单,一个小小的解释就够友情支持了——chatgpt
总结第一个博客!接下来,你会慢慢地记录你所学到的东西,这也是你自己的成长记录。同时,也方便您咨询和与您一起学习和讨论。
