溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

ShardingJdbc讀寫分離的BUG坑怎么解決

發(fā)布時(shí)間:2022-08-29 16:50:14 來源:億速云 閱讀:217 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“ShardingJdbc讀寫分離的BUG坑怎么解決”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“ShardingJdbc讀寫分離的BUG坑怎么解決”吧!

數(shù)據(jù)庫介紹

我本地使用了兩個(gè)庫來做實(shí)驗(yàn),寫庫(ds_0_master)和讀庫(ds_0_salve),兩個(gè)庫并沒有配置主從,但也不影響實(shí)驗(yàn)操作。

庫里有一個(gè)city 表。主庫的 city 表沒有數(shù)據(jù),而從庫的 city 表就一條數(shù)據(jù)。數(shù)據(jù)內(nèi)容如下:

ShardingJdbc讀寫分離的BUG坑怎么解決

我們討論 4 種業(yè)務(wù)場景:

  • 常規(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("四川"))?"主庫":"從庫")+":"+x);
        });
    }
}

打印結(jié)果:

ShardingJdbc讀寫分離的BUG坑怎么解決

實(shí)驗(yàn)分析: 我們對(duì) city 表進(jìn)行插入后,緊接著對(duì) city 表進(jìn)行了查詢,查出的內(nèi)容是我們剛剛插入的內(nèi)容。說明查詢操作沒有走讀庫,而是走了主庫。

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("四川"))?"主庫":"從庫")+":"+x);
        });
    }
}

service2 的代碼:

public void test(){
    List<City> all = cityRepository.findAll();
    all.forEach(x->{
        System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主庫":"從庫")+":"+x);
    });
}

打印結(jié)果:

ShardingJdbc讀寫分離的BUG坑怎么解決

實(shí)驗(yàn)分析:在 service 方法里調(diào)用了其他 service,其他 service 也會(huì)受到影響。service2 也是走的主庫。

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("四川"))?"主庫":"從庫")+":"+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);
        });
    }
}

打印結(jié)果:

ShardingJdbc讀寫分離的BUG坑怎么解決

實(shí)驗(yàn)分析: 我們新開了線程對(duì) city 表進(jìn)行查詢,此次查詢讀的是從庫。新開的線程會(huì)走從庫,我猜想是新開的線程它認(rèn)為是沒有寫入/修改操作,所以走了從庫。

我又改動(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("四川"))?"主庫":"從庫")+":"+x);
        });
    }

再次執(zhí)行,結(jié)果如下:

ShardingJdbc讀寫分離的BUG坑怎么解決

和預(yù)想的不一樣,依舊是走的從庫。

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("四川"))?"主庫":"從庫")+":"+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é)果:

ShardingJdbc讀寫分離的BUG坑怎么解決

實(shí)驗(yàn)分析: 這個(gè)結(jié)果確實(shí)是沒想到,service2 新開了個(gè)事務(wù)走的是主庫,而 service 里面的同一個(gè)事務(wù)里的寫后讀,反而走了從庫。

實(shí)驗(yàn)總結(jié):

場景serviceservice2
同一個(gè) service 里寫完讀主庫主庫
service 里寫完調(diào)用另一個(gè) servcie 進(jìn)行讀操作主庫主庫
service 里寫完新開線程調(diào)用另一個(gè) servcie 進(jìn)行讀操作主庫從庫
service 里寫完新開一個(gè)事務(wù)調(diào)用另一個(gè) servcie 進(jìn)行讀操作從庫主庫

常規(guī)的寫完讀操作和寫完在另一個(gè) service 里進(jìn)行讀操作,都能夠走到主庫,保證了常規(guī)業(yè)務(wù)的正確性,也滿足了我們一般的使用場景了。而新開線程進(jìn)行讀操作的情況其實(shí)比較少,如果非要使用,我們可以用強(qiáng)制指定主庫的方式進(jìn)行處理。

最后一種情況,service中調(diào)用另一個(gè)service2(新開事務(wù)),原本 service 里同一個(gè)事務(wù)的寫完讀操作走到了從庫,一不注意容易引起實(shí)際業(yè)務(wù)bug,需要使用者謹(jǐn)慎使用。

到此,相信大家對(duì)“ShardingJdbc讀寫分離的BUG坑怎么解決”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI