当前位置: 首页 > news >正文

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开发数据库应用时&#xff0c;读写分离是一种常见的优化策略。读写分离将读操作和写操作分别分配给不同的数据库实例&#xff0c;以提高系统的吞吐量和性能。 读写分离实现主要是通过动态数据源功能实现的&#xff0c;动态数据源是一种通过…...

企业如何利用好用户画像对客户进行精准营销?提高营销转化?

随着市场竞争的加剧&#xff0c;企业对于客户的需求和行为越来越关注&#xff0c;如何利用好用户画像对客户进行精准营销&#xff0c;提高营销转化&#xff0c;成为企业关注的焦点。 一、了解用户需求和行为 首先&#xff0c;企业需要了解客户的需求和行为&#xff0c;包括客户…...

acwing算法基础之搜索与图论--匈牙利算法求二分图的最大匹配数

目录 1 基础知识2 模板3 工程化 1 基础知识 二分图中的最大匹配数&#xff1a;从二分图中选择一些边&#xff08;这些边连接集合A和集合B&#xff0c;集合A中结点数目为n1&#xff0c;集合B中结点数目为n2&#xff09;&#xff0c;设为集合S&#xff0c;其中任意两条边不共用一…...

优化重复冗余代码的8种方式

文章目录 前言1、抽取公用方法2、抽工具类3、反射4、泛型5、继承与多态6、使用设计模式7、自定义注解(或者说AOP面向切面)8、函数式接口和Lambda表达式 前言 日常开发中&#xff0c;我们经常会遇到一些重复代码。大家都知道重复代码不好&#xff0c;它主要有这些缺点&#xff…...

DVWA - 3

文章目录 XSS&#xff08;Dom&#xff09;lowmediumhighimpossible XSS&#xff08;Dom&#xff09; XSS 主要基于JavaScript语言进行恶意攻击&#xff0c;常用于窃取 cookie&#xff0c;越权操作&#xff0c;传播病毒等。DOM全称为Document Object Model&#xff0c;即文档对…...

android studio离线tips

由于种种原因&#xff08;你懂的&#xff0c;导致我们使用android studio会有很多坑&#xff0c;这里记录一下遇到的问题以及解决方案 环境问题 无法下载gradle 因为android studio采用gradle作为构建工具&#xff0c;国内gradle没有镜像下载非常慢&#xff0c;并且大概率失…...

JWT概念(登录代码实现)

JWT (JSON Web Token)是一种开放标准&#xff0c;用于在网络应用程序之间安全地传输信息。JWT是一种基于JSON的轻量级令牌&#xff0c;包含了一些声明和签名&#xff0c;可以用于认证和授权。 JWT主要由三部分组成&#xff1a;头部、载荷和签名。 头部包含了使用的算法和类型…...

如何在 Windows 10/11 上高质量地将 WAV 转换为 MP3

WAV 几乎完全准确地存储了录音硬件所听到的内容&#xff0c;这使得它变得很大并占用了更多的存储空间。因此&#xff0c;WAV 格式在作为电子邮件附件发送、保存在便携式音频播放器上、通过蓝牙或互联网从一台设备传输到另一台设备等时可能无法正常工作。 如果您遇到 WAV 问题&…...

详解FreeRTOS:FreeRTOS消息队列(高级篇—1)

目录 1、队列简介 2、队列的运行机制 3、队列的阻塞机制 4、队列结构体 5、创建队列...

Vue3 + ts+ elementUi 实现后台数据渲染到下拉框选项中,滑动加载更多数据效果

前言 功能需求&#xff1a;下拉框中分页加载后端接口返回的人员数据&#xff0c;实现滑动加载更多数据效果&#xff0c;并且可以手动搜索定位数据&#xff0c;此项目使用Vue3 ts elementUi 实现 实现 把此分页滑动加载数据功能封装成vue中的hooks&#xff0c;文件命名为use…...

