SpringBoot 项目优雅实现读写分离
一、读写分离介绍
当使用Spring Boot开发数据库应用时,读写分离是一种常见的优化策略。读写分离将读操作和写操作分别分配给不同的数据库实例,以提高系统的吞吐量和性能。
读写分离实现主要是通过动态数据源功能实现的,动态数据源是一种通过在运行时动态切换数据库连接的机制。它允许应用程序根据不同的条件或配置选择不同的数据源,以实现更灵活和可扩展的数据库访问。
二、实现读写分离-基础
1. 配置主数据库和从数据库的连接信息
# 主库配置
spring.datasource.master.jdbc-url=jdbc:mysql://ip:port/master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.master.username=master
spring.datasource.master.password=123456
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
# 从库配置
spring.datasource.slave.jdbc-url=jdbc:mysql://ip:port/slave?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.slave.username=slave
spring.datasource.slave.password=123456
spring.datasource.slave.driver-class-name=com.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;
@Override
protected 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")
@Primary
public DataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) {
Map<Object, 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 ThreadLocal<DataSourceTypeEnum> 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 {
@Autowired
private TestDao testDao;
@SlaveDataSource
public Test test() {
return testDao.queryByPrimaryKey(11L);
}
}
10. 排除掉数据源自动配置类
如果不排除自动配置类会导致初始化多个 dataSource 对象导致出现问题
SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
三、实现读写分离-进阶
1. 使用链接池,以Hikari为例
修改链接配置,加入链接池相关配置即可
# 主库配置
spring.datasource.master.jdbc-url=jdbc:mysql://ip:port/master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.master.username=master
spring.datasource.master.password=123456
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.master.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.master.hikari.name=master
spring.datasource.master.hikari.minimum-idle=5
spring.datasource.master.hikari.idle-timeout=30
spring.datasource.master.hikari.maximum-pool-size=10
spring.datasource.master.hikari.auto-commit=true
spring.datasource.master.hikari.pool-name=DatebookHikariCP
spring.datasource.master.hikari.max-lifetime=1800000
spring.datasource.master.hikari.connection-timeout=30000
spring.datasource.master.hikari.connection-test-query=SELECT 1
# 从库配置
spring.datasource.slave.jdbc-url=jdbc:mysql://ip:port/slave?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.slave.username=root
spring.datasource.slave.password=123456
spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.slave.hikari.name=master
spring.datasource.slave.hikari.minimum-idle=5
spring.datasource.slave.hikari.idle-timeout=30
spring.datasource.slave.hikari.maximum-pool-size=10
spring.datasource.slave.hikari.auto-commit=true
spring.datasource.slave.hikari.pool-name=DatebookHikariCP
spring.datasource.slave.hikari.max-lifetime=1800000
spring.datasource.slave.hikari.connection-timeout=30000
spring.datasource.slave.hikari.connection-test-query=SELECT 1
2. 集成 mybatis 并在写入时强制切换到主库
不需要做任何配置,正常集成 mybatis 即可使用读写分离功能
可以通过 mybatis 的拦截器在写入操作时强制切换到主库
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
})
@Component
public class WriteInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取 SQL 类型
DataSourceTypeEnum dataSourceType = DataSourceContextHolder.getDataSourceType();
if(DataSourceTypeEnum.SLAVE.equals(dataSourceType)) {
DataSourceContextHolder.setDataSourceType(DataSourceTypeEnum.MASTER);
}
try {
// 执行 SQL
return invocation.proceed();
} finally {
// 恢复数据源 考虑到写入后可能会反查,后续都走主库
// DataSourceContextHolder.setDataSourceType(dataSourceType);
}
}
}
作者:京东健康 苏曼
来源:京东云开发者社区 转发请注明来源
SpringBoot 项目优雅实现读写分离的更多相关文章
- SpringBoot 整合 MyCat 实现读写分离
MyCat一个彻底开源的,面向企业应用开发的大数据库集群.基于阿里开源的Cobar产品而研发.能满足数据库数据大量存储:提高了查询性能.文章介绍如何实现MyCat连接MySQL实现主从分离,并集成Sp ...
- web 项目 连接mycat 读写分离失效问题,
问题描述:mycat 读写分离已配好,在sql工具上查询操作是可以的,但是在项目中,读数据就走write 数据库, 解决 :环境spring +mvc +ibaites,在java中自己写j ...
- 搭建 springboot 2.0 mybatis 读写分离 配置区分不同环境
最近公司打算使用springboot2.0, springboot支持HTTP/2,所以提前先搭建一下环境.网上很多都在springboot1.5实现的,所以还是有些差异的.接下来咱们一块看一下. 文 ...
- SpringBoot整合AbstractRoutingDataSource实现读写分离
在配置数据源时候,已经把主库和从库的数据源配置到DynamicDataSource里了 利用AbstractRoutingDataSource实现动态切换数据源,可以通过注解或者根据方法名前缀切换要使 ...
- Spring + Mybatis项目实现数据库读写分离
主要思路:通过实现AbstractRoutingDataSource类来动态管理数据源,利用面向切面思维,每一次进入service方法前,选择数据源. 1.首先pom.xml中添加aspect依赖 & ...
- springboot整合shading_jdbc实现读写分离
之前是通过XML方式来配置数据源,读写分离策略,分库分表策略等,之前有朋友也问过我,有没有Spring Boot的方式来配置,既然已经用Spring Boot还用XML来配置感觉有点不协调. 其实吧我 ...
- springboot(五)读写分离,多个读库,Druid监控--待整理
1.修改mybatis.properties # 主数据源,默认的 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.d ...
- springboot+springAOP实现数据库读写分离及数据库同步(MySQL)----最新可用2019-2-14
原文:https://blog.csdn.net/wsbgmofo/article/details/79260896 1,数据源配置文件,如下 datasource.readSize=1spring. ...
- Springboot + Mysql8实现读写分离
在实际的生产环境中,为了确保数据库的稳定性,我们一般会给数据库配置双机热备机制,这样在master数据库崩溃后,slave数据库可以立即切换成主数据库,通过主从复制的方式将数据从主库同步至从库,在业务 ...
- J2EE 项目读写分离
先回答下 1.为啥要读写分离? 大家都知道最初开始,一个项目对应一个数据库,基本是一对一的,但是由于后来用户及数据还有访问的急剧增多, 系统在数据的读写上出现了瓶颈,为了让提高效率,想读和写不相互影响 ...
随机推荐
- git: failed to push some refs to
错误原因 没有添加readme文件 解决方案 git pull --rebase origin master 至此问题解决
- AcWing 4799. 最远距离题解
请看: 我们规定,如果一个无向连通图满足去掉其中的任意一条边都会使得该图变得不连通,则称该图为有效无向连通图. 去掉一条边就不连通了,这不就是树吗? (否则如果是图(就是不是树的图)的话,一定有环,拆 ...
- 语音合成技术3:HierVST: Hierarchical Adaptive Zero-shot Voice Style Transfer
HierVST: 分层自适应零样本语音风格转换 摘要: 尽管语音风格转换(VST)领域取得了快速进展,但最近的零样本VST系统仍然缺乏将新的说话者的语音风格进行转换的能力.在本文中,我们提出了Hier ...
- Json 基于类 Newtonsoft.Json.Linq.JToken 的应用简介【C# 基础】
〇.前言 在日常开发中,对于 Json 的使用还是比较频繁的,特别是 Json 对象和字符串或者实体对象之间的转换. 虽然几乎天天用,但是总是感觉没那么明了,今天结合微软的 Newtonsoft.Js ...
- 【技术积累】Vue.js中的CSS过渡【一】
CSS过渡是什么 在Vue中,可以使用<transition>组件来实现CSS过渡效果.CSS过渡是指在元素的状态发生改变时,通过添加或移除CSS类来实现平滑的过渡效果. <tran ...
- Html+JavaScript实现手写签名
前言 Hello各位,本葡萄又来啦,今天遇到的场景是这样的:在日常业务流程中,经常需要某一流程环节中相关责任人员进行审批签字,早期许多公司为了省事就直接会把这位负责人的签名以键盘打字(楷体)的形式打印 ...
- 全免费开源-国内搭建ChatGPT个人镜像站与维护全攻略
本教程收集于:AIGC从入门到精通教程汇总 全免费开源,仅需一个域名就可以部署国内的ChatGPT镜像版本. One-Click to deploy well-designed ChatGPT web ...
- Go 并发编程 - 并发安全(二)
什么是并发安全 并发情况下,多个线程或协程会同时操作同一个资源,例如变量.数据结构.文件等.如果不保证并发安全,就可能导致数据竞争.脏读.脏写.死锁.活锁.饥饿等一系列并发问题,产生重大的安全隐患,比 ...
- 分布式环境下Session共享问题解决和原理讲解
1.分布式环境下Session共享问题: 2.几种解决方法 3.通过后端统一存储方法在实际项目中问题的体现: 当session的作用域只限于auth.gulimall.com时,在auth.gulim ...
- day1项目配置
项目初始化 本项目使用vite进行构建,vite参考官网 pnpm包管理:performant npm,意味"高性能的npm".pnpm由npm/yarn衍生而来,解决了npmly ...