本篇内容主要讲解“ShardingJdbc读写分离的BUG坑怎么解决”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“ShardingJdbc读写分离的BUG坑怎么解决”吧!
我本地使用了两个库来做实验,写库(ds_0_master)和读库(ds_0_salve),两个库并没有配置主从,但也不影响实验操作。
库里有一个city 表。主库的 city 表没有数据,而从库的 city 表就一条数据。数据内容如下:
我们讨论 4 种业务场景:
常规写完读
在一个 service 里面调用另一个 service2 进行读
在一个 service 里面新开一个线程去调用 service2
在一个 service 里面调用 service2,但 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); List<City> all = cityRepository.findAll(); all.forEach(x->{ System.out.println("cityService:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x); }); } }
打印结果:
实验分析: 我们对 city 表进行插入后,紧接着对 city 表进行了查询,查出的内容是我们刚刚插入的内容。说明查询操作没有走读库,而是走了主库。
代码如下:
@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 也是走的主库。
代码如下:
@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); }); }
再次执行,结果如下:
和预想的不一样,依旧是走的从库。
我们调整 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坑怎么解决”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。