Elasticsearch 索引库操作与 Rest API 使用详解

1. 引入 Elasticsearch 依赖 在开始之前&#xff0c;确保你的 Maven 或 Gradle 项目中已经引入了 Elasticsearch 的 Java 客户端库。你可以使用以下 Maven 依赖&#xff1a; xml <dependency> <groupId>org.elasticsearch.client</groupId> <ar…...

线性代数(四)| 解方程 齐次性 非齐次性 扩充问题

文章目录 1 方程解的个数2 解方程步骤2.1 齐次性方程组2.2 非齐次方程组 3 一些扩充问题 系数矩阵 增广矩阵 A m n X B A_{mn}XB Amn​XB 1 方程解的个数 m 代表有m个方程 n代表有n个未知数 系数矩阵的秩与增广矩阵的秩不同 无解 若相同 &#xff0c;如系数矩阵的秩和未知…...

快乐数问题

编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。 如果这个过程 结果为 1&#xff…...

8 历史服务器配置

为了查看程序的历史运行情况&#xff0c;需要配置一下历史服务器 1、配置mapred-site.xml vim mapred-site.xml在该文件里面增加如下配置 //原先的配置不用删除 <!-- 历史服务器端地址 --> <property><name>mapreduce.jobhistory.address</name><…...

读书笔记:《精益数据分析》

《精益数据分析 . Lean Analytics Use Data to Build a Better Startup Faster》 加 . 阿利斯泰尔 . 克罗尔 本杰明 . 尤科维奇 著&#xff0c;韩知白 王鹤达 译 2023.7.27 ~ 2023.11.4 本以为是本纯数学的、介绍公式的数据分析用法的书&#xff0c;结果是&#xff1a;…...

酷柚易汛ERP- 组装单与拆卸单操作

1、功能介绍 组装单用来处理企业组装等加工业务&#xff0c;拆卸单用来处理企业拆卸等加工业务&#xff0c;支持一对多的产品加工业务。 2、主要操作 2.1 新增组装单 打开【仓库】-【组装单】新增组装单。 录入组合件与子件&#xff0c;单据审核后&#xff0c;系统根据存货…...

yolov8训练

介绍 训练深度学习模型包括向其提供数据并调整其参数&#xff0c;以便其能够做出准确的预测。Ultralytics YOLOv8中的训练模式旨在充分利用现代硬件功能&#xff0c;对目标检测模型进行有效和高效的训练。本指南旨在涵盖使用YOLOv8强大的一组功能开始训练自己的模型所需的所有细…...

抖音短视频账号矩阵系统、短视频矩阵源码+无人直播源码开发可打包

抖音短视频账号矩阵系统、短视频矩阵源码无人直播源码开发可打包 矩阵系统源码主要有三种框架&#xff1a;Spring、Struts和Hibernate。Spring框架是一个全栈式的Java应用程序开发框架&#xff0c;提供了IOC容器、AOP、事务管理等功能。Struts框架是一个MVC架构的Web应用程序框…...

NI和EttusResearchUSRP设备之间的区别

NI和EttusResearchUSRP设备之间的区别 概述 USRP&#xff08;通用软件无线电外设&#xff09;设备是业界领先的商软件定义无线电&#xff08;SDR&#xff09;。全球数以千计的工程师使用USRPSDR来快速设计、原型设计和部署无线系统。它们以两个不同的品牌进行营销和销售&…...

WPF UI样式介绍

WPF&#xff08;Windows Presentation Foundation&#xff09;是微软的一个用于创建桌面客户端应用程序的UI框架。WPF使用XAML&#xff08;可扩展应用程序标记语言&#xff09;作为其界面设计语言&#xff0c;这使得开发者能够以声明性方式定义UI元素和布局。 在WPF中&#xf…...

AMD Ryzen硬件调试指南:5分钟掌握SMUDebugTool核心功能

