网站建设与网页设计案例教程 重庆大学出版社,社交类网站手机模版,工商查询,四川省建设厅官方网站一、读写分离介绍
当使用Spring Boot开发数据库应用时#xff0c;读写分离是一种常见的优化策略。读写分离将读操作和写操作分别分配给不同的数据库实例#xff0c;以提高系统的吞吐量和性能。
读写分离实现主要是通过动态数据源功能实现的#xff0c;动态数据源是一种通过…一、读写分离介绍
当使用Spring Boot开发数据库应用时读写分离是一种常见的优化策略。读写分离将读操作和写操作分别分配给不同的数据库实例以提高系统的吞吐量和性能。
读写分离实现主要是通过动态数据源功能实现的动态数据源是一种通过在运行时动态切换数据库连接的机制。它允许应用程序根据不同的条件或配置选择不同的数据源以实现更灵活和可扩展的数据库访问。
二、实现读写分离-基础
1. 配置主数据库和从数据库的连接信息
# 主库配置
spring.datasource.master.jdbc-urljdbc:mysql://ip:port/master?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalse
spring.datasource.master.usernamemaster
spring.datasource.master.password123456
spring.datasource.master.driver-class-namecom.mysql.jdbc.Driver# 从库配置
spring.datasource.slave.jdbc-urljdbc:mysql://ip:port/slave?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalse
spring.datasource.slave.usernameslave
spring.datasource.slave.password123456
spring.datasource.slave.driver-class-namecom.mysql.jdbc.Driver
2. 创建主数据库和从数据库的数据源配置类
通过不同的条件限制和配置文件前缀可以完成不同数据源的创建工作不止是主从也可以是多个不同的数据库
主库数据源配置
Configuration
ConditionalOnProperty(spring.datasource.master.jdbc-url)
public class MasterDataSourceConfiguration {Bean(masterDataSource)ConfigurationProperties(prefix spring.datasource.master)public DataSource masterDataSource() {return DataSourceBuilder.create().build();}
}
从库数据源配置
Configuration
ConditionalOnProperty(spring.datasource.slave.jdbc-url)
public class SlaveDataSourceConfiguration {Bean(slaveDataSource)ConfigurationProperties(prefix spring.datasource.slave)public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}
}
3. 创建主从数据源枚举
public enum DataSourceTypeEnum {/*** 主库*/MASTER,/*** 从库*/SLAVE,;}
4. 创建动态路由数据源
这儿做了一个开关可以控制读写分离的开启和关闭工作可以讲操作全部切换到主库进行。然后根据上下文中的数据源类型来返回不同的数据源类型枚举
Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {Value(${DB_RW_SEPARATE_SWITCH:false})private boolean dbRwSeparateSwitch;Overrideprotected Object determineCurrentLookupKey() {if(dbRwSeparateSwitch DataSourceTypeEnum.SLAVE.equals(DataSourceContextHolder.getDataSourceType())) {log.info(DynamicRoutingDataSource 切换数据源到从库);return DataSourceTypeEnum.SLAVE;}log.info(DynamicRoutingDataSource 切换数据源到主库);// 根据需要指定当前使用的数据源这里可以使用ThreadLocal或其他方式来决定使用主库还是从库return DataSourceTypeEnum.MASTER;}
}
5. 创建动态数据源配置类
将主数据库和从数据库的数据源添加到动态数据源中并可以通过枚举创建一个数据源 map这样就可以通过上面的路由返回的枚举来切换数据源
Configuration
ConditionalOnProperty(spring.datasource.master.jdbc-url)
public class DynamicDataSourceConfiguration {Bean(dataSource)Primarypublic DataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) {MapObject, Object targetDataSources new HashMap();targetDataSources.put(DataSourceTypeEnum.MASTER, masterDataSource);targetDataSources.put(DataSourceTypeEnum.SLAVE, slaveDataSource);DynamicRoutingDataSource dynamicDataSource new DynamicRoutingDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.setDefaultTargetDataSource(masterDataSource);return dynamicDataSource;}
}
6. 创建DatasourceContextHolder类使用ThreadLocal存储当前线程的数据源类型
注意这儿有个潜在风险就是创建新的线程时会导致 ThreadLocal 中的数据无法正确读取如果涉及到在开启新线程可以使用 TransmittableThreadLocal 来进行父子线程数据的同步git 地址 https://github.com/alibaba/transmittable-thread-local
public class DataSourceContextHolder {private static final ThreadLocalDataSourceTypeEnum contextHolder new ThreadLocal();public static void setDataSourceType(DataSourceTypeEnum dataSourceType) {contextHolder.set(dataSourceType);}public static DataSourceTypeEnum getDataSourceType() {return contextHolder.get();}public static void clearDataSourceType() {contextHolder.remove();}
}
7. 创建自定义注解用于标记主和从数据源
Retention(RetentionPolicy.RUNTIME)
Target(ElementType.METHOD)
public interface MasterDataSource {
}
Retention(RetentionPolicy.RUNTIME)
Target(ElementType.METHOD)
public interface SlaveDataSource {
}
8. 创建切面类拦截数据库操作并根据注解设置切换数据源参数
Aspect
Component
public class DataSourceAspect {Before(annotation(xxx.MasterDataSource))public void setMasterDataSource(JoinPoint joinPoint) {DataSourceContextHolder.setDataSourceType(DataSourceTypeEnum.MASTER);}Before(annotation(xxx.SlaveDataSource))public void setSlaveDataSource(JoinPoint joinPoint) {DataSourceContextHolder.setDataSourceType(DataSourceTypeEnum.SLAVE);}After(annotation(xxx.MasterDataSource) || annotation(xxx.SlaveDataSource))public void clearDataSource(JoinPoint joinPoint) {DataSourceContextHolder.clearDataSourceType();}
}
9. 在Service层的方法上使用自定义注解标记查询数据源
Service
public class TestService {Autowiredprivate TestDao testDao;SlaveDataSourcepublic Test test() {return testDao.queryByPrimaryKey(11L);}
}
10. 排除掉数据源自动配置类
如果不排除自动配置类会导致初始化多个 dataSource 对象导致出现问题
SpringBootApplication(exclude {DataSourceAutoConfiguration.class})
三、实现读写分离-进阶
1. 使用链接池以Hikari为例
修改链接配置加入链接池相关配置即可
# 主库配置
spring.datasource.master.jdbc-urljdbc:mysql://ip:port/master?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalse
spring.datasource.master.usernamemaster
spring.datasource.master.password123456
spring.datasource.master.driver-class-namecom.mysql.jdbc.Driver
spring.datasource.master.typecom.zaxxer.hikari.HikariDataSource
spring.datasource.master.hikari.namemaster
spring.datasource.master.hikari.minimum-idle5
spring.datasource.master.hikari.idle-timeout30
spring.datasource.master.hikari.maximum-pool-size10
spring.datasource.master.hikari.auto-committrue
spring.datasource.master.hikari.pool-nameDatebookHikariCP
spring.datasource.master.hikari.max-lifetime1800000
spring.datasource.master.hikari.connection-timeout30000
spring.datasource.master.hikari.connection-test-querySELECT 1# 从库配置
spring.datasource.slave.jdbc-urljdbc:mysql://ip:port/slave?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalse
spring.datasource.slave.usernameroot
spring.datasource.slave.password123456
spring.datasource.slave.driver-class-namecom.mysql.jdbc.Driver
spring.datasource.slave.typecom.zaxxer.hikari.HikariDataSource
spring.datasource.slave.hikari.namemaster
spring.datasource.slave.hikari.minimum-idle5
spring.datasource.slave.hikari.idle-timeout30
spring.datasource.slave.hikari.maximum-pool-size10
spring.datasource.slave.hikari.auto-committrue
spring.datasource.slave.hikari.pool-nameDatebookHikariCP
spring.datasource.slave.hikari.max-lifetime1800000
spring.datasource.slave.hikari.connection-timeout30000
spring.datasource.slave.hikari.connection-test-querySELECT 1
2. 集成 mybatis 并在写入时强制切换到主库
不需要做任何配置正常集成 mybatis 即可使用读写分离功能
可以通过 mybatis 的拦截器在写入操作时强制切换到主库
Intercepts({Signature(type Executor.class, method update, args {MappedStatement.class, Object.class}),
})
Component
public class WriteInterceptor implements Interceptor {Overridepublic Object intercept(Invocation invocation) throws Throwable {// 获取 SQL 类型DataSourceTypeEnum dataSourceType DataSourceContextHolder.getDataSourceType();if(DataSourceTypeEnum.SLAVE.equals(dataSourceType)) {DataSourceContextHolder.setDataSourceType(DataSourceTypeEnum.MASTER);}try {// 执行 SQLreturn invocation.proceed();} finally {// 恢复数据源 考虑到写入后可能会反查后续都走主库// DataSourceContextHolder.setDataSourceType(dataSourceType);}}
} 作者京东健康 苏曼 来源京东云开发者社区 转发请注明来源