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.为啥要读写分离? 大家都知道最初开始,一个项目对应一个数据库,基本是一对一的,但是由于后来用户及数据还有访问的急剧增多, 系统在数据的读写上出现了瓶颈,为了让提高效率,想读和写不相互影响 ...
随机推荐
- 【游记】NOIP2022 预备赛游记
Day -2 \(NOIP\) 就要来了,\(CSYZ\) 斥巨资给我们在 \(NOIP\) 正式考点举办了一场 \(NOIP\) 预备赛,真是太感动了~~ \(cy\) 说明天要颁奖,激动激动! D ...
- Unity的IPostprocessBuildWithReport:深入解析与实用案例
Unity IPostprocessBuildWithReport Unity IPostprocessBuildWithReport是Unity引擎中的一个非常有用的功能,它可以让开发者在构建项目后 ...
- 线性关系和非线性关系在.net中的应用
在数学中,线性关系和非线性关系是描述两个变量之间函数关系的两种不同类型. 线性关系是指两个变量之间可以用一条直线来表示的关系.具体来说,如果存在一个一次函数 y = kx + b,其中k和b是常数,使 ...
- nginx搭建静态文件下载服务器
配置文件大致内容 server { # 监听8001端口 listen 8001; server_name 192.168.0.2; # 指定使用utf8的编码 charset utf-8; # 内容 ...
- 行行AI人才直播第15期:【AIGC科技公司法律顾问】Amber《AIGC的法律挑战》
近年来,AIGC技术的迅速进步为社会经济发展带来了新的机遇.各行各业都开始关注AIGC相关技术在商业落地中的应用,AIGC相关的创业及项目如雨后春笋般涌现.然而,AIGC的广泛应用也带来了一系列的法律 ...
- WPF-实现屏幕截图(一)
源码路径:https://gitee.com/LiuShuiRuoBing/wpf_screen_cut 实现功能 实现基本的截屏窗体 鼠标随意选择截图区域 鼠标抬起时弹出按钮区 快捷键Ctrl+Al ...
- 使用阿里云ECS和RDS搭建个人博客
一.ECS实例配置 1.重置云服务器ECS密码 前往ECS控制台,点击实例,找到刚才开通的ECS实例(找不到的话就看一下上方的地区是否是你的服务器的地域),点击右侧操作栏中的三个点,找到重置实例密码, ...
- vue 脚手架文件结构及加载过程浅谈
1. 初始化脚手架 1.1 全局安装 @vue/cli npm install -g @vue/cli 1.2 切换到创建项目的目录,执行 vue create projectname 1.3 选 ...
- 超全技术学习资料PDF分享
技术学习资料分享,目前共20G,持续更新... Java学习资料: 大数据Hadoop: 这里不一一截图了,资源持续更新中. 关注下面公众号进行下载.
- 【matplotlib基础】--图例
Matplotlib 中的图例是帮助观察者理解图像数据的重要工具.图例通常包含在图像中,用于解释不同的颜色.形状.标签和其他元素. 1. 主要参数 当不设置图例的参数时,默认的图例是这样的. impo ...