踩坑,发现一个ShardingJdbc读写分离的BUG
ShardingJdbc 怎么处理写完数据立即读的情况的呢?
写在前面
我本地使用了两个库来做写库(ds_0_master)和读库(ds_0_salve),两个库并没有配置主从。
下面我就使用库里的 city 表做实验。主库的 city 表没有数据,而从库的 city 表就一条数据
我们讨论 4 种情况:
- 常规写完读
- 在一个 service 里面调用另一个 service2 进行读
- 在一个 service 里面新开一个线程去调用 service2
- 在一个 service 里面调用 service2,但 service2 是新开的事务
先直接上实验结果:
1. 常规写完读
@Service
public class CityService {
@Autowired
private CityRepository cityRepository;
@Autowired
private CityService2 cityService2;
@Transactional(rollbackFor = Exception.class)
public void test(){
City city=new City();
city.setName("眉山");
city.setProvince("四川");
cityRepository.save(city);
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.out.println("cityService:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
});
}
}
打印结果:
实验分析:
我们对 city 表进行插入后,紧接着对 city 表进行了查询,查出的内容是我们刚刚插入的内容。说明查询操作没有走读库,而是走了主库。
2. 在一个 service 里面调用另一个 service
代码如下:
@Transactional(rollbackFor = Exception.class)
public void test(){
City city=new City();
city.setName("眉山");
city.setProvince("四川");
cityRepository.save(city);
//调用其他service
cityService2.test();
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.out.println("cityService:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
});
}
}
service2 的代码:
public void test(){
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
});
}
打印结果:
实验分析:在 service 方法里调用了其他 service,其他 service 也会受到影响。service2 也是走的主库。
3. 新开一个线程去调用 service2
代码如下:
@Service
public class CityService {
@Autowired
private CityRepository cityRepository;
@Autowired
private CityService2 cityService2;
@Transactional(rollbackFor = Exception.class)
public void test(){
City city=new City();
city.setName("眉山");
city.setProvince("四川");
cityRepository.save(city);
new Thread(()->{cityService2.test();}).start();
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.out.println("cityService:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
});
}
}
@Service
public class CityService2 {
@Autowired
private CityRepository cityRepository;
public void test(){
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
});
}
}
打印结果:
实验分析:
我们新开了线程对 city 表进行查询,此次查询读的是从库。新开的线程会走从库,我猜想是新开的线程它认为是没有写入/修改操作,所以走了从库。
我又改动了 service2,加了一段写入操作。代码如下:
public void test(){
City city=new City();
city.setName("成都");
city.setProvince("四川");
cityRepository.save(city);
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
});
}
再次执行,结果如下:
和预想的不一样,依旧是走的从库。
4. service2 新开一个事务执行
我们调整 service2 的事务传播行为级别。代码如下:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test(){
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
});
}
REQUIRES_NEW 的含义是:
强制自己开启一个新的事务,如果一个事务已经存在,那么将这个事务挂起.如 ServiceA.methodA()调用 ServiceB.methodB(),methodB()上的传播级别是 PROPAGATION_REQUIRES_NEW 的话,那么如果 methodA 报错,不影响 methodB 的事务,如果 methodB 报错,那么 methodA 是可以选择是回滚或者提交的,就看你是否将 methodB 报的错误抛出还是 try catch 了.
打印结果:
实验分析:
这个结果确实是没想到,service2 新开了个事务走的是主库,而 service 里面的同一个事务里的写后读,反而走了从库。
实验总结:
场景 | service | service2 |
---|---|---|
同一个 service 里写完读 | 主库 | 主库 |
service 里写完调用另一个 servcie 进行读操作 | 主库 | 主库 |
service 里写完新开线程调用另一个 servcie 进行读操作 | 主库 | 从库 |
service 里写完新开一个事务调用另一个 servcie 进行读操作 | 从库 | 主库 |
常规的写完读操作和写完在另一个 service 里进行读操作,都能够走到主库,保证了常规业务的正确性,也满足了我们一般的使用场景了。而新开线程进行读操作的情况其实比较少,如果非要使用,我们可以用强制指定主库的方式进行处理。
最后一种情况,service中调用另一个service2(新开事务),原本 service 里同一个事务的写完读操作走到了从库,一不注意容易引起实际业务bug,需要使用者谨慎使用。大家觉得这是不是ShardingJdbc的一个BUG呢?
踩坑,发现一个ShardingJdbc读写分离的BUG的更多相关文章
- SpringBoot使用Sharding-JDBC读写分离
本文介绍SpringBoot使用当当Sharding-JDBC进行读写分离. 1.有关Sharding-JDBC 本文还是基于当当网Sharding-Jdbc的依赖,与上一篇使用Sharding-Jd ...
- Spring Boot中整合Sharding-JDBC读写分离示例
在我<Spring Cloud微服务-全栈技术与案例解析>书中,第18章节分库分表解决方案里有对Sharding-JDBC的使用进行详细的讲解. 之前是通过XML方式来配置数据源,读写分离 ...
- Spring Boot + Sharding-JDBC 读写分离
本文使用 Sharding-JDBC 实现读写分离,基于 CentOS 7 + MySQL 5.7 一.MySQL 安装及配置 1.1 安装 依次执行命令: sudo wget -i -c http: ...
- 我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题
前言: 本渣渣想分析分析Doug Lea大佬对高并发代码编写思路, 于是找到了我们今天的小主角ConcurrentLinkedQueue进行鞭打, 说实话草稿我都打好了, 就差临门一脚, 给踢折了 直 ...
- Sharding-JDBC读写分离
https://www.jianshu.com/p/8bbc8ca63037 官网文档:当当网,架构师张亮 http://shardingsphere.io/document/current/cn/m ...
- 发现一个c++ vector sort的bug
在开发中遇到一个非常诡异的问题:我用vector存储了一组数据,然后调用sort方法,利用自定义的排序函数进行排序,但是一直都会段错误,在排序函数中打印参加排序的值,发现有空值,而且每次都跟同一个数据 ...
- 我的踩坑之旅-跨域问题引发bug
场景: 由于业务原因需要在请求中添加一个信息表明请求的source,经过一轮方案的评审,大家共同决定把这source信息存放在消息header中.前端小伙伴听完之后心里暗自偷笑:就一行的代码的事,请求 ...
- 我的踩坑之旅-代码不规范引发的“bug”
今早公司上班,老大跟我说有一个服务老是上线,下线,问我啥情况.我回想了下我的项目部署,觉得不可能会出现这个问题呀.然后各种鼓捣,倒腾了一个早上,终于找出了罪魁祸首. 场景:我们的服务部署在亚马逊上.我 ...
- vue+ vue-router + webpack 踩坑之旅
说是踩坑之旅 其实是最近在思考一些问题 然后想实现方案的时候,就慢慢的查到这些方案 老司机可以忽略下面的内容了 1)起因 考虑到数据分离的问题 因为server是express搭的 自然少 ...
随机推荐
- Hash表、 继承
Hash表 我们来了解什么是Hash表?? 要想知道什么是哈希表,那得先了解哈希函数 二叉平衡树 红黑树 B B+树,它们的查找都是先从根节点进行查找,从节点取出数据或索引与查找值进行比较.那么,有没 ...
- 在Winform开发中,使用Async-Awati异步任务处理代替BackgroundWorker
在Winform开发中有时候我们为了不影响主UI线程的处理,以前我们使用后台线程BackgroundWorker来处理一些任务操作,不过随着异步处理提供的便利性,我们可以使用Async-Awati异步 ...
- 【Java面试】介绍下Spring IoC的工作流程
Hi,我是Mic 一个工作了4年的粉丝,在面试的时候遇到一个这样的问题. "介绍一下Spring IOC的工作流程" 他说回答得不是很好,希望我能帮他梳理一下. 关于这个问题,我们 ...
- javaEE-IDEA创建项目-使用Mybatis
新建项目 点Next之后给项目命名 创建如下文件夹以及文件 修改pom.xml, 加入 <dependencies> <!-- junit单元测试 --> <depend ...
- python 连接SAP 代码
def Main(): sap_app = r"C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe" subproces ...
- 【RPA之家转载RPA创新产业峰会回看】机器人流程自动化专利态势报告
[RPA之家转载RPA创新产业峰会回看]机器人流程自动化专利态势报告 自动化的一个专利情况的监测,就是全球监测的情况.今天我可能给大家汇报的主要是三个方面,第一个方面就是讲一下全球投资智能化的专利的一 ...
- Linux文本查看工具
文本查看工具 cat 特点: 不能用来看二进制文件 选项: -A: 显示不可见字符 cat支持标准输入: cat > aa.txt ---键盘作为标准输入,输出的结果重定向文件中去了 cat & ...
- NC14662 小咪买东西
NC14662 小咪买东西 题目 题目描述 小咪是一个土豪手办狂魔,这次他去了一家店,发现了好多好多( \(n\) 个)手办,但他是一个很怪的人,每次只想买 \(k\) 个手办,而且他要让他花的每一分 ...
- NC20032 [HNOI2003]激光炸弹
NC20032 [HNOI2003]激光炸弹 题目 题目描述 一种新型的激光炸弹,可以摧毁一个边长为R的正方形内的所有的目标. 现在地图上有 \(n\) (\(N ≤ 10000\))个目标,用整数 ...
- 图片64base转码与解码
场景一:图片转码成base64,传输,接收后解码成png等格式图片 import base64 # 读取图片,转换为base64编码格式 with open("F:\Archer\pictu ...