ShardingJdbc读写分离的BUG踩坑解决

 更新时间:2022年08月29日 10:02:01   作者:女友在高考  
这篇文章主要为大家介绍了ShardingJdbc读写分离的BUG踩坑解决,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

最近公司准备接入ShardingJdbc做读写分离了,老大让我们理一理有没有写完数据立马读的场景,因为主从同步是有延迟的,如果写完读取数据走到从库,而从库正好有延迟,没读取到数据,岂不是造成了生产事故。

今天我们来看看,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 里面的同一个事务里的写后读,反而走了从库。

实验总结:

场景serviceservice2
同一个 service 里写完读主库主库
service 里写完调用另一个 servcie 进行读操作主库主库
service 里写完新开线程调用另一个 servcie 进行读操作主库从库
service 里写完新开一个事务调用另一个 servcie 进行读操作从库主库

常规的写完读操作和写完在另一个 service 里进行读操作,都能够走到主库,保证了常规业务的正确性,也满足了我们一般的使用场景了。而新开线程进行读操作的情况其实比较少,如果非要使用,我们可以用强制指定主库的方式进行处理。

最后一种情况,service中调用另一个service2(新开事务),原本 service 里同一个事务的写完读操作走到了从库,一不注意容易引起实际业务bug,需要使用者谨慎使用。大家觉得这是不是ShardingJdbc的一个BUG呢?

以上就是ShardingJdbc读写分离的BUG踩坑解决的详细内容,更多关于ShardingJdbc读写分离BUG的资料请关注脚本之家其它相关文章!

相关文章

  • 性能调优之java服务器容器调优详解

    性能调优之java服务器容器调优详解

    这篇文章主要介绍了java服务器容器调优,如果接口响应时间超过了既定数据,项目支撑不了这么大的请求,就需要对项目以及项目接口进行数据库、容器、缓存等方面的调优,文章中有详细的代码示例,需要的朋友可以参考一下
    2023-04-04
  • Spring Cloud Alibaba Nacos Config进阶使用

    Spring Cloud Alibaba Nacos Config进阶使用

    这篇文章主要介绍了Spring Cloud Alibaba Nacos Config进阶使用,文中使用企业案例,图文并茂的展示了Nacos Config的使用,感兴趣的小伙伴可以看一看
    2021-08-08
  • 解析SpringBoot @EnableAutoConfiguration的使用

    解析SpringBoot @EnableAutoConfiguration的使用

    这篇文章主要介绍了解析SpringBoot @EnableAutoConfiguration的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • Spring Boot实现热部署的五种方式

    Spring Boot实现热部署的五种方式

    这篇文章主要介绍了Spring Boot 五种热部署方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-01-01
  • Java向数据库插入中文出现乱码解决方案

    Java向数据库插入中文出现乱码解决方案

    这篇文章主要介绍了Java向数据库插入中文出现乱码解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • 详解Java对象创建的过程及内存布局

    详解Java对象创建的过程及内存布局

    今天给大家带来的文章是Java对象创建的过程及内存布局,文中有非常详细的图文示例及介绍,需要的朋友可以参考下
    2021-06-06
  • Spring boot实现热部署的两种方式详解

    Spring boot实现热部署的两种方式详解

    这篇文章主要介绍了Spring boot实现热部署的两种方式,这两种方法分别是使用 Spring Loaded和使用spring-boot-devtools进行热部署,文中给出了详细示例代码和介绍,需要的朋友可以参考学习,下面来一起看看吧。
    2017-04-04
  • idea运行tomcat报错找不到catalina.bat,系统找不到指定的文件问题

    idea运行tomcat报错找不到catalina.bat,系统找不到指定的文件问题

    这篇文章主要介绍了idea运行tomcat报错找不到catalina.bat,系统找不到指定的文件问题,具有很好的参考价值,希望对大家有所帮助,
    2023-11-11
  • SpringBoot项目docker容器部署实现

    SpringBoot项目docker容器部署实现

    本文主要介绍了SpringBoot项目docker容器部署实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-03-03
  • Spring创建Bean的6种方式详解

    Spring创建Bean的6种方式详解

    这篇文章主要介绍了Spring创建Bean的6种方式详解,本文讲解了在Spring 应用中创建Bean的多种方式,包括自动创建,以及手动创建注入方式,实际开发中可以根据业务场景选择合适的方案。,需要的朋友可以参考下
    2019-06-06

最新评论