ShardingJdbc讀寫分離的BUG踩坑解決
前言
最近公司準(zhǔn)備接入ShardingJdbc做讀寫分離了,老大讓我們理一理有沒有寫完數(shù)據(jù)立馬讀的場(chǎng)景,因?yàn)橹鲝耐绞怯醒舆t的,如果寫完讀取數(shù)據(jù)走到從庫(kù),而從庫(kù)正好有延遲,沒讀取到數(shù)據(jù),豈不是造成了生產(chǎn)事故。
今天我們來看看,ShardingJdbc作為一個(gè)成熟的框架是怎么處理寫完數(shù)據(jù)立即讀取的場(chǎng)景的。
數(shù)據(jù)庫(kù)介紹
我本地使用了兩個(gè)庫(kù)來做實(shí)驗(yàn),寫庫(kù)(ds_0_master)和讀庫(kù)(ds_0_salve),兩個(gè)庫(kù)并沒有配置主從,但也不影響實(shí)驗(yàn)操作。
庫(kù)里有一個(gè)city 表。主庫(kù)的 city 表沒有數(shù)據(jù),而從庫(kù)的 city 表就一條數(shù)據(jù)。數(shù)據(jù)內(nèi)容如下:
我們討論 4 種業(yè)務(wù)場(chǎng)景:
- 常規(guī)寫完讀
- 在一個(gè) service 里面調(diào)用另一個(gè) service2 進(jìn)行讀
- 在一個(gè) service 里面新開一個(gè)線程去調(diào)用 service2
- 在一個(gè) service 里面調(diào)用 service2,但 service2 是新開的事務(wù)
先直接上實(shí)驗(yàn)結(jié)果:
1. 常規(guī)寫完讀
@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("四川"))?"主庫(kù)":"從庫(kù)")+":"+x); }); } }
打印結(jié)果:
實(shí)驗(yàn)分析: 我們對(duì) city 表進(jìn)行插入后,緊接著對(duì) city 表進(jìn)行了查詢,查出的內(nèi)容是我們剛剛插入的內(nèi)容。說明查詢操作沒有走讀庫(kù),而是走了主庫(kù)。
2. 在一個(gè) service 里面調(diào)用另一個(gè) service
代碼如下:
@Transactional(rollbackFor = Exception.class) public void test(){ City city=new City(); city.setName("眉山"); city.setProvince("四川"); cityRepository.save(city); //調(diào)用其他service cityService2.test(); List<City> all = cityRepository.findAll(); all.forEach(x->{ System.out.println("cityService:"+((x.getProvince().equals("四川"))?"主庫(kù)":"從庫(kù)")+":"+x); }); } }
service2 的代碼:
public void test(){ List<City> all = cityRepository.findAll(); all.forEach(x->{ System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主庫(kù)":"從庫(kù)")+":"+x); }); }
打印結(jié)果:
實(shí)驗(yàn)分析:在 service 方法里調(diào)用了其他 service,其他 service 也會(huì)受到影響。service2 也是走的主庫(kù)。
3. 新開一個(gè)線程去調(diào)用 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("四川"))?"主庫(kù)":"從庫(kù)")+":"+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("四川"))?"主庫(kù)":"從庫(kù)")+":"+x); }); } }
打印結(jié)果:
實(shí)驗(yàn)分析: 我們新開了線程對(duì) city 表進(jìn)行查詢,此次查詢讀的是從庫(kù)。新開的線程會(huì)走從庫(kù),我猜想是新開的線程它認(rèn)為是沒有寫入/修改操作,所以走了從庫(kù)。
我又改動(dòng)了 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("四川"))?"主庫(kù)":"從庫(kù)")+":"+x); }); }
再次執(zhí)行,結(jié)果如下:
和預(yù)想的不一樣,依舊是走的從庫(kù)。
4. service2 新開一個(gè)事務(wù)執(zhí)行
我們調(diào)整 service2 的事務(wù)傳播行為級(jí)別。代碼如下:
@Transactional(propagation = Propagation.REQUIRES_NEW) public void test(){ List<City> all = cityRepository.findAll(); all.forEach(x->{ System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主庫(kù)":"從庫(kù)")+":"+x); }); }
REQUIRES_NEW 的含義是:
強(qiáng)制自己開啟一個(gè)新的事務(wù),如果一個(gè)事務(wù)已經(jīng)存在,那么將這個(gè)事務(wù)掛起.如 ServiceA.methodA()調(diào)用 ServiceB.methodB(),methodB()上的傳播級(jí)別是 PROPAGATION_REQUIRES_NEW 的話,那么如果 methodA 報(bào)錯(cuò),不影響 methodB 的事務(wù),如果 methodB 報(bào)錯(cuò),那么 methodA 是可以選擇是回滾或者提交的,就看你是否將 methodB 報(bào)的錯(cuò)誤拋出還是 try catch 了.
打印結(jié)果:
實(shí)驗(yàn)分析: 這個(gè)結(jié)果確實(shí)是沒想到,service2 新開了個(gè)事務(wù)走的是主庫(kù),而 service 里面的同一個(gè)事務(wù)里的寫后讀,反而走了從庫(kù)。
實(shí)驗(yàn)總結(jié):
場(chǎng)景 | service | service2 |
---|---|---|
同一個(gè) service 里寫完讀 | 主庫(kù) | 主庫(kù) |
service 里寫完調(diào)用另一個(gè) servcie 進(jìn)行讀操作 | 主庫(kù) | 主庫(kù) |
service 里寫完新開線程調(diào)用另一個(gè) servcie 進(jìn)行讀操作 | 主庫(kù) | 從庫(kù) |
service 里寫完新開一個(gè)事務(wù)調(diào)用另一個(gè) servcie 進(jìn)行讀操作 | 從庫(kù) | 主庫(kù) |
常規(guī)的寫完讀操作和寫完在另一個(gè) service 里進(jìn)行讀操作,都能夠走到主庫(kù),保證了常規(guī)業(yè)務(wù)的正確性,也滿足了我們一般的使用場(chǎng)景了。而新開線程進(jìn)行讀操作的情況其實(shí)比較少,如果非要使用,我們可以用強(qiáng)制指定主庫(kù)的方式進(jìn)行處理。
最后一種情況,service中調(diào)用另一個(gè)service2(新開事務(wù)),原本 service 里同一個(gè)事務(wù)的寫完讀操作走到了從庫(kù),一不注意容易引起實(shí)際業(yè)務(wù)bug,需要使用者謹(jǐn)慎使用。大家覺得這是不是ShardingJdbc的一個(gè)BUG呢?
以上就是ShardingJdbc讀寫分離的BUG踩坑解決的詳細(xì)內(nèi)容,更多關(guān)于ShardingJdbc讀寫分離BUG的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
性能調(diào)優(yōu)之java服務(wù)器容器調(diào)優(yōu)詳解
這篇文章主要介紹了java服務(wù)器容器調(diào)優(yōu),如果接口響應(yīng)時(shí)間超過了既定數(shù)據(jù),項(xiàng)目支撐不了這么大的請(qǐng)求,就需要對(duì)項(xiàng)目以及項(xiàng)目接口進(jìn)行數(shù)據(jù)庫(kù)、容器、緩存等方面的調(diào)優(yōu),文章中有詳細(xì)的代碼示例,需要的朋友可以參考一下2023-04-04Spring Cloud Alibaba Nacos Config進(jìn)階使用
這篇文章主要介紹了Spring Cloud Alibaba Nacos Config進(jìn)階使用,文中使用企業(yè)案例,圖文并茂的展示了Nacos Config的使用,感興趣的小伙伴可以看一看2021-08-08解析SpringBoot @EnableAutoConfiguration的使用
這篇文章主要介紹了解析SpringBoot @EnableAutoConfiguration的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Spring?Boot實(shí)現(xiàn)熱部署的五種方式
這篇文章主要介紹了Spring?Boot?五種熱部署方式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01Java向數(shù)據(jù)庫(kù)插入中文出現(xiàn)亂碼解決方案
這篇文章主要介紹了Java向數(shù)據(jù)庫(kù)插入中文出現(xiàn)亂碼解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08詳解Java對(duì)象創(chuàng)建的過程及內(nèi)存布局
今天給大家?guī)淼奈恼率荍ava對(duì)象創(chuàng)建的過程及內(nèi)存布局,文中有非常詳細(xì)的圖文示例及介紹,需要的朋友可以參考下2021-06-06Spring boot實(shí)現(xiàn)熱部署的兩種方式詳解
這篇文章主要介紹了Spring boot實(shí)現(xiàn)熱部署的兩種方式,這兩種方法分別是使用 Spring Loaded和使用spring-boot-devtools進(jìn)行熱部署,文中給出了詳細(xì)示例代碼和介紹,需要的朋友可以參考學(xué)習(xí),下面來一起看看吧。2017-04-04idea運(yùn)行tomcat報(bào)錯(cuò)找不到catalina.bat,系統(tǒng)找不到指定的文件問題
這篇文章主要介紹了idea運(yùn)行tomcat報(bào)錯(cuò)找不到catalina.bat,系統(tǒng)找不到指定的文件問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,2023-11-11SpringBoot項(xiàng)目docker容器部署實(shí)現(xiàn)
本文主要介紹了SpringBoot項(xiàng)目docker容器部署實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-03-03