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;@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) {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 {@Autowiredprivate TestDao testDao;@SlaveDataSourcepublic 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 {@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);}}
}
作者:京东健康 苏曼
来源:京东云开发者社区 转发请注明来源
相关文章:
SpringBoot 项目优雅实现读写分离 | 京东云技术团队
一、读写分离介绍 当使用Spring Boot开发数据库应用时,读写分离是一种常见的优化策略。读写分离将读操作和写操作分别分配给不同的数据库实例,以提高系统的吞吐量和性能。 读写分离实现主要是通过动态数据源功能实现的,动态数据源是一种通过…...
企业如何利用好用户画像对客户进行精准营销?提高营销转化?
随着市场竞争的加剧,企业对于客户的需求和行为越来越关注,如何利用好用户画像对客户进行精准营销,提高营销转化,成为企业关注的焦点。 一、了解用户需求和行为 首先,企业需要了解客户的需求和行为,包括客户…...
acwing算法基础之搜索与图论--匈牙利算法求二分图的最大匹配数
目录 1 基础知识2 模板3 工程化 1 基础知识 二分图中的最大匹配数:从二分图中选择一些边(这些边连接集合A和集合B,集合A中结点数目为n1,集合B中结点数目为n2),设为集合S,其中任意两条边不共用一…...
优化重复冗余代码的8种方式
文章目录 前言1、抽取公用方法2、抽工具类3、反射4、泛型5、继承与多态6、使用设计模式7、自定义注解(或者说AOP面向切面)8、函数式接口和Lambda表达式 前言 日常开发中,我们经常会遇到一些重复代码。大家都知道重复代码不好,它主要有这些缺点ÿ…...
DVWA - 3
文章目录 XSS(Dom)lowmediumhighimpossible XSS(Dom) XSS 主要基于JavaScript语言进行恶意攻击,常用于窃取 cookie,越权操作,传播病毒等。DOM全称为Document Object Model,即文档对…...
android studio离线tips
由于种种原因(你懂的,导致我们使用android studio会有很多坑,这里记录一下遇到的问题以及解决方案 环境问题 无法下载gradle 因为android studio采用gradle作为构建工具,国内gradle没有镜像下载非常慢,并且大概率失…...
JWT概念(登录代码实现)
JWT (JSON Web Token)是一种开放标准,用于在网络应用程序之间安全地传输信息。JWT是一种基于JSON的轻量级令牌,包含了一些声明和签名,可以用于认证和授权。 JWT主要由三部分组成:头部、载荷和签名。 头部包含了使用的算法和类型…...
如何在 Windows 10/11 上高质量地将 WAV 转换为 MP3
WAV 几乎完全准确地存储了录音硬件所听到的内容,这使得它变得很大并占用了更多的存储空间。因此,WAV 格式在作为电子邮件附件发送、保存在便携式音频播放器上、通过蓝牙或互联网从一台设备传输到另一台设备等时可能无法正常工作。 如果您遇到 WAV 问题&…...
详解FreeRTOS:FreeRTOS消息队列(高级篇—1)
目录 1、队列简介 2、队列的运行机制 3、队列的阻塞机制 4、队列结构体 5、创建队列...
Vue3 + ts+ elementUi 实现后台数据渲染到下拉框选项中,滑动加载更多数据效果
前言 功能需求:下拉框中分页加载后端接口返回的人员数据,实现滑动加载更多数据效果,并且可以手动搜索定位数据,此项目使用Vue3 ts elementUi 实现 实现 把此分页滑动加载数据功能封装成vue中的hooks,文件命名为use…...
Elasticsearch 索引库操作与 Rest API 使用详解
1. 引入 Elasticsearch 依赖 在开始之前,确保你的 Maven 或 Gradle 项目中已经引入了 Elasticsearch 的 Java 客户端库。你可以使用以下 Maven 依赖: xml <dependency> <groupId>org.elasticsearch.client</groupId> <ar…...
线性代数(四)| 解方程 齐次性 非齐次性 扩充问题
文章目录 1 方程解的个数2 解方程步骤2.1 齐次性方程组2.2 非齐次方程组 3 一些扩充问题 系数矩阵 增广矩阵 A m n X B A_{mn}XB AmnXB 1 方程解的个数 m 代表有m个方程 n代表有n个未知数 系数矩阵的秩与增广矩阵的秩不同 无解 若相同 ,如系数矩阵的秩和未知…...
快乐数问题
编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为: 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。 如果这个过程 结果为 1ÿ…...
8 历史服务器配置
为了查看程序的历史运行情况,需要配置一下历史服务器 1、配置mapred-site.xml vim mapred-site.xml在该文件里面增加如下配置 //原先的配置不用删除 <!-- 历史服务器端地址 --> <property><name>mapreduce.jobhistory.address</name><…...
读书笔记:《精益数据分析》
《精益数据分析 . Lean Analytics Use Data to Build a Better Startup Faster》 加 . 阿利斯泰尔 . 克罗尔 本杰明 . 尤科维奇 著,韩知白 王鹤达 译 2023.7.27 ~ 2023.11.4 本以为是本纯数学的、介绍公式的数据分析用法的书,结果是:…...
酷柚易汛ERP- 组装单与拆卸单操作
1、功能介绍 组装单用来处理企业组装等加工业务,拆卸单用来处理企业拆卸等加工业务,支持一对多的产品加工业务。 2、主要操作 2.1 新增组装单 打开【仓库】-【组装单】新增组装单。 录入组合件与子件,单据审核后,系统根据存货…...
yolov8训练
介绍 训练深度学习模型包括向其提供数据并调整其参数,以便其能够做出准确的预测。Ultralytics YOLOv8中的训练模式旨在充分利用现代硬件功能,对目标检测模型进行有效和高效的训练。本指南旨在涵盖使用YOLOv8强大的一组功能开始训练自己的模型所需的所有细…...
抖音短视频账号矩阵系统、短视频矩阵源码+无人直播源码开发可打包
抖音短视频账号矩阵系统、短视频矩阵源码无人直播源码开发可打包 矩阵系统源码主要有三种框架:Spring、Struts和Hibernate。Spring框架是一个全栈式的Java应用程序开发框架,提供了IOC容器、AOP、事务管理等功能。Struts框架是一个MVC架构的Web应用程序框…...
NI和EttusResearchUSRP设备之间的区别
NI和EttusResearchUSRP设备之间的区别 概述 USRP(通用软件无线电外设)设备是业界领先的商软件定义无线电(SDR)。全球数以千计的工程师使用USRPSDR来快速设计、原型设计和部署无线系统。它们以两个不同的品牌进行营销和销售&…...
WPF UI样式介绍
WPF(Windows Presentation Foundation)是微软的一个用于创建桌面客户端应用程序的UI框架。WPF使用XAML(可扩展应用程序标记语言)作为其界面设计语言,这使得开发者能够以声明性方式定义UI元素和布局。 在WPF中…...
Veo 2提示词效能跃迁实战(工业级Prompt链构建全图谱)
更多请点击: https://codechina.net 第一章:Veo 2提示词编写的核心范式演进 Veo 2作为新一代视频生成模型,其提示词(prompt)工程已从早期的“关键词堆叠”转向结构化、语义分层与意图对齐的复合范式。这一演进并非简…...
Kerberos身份认证原理与实战排错指南
1. 为什么今天还要花时间搞懂 Kerberos?——一个被低估的“老协议”正在悄悄支撑着你的日常你每天登录公司内网查邮件、访问财务系统提交报销、用 Jenkins 构建代码、甚至在 Windows 域环境中打开一台同事的共享文件夹……这些看似顺滑的操作背后,大概率…...
UOS系统下WPS卸载不干净?手把手教你用命令行精准清理(附dpkg/apt组合拳)
UOS系统下WPS卸载不干净?手把手教你用命令行精准清理 在UOS系统日常使用中,WPS Office作为常用办公软件,有时因版本更新或功能调整需要彻底卸载。但不少用户发现,通过图形界面或简单命令卸载后,系统中仍残留配置文件、…...
Agent开发面试通关攻略:吃透稳拿offer
阅读前置:2026年当下最卷也最缺人的AI岗位,一定是AI Agent开发。最近刷遍CSDN、牛客、力扣最新面经,发现一个非常明显的招聘趋势:普通大模型微调岗位饱和内卷,而AI Agent开发岗位人才严重缺口,薪资更高、竞…...
Redis分布式锁进阶第二十篇
一、本篇前置衔接 第二十篇我们完成了全系列终局复盘,整理了故障排查SOP与企业级落地铁律。常规单资源锁、热点分片锁、隔离锁全部讲透,但真实复杂业务永远不是单一资源:下单要扣库存、扣优惠券、扣积分、冻结余额,多资源并行争抢…...
终极艾尔登法环帧率解锁指南:轻松突破60FPS限制
终极艾尔登法环帧率解锁指南:轻松突破60FPS限制 【免费下载链接】EldenRingFpsUnlockAndMore A small utility to remove frame rate limit, change FOV, add widescreen support and more for Elden Ring 项目地址: https://gitcode.com/gh_mirrors/el/EldenRing…...
深度解析网络设备权限管理工具:中兴光猫工厂模式与Telnet服务完整指南
深度解析网络设备权限管理工具:中兴光猫工厂模式与Telnet服务完整指南 【免费下载链接】zteOnu A tool that can open ZTE onu device factory mode 项目地址: https://gitcode.com/gh_mirrors/zt/zteOnu 在当今网络设备管理领域,获取设备完整控制…...
Jupyter Notebook里跑argparse脚本总报错?一个空列表参数搞定ipykernel_launcher.py error
Jupyter Notebook中argparse报错的终极解决方案:空列表参数实战解析在数据科学和机器学习的工作流中,Jupyter Notebook因其交互式特性成为众多研究者的首选工具。然而,当我们尝试在Notebook中运行那些原本为命令行设计的Python脚本时…...
手把手教你用Mind+和Blynk,让手机轻松遥控掌控板(含自建服务器避坑指南)
从零搭建物联网控制平台:Mind与Blynk深度整合实战 当你第一次尝试用手机控制硬件设备时,那种"隔空取物"的奇妙感总会让人兴奋不已。想象一下,躺在沙发上就能调节书桌上的智能台灯亮度,或者在外出时随时查看家中的温湿度…...
基于MAX78000的边缘AI语音识别:从模型训练到嵌入式部署实战
1. 项目概述与核心思路最近在捣鼓一个挺有意思的小项目,我把它叫做“声控转向控制器”。简单来说,这玩意儿能听懂你说的几个特定单词,比如“左转”、“右转”、“前进”、“后退”,然后控制对应的LED灯亮起。你可能会想࿰…...