AMD Ryzen硬件调试指南&#xff1a;5分钟掌握SMUDebugTool核心功能 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://g…...

字符串拆分合并

贪心算法,最长限制。 import reclass TextFilter:def __init__(self):# 字符映射规则self.char_map = {# 省略号 → 停顿…: ,, ...: ,,: ,,# 破折号 → 停顿——: ,, —: ,,# 书名号 → 直接删除《: , 》: , 〈: , 〉: ,# 其他特殊符号 → 删除*: , /: , #: ,}# 需要保留的…...

闽北哥-一个人最顶级的能力:复归于朴

一个人最顶级的能力 ——复归于朴**“道家说‘复归于朴’&#xff0c; 儒家说‘赤子之心’&#xff0c; 佛家说‘本自具足’&#xff0c; 鬼谷子说‘知世故而不世故’—— 他们都指向同一种状态&#xff1a; 在红尘中&#xff0c;活出婴儿般的清澈。”&#x1f33f; 这不是天真…...

下篇:那个听声辨位的侦探后来破了大案——AI中隐马尔可夫模型的类型与作用,以及它为什么还在被使用

我们说了隐马尔可夫模型是一个“只能听声、不能见人”的侦探&#xff0c;靠着一串声音推理出隔壁房间在发生什么。现在的问题是&#xff1a;它到底有哪些具体的“形态”&#xff1f;不同类型的隐马尔可夫模型分别擅长什么&#xff1f;这个“老古董”在今天还能干什么&#xff1…...

Tomcat中间件能够提供的能力

Tomcat 中间件能够提供的能力主要包括以下几个方面&#xff1a;‌运行 Java Web 应用程序‌&#xff1a;Tomcat 是一个开源的 Web 应用服务器&#xff0c;主要用于运行基于 Java 的 Web 应用&#xff0c;包括 Servlet、JSP 和 JavaBean 等组件。‌提供 Servlet 容器功能‌&…...

ExplorerBlurMica终极指南:让你的Windows文件资源管理器焕然一新

ExplorerBlurMica终极指南&#xff1a;让你的Windows文件资源管理器焕然一新 【免费下载链接】ExplorerBlurMica Add background Blur effect or Acrylic (Mica for win11) effect to explorer for win10 and win11 项目地址: https://gitcode.com/gh_mirrors/ex/ExplorerBlu…...

终极指南:深度实战OpenCore Legacy Patcher让老旧Mac重获新生

终极指南&#xff1a;深度实战OpenCore Legacy Patcher让老旧Mac重获新生 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher OpenCore Legacy Patcher是一款革命…...

ofa_image-caption_coco_distilled_en快速部署教程:7860端口WebUI调用全流程详解

ofa_image-caption_coco_distilled_en快速部署教程&#xff1a;7860端口WebUI调用全流程详解 本文介绍如何快速部署和使用ofa_image-caption_coco_distilled_en模型&#xff0c;这是一个专门用于为图片生成英文描述的AI系统。通过简单的Web界面&#xff0c;任何人都能轻松上传图…...

从ThreadLocal到TransmittableThreadLocal:手把手解决线程池上下文传递难题

从ThreadLocal到TransmittableThreadLocal&#xff1a;线程池上下文传递的终极解决方案 在分布式系统和微服务架构盛行的今天&#xff0c;异步编程已成为Java开发者日常工作中不可或缺的一部分。无论是处理高并发请求、优化系统性能&#xff0c;还是实现复杂的业务流程&#xf…...

突破工厂建设瓶颈:FactoryBluePrints蓝图库带来的自动化生产革命

突破工厂建设瓶颈&#xff1a;FactoryBluePrints蓝图库带来的自动化生产革命 【免费下载链接】FactoryBluePrints 游戏戴森球计划的**工厂**蓝图仓库 项目地址: https://gitcode.com/GitHub_Trending/fa/FactoryBluePrints FactoryBluePrints是戴森球计划的开源工厂蓝图…...