开发经验总结: 读写分离简单实现
背景
使用mysql的代理中间件,某些接口如果主从同步延迟大,容易出现逻辑问题。所以程序中没有直接使用这个中间件。
依赖程序逻辑,如果有一些接口可以走读库,需要一个可以显示指定读库的方式来连接读库,降低主库的压力。
所以需要一个能配置多个数据源,通过注解或者代码方式设置读还是写数据源的方法。
实现方式
使用MybatisPlus的多数据源切换的能力。
github地址: https://github.com/baomidou/dynamic-datasource
已经封装好了。直接使用就行。
介绍
dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。
其支持 Jdk 1.7+, SpringBoot 1.5.x 2.x.x 3.x.x。 JPA用户不建议使用,JPA自带事务,无法连续切库。
特性:
- 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
- 支持数据库敏感配置信息 加密(可自定义) ENC()。
- 支持每个数据库独立初始化表结构schema和数据库database。
- 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
- 支持 自定义注解 ,需继承DS(3.2.0+)。
- 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
- 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
- 提供 自定义数据源来源 方案(如全从数据库加载)。
- 提供项目启动后 动态增加移除数据源 方案。
- 提供Mybatis环境下的 纯读写分离 方案。
- 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
- 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
- 提供 基于seata的分布式事务方案 。
- 提供 本地多数据源事务方案。
我们需要的是纯读写分离 方案,它已经内置了。
功能范围:
- 本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD。
- 配置文件所有以下划线 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。
- 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
- 默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。
- 方法上的注解优先于类上注解。
- DS支持继承抽象类上的DS,暂不支持继承接口上的DS。
使用步骤
1.引入依赖。
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>${version}</version>
</dependency>
2.配置数据源
spring:datasource:dynamic:primary: master #设置默认的数据源或者数据源组,默认值即为masterstrict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源datasource:master:url: jdbc:mysql://xx.xx.xx.xx:3306/dynamicusername: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置slave_1:url: jdbc:mysql://xx.xx.xx.xx:3307/dynamicusername: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driverslave_2:url: ENC(xxxxx) # 内置加密,使用请查看详细文档username: ENC(xxxxx)password: ENC(xxxxx)driver-class-name: com.mysql.jdbc.Driver#......省略#以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
3.使用注解切换数据源。
@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
@Service
@DS("slave")
public class UserServiceImpl implements UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;public List selectAll() {return jdbcTemplate.queryForList("select * from user");}@Override@DS("slave_1")public List selectByCondition() {return jdbcTemplate.queryForList("select * from user where age >10");}
}
业务集成
我们的程序使用的经典的三层结构,即MVC .
看到我们的框架是已经集成了dynamicSource的。如果没有,则加一下依赖即可。
以crm服务为例子。写了一些测试代码,进行验证测试。
package com.pig4cloud.pigx.crm.controller;import com.baomidou.dynamic.datasource.annotation.DS;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.security.annotation.Inner;
import com.pig4cloud.pigx.crm.mapper.AiAgentMapper;
import com.pig4cloud.pigx.crm.service.AiAgentService;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;@RestController
@RequiredArgsConstructor
@Tag(description = "test read write", name = "测试读写分离")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
@Valid
@Slf4j
public class TestDsController {private final AiAgentMapper aiAgentMapper;private final AiAgentService aiAgentService;@GetMapping("/testD")@ResponseBody//@Inner(value = false)public R testD() {return R.ok(aiAgentMapper.selectList(null));}@GetMapping("/testRead")@ResponseBody//@Inner(value = false)@DS("read")public R testRead() {return R.ok(aiAgentMapper.selectList(null));}@GetMapping("/testMaster")@ResponseBody//@Inner(value = false)@DS("master")public R testMaster() {return R.ok(aiAgentMapper.selectList(null));}@GetMapping("/testHe")@ResponseBody//@Inner(value = false)@DS("master")public R testHe() {return R.ok(aiAgentService.listTest());}@GetMapping("/testHe2")@ResponseBody//@Inner(value = false)@DS("read")public R testHe2() {return R.ok(aiAgentService.listTest2());}@GetMapping(value = "/testHe3",produces = "application/json")@ResponseBody//@Inner(value = false)public R testHe3() {Map<String, Object> dataMap = new HashMap<>();dataMap.put("read", aiAgentService.listTest());dataMap.put("master", aiAgentService.listTest2());return R.ok(dataMap);}}
对应的Service实现代码:
@Service
@Slf4j
@RequiredArgsConstructor
public class AiAgentServiceImpl extends ServiceImpl<AiAgentMapper, AiAgentEntity> implements AiAgentService {@Override@DS("read")public List<AiAgentEntity> listTest() {return baseMapper.selectList(null);}@Override@DS("master")public List<AiAgentEntity> listTest2() {return baseMapper.selectList(null);}
}
测试用例和结果:
测试接口和场景 | 测试说明 | 预期结果 | 测试结果 |
---|---|---|---|
/testD | 不加任何注解 | 走主库 | - [x] 走主库 ![]![]() |
/testRead | 添加了读库注解 | 走读库 | - [x] 走读库![]() |
/testMaster | 添加了主库注解 | 走主库 | - [x] 走主库![]() |
/testHe | 上层加主库注解,下层加读库注解 | 走读库 | - [x] 走读库![]() |
/testHe2 | 上层加读库注解,下层加写库注解 | 走主库 | - [x] 走主库 ![]() |
/testHe3 | 在中层的方法分别加读库和写库注解 | 分别走读库和主库 | - [x] 分别走读库和读库![]() |
目前需要增加读写分离的服务。
服务 | 增加读写分离的原因 | 是否完成增加 |
---|---|---|
crm | 后台列表查询页面, openAPI的读接口,直接走读库; | - [x] |
后台列表查询页面, openAPI的读接口,直接走读库; | - [ ] | |
phone | 后台列表查询页面, openAPI的读接口,直接走读库; | - [ ] |
ssm | 后台列表查询页面, openAPI的读接口,直接走读库; | - [ ] |
upms | 后台列表查询页面, openAPI的读接口,直接走读库; | - [ ] |
源码研究
3.6.0版本。基于这个进行分析。
1 自动装配类
配置属性:DynamicDataSourceProperties
属性 | 说明 |
---|---|
primary | 默认的库 |
strict | 严格检查 |
p6spy | 日志输出 |
seata | 是否开启seata |
lazy | 是否懒加载数据源 |
setaMode | AT模式 |
publicKey | 加密key |
datasource | 数据源,可以单独配置; |
strategy | 负载均衡策略 |
druid | 数据源 |
hikari | 数据源 |
beecp | 数据源 |
dbcp2 | 数据源 |
aop | aop配置,有默认值 |
DataSourceProperty
属性说明 | 说明 |
---|---|
poolName | 连接池名称 |
type | 连接类型 |
driverClassName | 驱动名称 |
url | 连接url |
username | 用户名 |
password | 密码 |
jndiName | jndi中的名称 |
init | 初始化配置 |
p6spy | 日志输出 |
seata | 是否开启seata |
lazy | 是否懒加载数据源 |
publicKey | 加密key |
druid | 数据源 |
hikari | 数据源 |
beecp | 数据源 |
dbcp2 | 数据源 |
DatasourceInitProperties
属性名 | 说明 |
---|---|
schema | 建表脚本 |
data | 数据脚本 |
continueOnError | 错误是否继续 |
separator | 分隔符 |
2 自动装配逻辑
基于切面实现。
小结
集成使用了mybatisPlus自带的多数据源实现了读写分离,并探究了源码实现过程。
详细过程可以看我的视频号。
原创不易,关注诚可贵,转发价更高!转载请注明出处,让我们互通有无,共同进步,欢迎沟通交流。
相关文章:

开发经验总结: 读写分离简单实现
背景 使用mysql的代理中间件,某些接口如果主从同步延迟大,容易出现逻辑问题。所以程序中没有直接使用这个中间件。 依赖程序逻辑,如果有一些接口可以走读库,需要一个可以显示指定读库的方式来连接读库,降低主库的压力…...

MySQL(面试题 - 同类型归纳面试题)
目录 一、MySQL 数据类型 1. 数据库存储日期格式时,如何考虑时区转换问题? 2. Blob和text有什么区别? 3. mysql里记录货币用什么字段类型比较好? 4. MySQL如何获取当前日期? 5. 你们数据库是否支持emoji表情存储&…...

【C++ Primer Plus习题】17.7
问题: 解答: #include <iostream> #include <vector> #include <string> #include <fstream> #include <algorithm>using namespace std;const int LIMIT 50;void ShowStr(const string& str); void GetStrs(ifstream& fin, vector<…...

vue3(整合版)
创建第一个vue项目 1.安装node.js cmd输入node查看是否安装成功 2.vscode开启一个终端,配置淘宝镜像 # 修改为淘宝镜像源 npm config set registry https://registry.npmmirror.com 输入如下命令创建第一个Vue项目 3.下载依赖,启动项目 访问5173端口 …...

复制他人 CSDN 文章到自己的博客
文章目录 0.前言步骤 0.前言 在复制别人文章发布时,记得表明转载哦 步骤 在需要复制的csdn 文章页面,打开浏览器开发者工具(F12)Ctrl F 查找"article_content"标签头 右键“Copy”->“Copy element”新建一个 tx…...
【算法——二分查找】
理论基础: 程序员面试经典题,二分搜索一个区间,区间查找 (LeetCode 34)_哔哩哔哩_bilibili 手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili 这个是红蓝法,很牛…...

Cisco Packet Tracer的安装加汉化
这个工具学计算机网络的同学会用到 1.下载安装 网盘链接:https://pan.baidu.com/s/1CmnxAD9MkCtE7pc8Tjw0IA 提取码:frkb 点击第一个进行安装,按步骤来即可。 2.汉化 (1)复制chinese.ptl文件 (2&…...
MMain函数定义为WinMain函数看port1632.h和pwin32.h文件
编译win2k3的源代码的时候有时候看到MMain函数 ..//public/sdk/inc/port1632.h #if defined(WIN16) /* ---------------- Maps to windows 3.0 and 3.1 16-bit APIs ----------------*/ #include "ptypes16.h" #include "pwin16.h" #include "plan16.…...

单词搜索问题(涉及递归等)
目录 一题目: 二思路解释: 三解答代码: 一题目: newcode题目链接: 单词搜索_牛客题霸_牛客网 二思路解释: 思路:个人理解是找到word中的第一个元素,然后去递归的上下左右查找&am…...

Redis的一些通用指令
首先我们需要先连接客户端服务器,此时我们需要通过redis-cli和redis服务器进行交互,输入ping来确保通路的流畅 (一)get和set redis中最核心的两个命令就是get和set,get就是根据key来取出对应value,set就是把…...

C++中vector类的使用
目录 1.vector类常用接口说明 1.1默认成员函数 1.1.1构造函数(constructor) 1.1.2 赋值运算符重载(operator()) 2. vector对象的访问及遍历操作(Iterators and Element access) 3.vector类对象的容量操作(Capacity) 4. vector类对象的修改及相关操作(Modifiers and Stri…...
cmaklist流程控制——调试及发布
cmaklist流程控制 目前只会配置-编译调试-打包发布,并且不会workflow控制 后续学习配置-编译调试-测试-打包发布,workflow控制,理解整个流程,目前对流程控制理解也不够。 1.CMake Presets 先于Cmakelist文件,指导项…...
制作一个能对话能跳舞的otto机器人
OTTO机器人是一个开源外壳,硬件和软件的桌面机器人项目,非常适合新手研究和拓展。记住,他是一个能移动有表情能声音的机器人。 b站有很多演示和组装的视频,我就不多说了,照着做就好,因为硬件我也是刚入门&…...

git配置SSH
1 打开cmd窗口 2 在窗口中输入如下命令: 配置用户名: git config --global user.name “gyk” 配置邮箱: git config --global user.email “247929163qq.com” 继续在Git命令窗口中输入如下命令,即可生成SSH公钥和私钥 ss…...
mozilla/pdf.js view.html加载指定页码
mozilla/pdf.js view.html加载指定页码 在Mozilla’s PDF.js中,如果你想要在viewer.html加载时直接跳转到指定的页码,你可以通过修改URL来实现。 PDF.js使用查询参数来处理URL,其中page参数用于指定页码。你可以通过修改URL的查询字符串来设…...
Qt之QFuture理解
结构 #mermaid-svg-J9J683RG8QjtEqoM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-J9J683RG8QjtEqoM .error-icon{fill:#552222;}#mermaid-svg-J9J683RG8QjtEqoM .error-text{fill:#552222;stroke:#552222;}#merm…...
求二叉树的高度(递归和非递归)
假设二叉树采用二叉链表存储结构,设计一个算法求二叉树的高度。 递归: int getTreeHight(BiTree T){if(TNULL){return 0;}else {int lh getTreeHight(T->lchild);int rh getTreeHight(T->rchild);return (lh>rh?lh:rh)1;}}时间复杂度O(n)&a…...

Java查找算法——(四)分块查找(完整详解,附有代码+案例)
文章目录 分块查找1.1普通分块查找 分块查找 1.1普通分块查找 分块原则: 块内无序,块间有序:前一块中的最大数据,小于后一块中所有的数据,块与块之间不能有数据重复的交集。块的数量一般等于数字个数开根号 核心思路ÿ…...

进制数知识(2)—— 浮点数在内存中的存储 和 易混淆的二进制知识总结
目录 1. 浮点数在内存中的存储 1.1 浮点数的大V表示法 1.2 浮点数的存储格式 1.3 浮点数的存入规则 1.4 浮点数的读取规则 1.5 补充:移码与掩码 1.6 题目解析 2. 易错的二进制知识 2.0 符号位到底会不会参与运算? 2.0.1 存储前的编码变化运算 …...
类似QQ聊天功能的Java程序
实现一个类似QQ聊天功能的Java程序需要考虑以下几个关键点: 用户界面:用于展示消息和输入消息。网络通信:用于客户端之间的信息传输。用户管理:用于管理用户的登录、注册和状态。消息存储:用于存储聊天记录。 这里提…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...

医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor
1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...