踩坑,发现一个ShardingJdbc读写分离的BUG

2023-11-15

前言

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

今天我们来看看,ShardingJdbc作为一个成熟的框架是怎么处理写完数据立即读取的场景的。

数据库介绍

我本地使用了两个库来做实验,写库(ds_0_master)和读库(ds_0_salve),两个库并没有配置主从,但也不影响实验操作。

库里有一个city 表。主库的 city 表没有数据,而从库的 city 表就一条数据。数据内容如下:

我们讨论 4 种业务场景:

  1. 常规写完读
  2. 在一个 service 里面调用另一个 service2 进行读
  3. 在一个 service 里面新开一个线程去调用 service2
  4. 在一个 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呢?

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

踩坑,发现一个ShardingJdbc读写分离的BUG 的相关文章

  • Java将字符串解析为double

    如何解析字符串中的这个 Double 00034800 变成 Double 值 最后两位数字实际上是小数点 所以我正在寻找的结果是348 00 是否有这样的格式可以与十进制格式一起使用 Well String s 00034800 doub
  • 在Windows Server 2003下如何在本地系统帐户下运行jvisualvm.exe?

    我在带有 Java 1 6 u 20 的 Windows Server 2003 下将 GlassFish 3 0 1 作为 Windows 服务运行 总体上我很满意 我希望能够在这个 JVM 上使用 VisualVM 并使用无法在 Tom
  • 连接外部 Accumulo 实例和 java

    我正在尝试使用 Accumulo 连接到虚拟机 问题是 我无法将其连接到 Java 中 我可以看到 Apache 抛出的网页 但我无法让它与代码一起工作 我认为这是缺乏知识的问题而不是真正的问题 但我找不到这方面的文档 所有示例都使用 lo
  • 在不支持 CAS 操作的处理器上进行 CompareAndSet

    今天 我在一次采访中被问到下一个问题 如果您在具有不支持 CAS 操作的处理器的机器上调用 AtomicLong 的compareAndSet 方法 会发生什么情况 您能否帮我解决这个问题 并在可能的情况下提供一些全面描述的链接 From
  • Kafka - 如何同时使用过滤器和过滤器?

    我有一个 Kafka 流 它从一个主题获取数据 并且需要将该信息过滤到两个不同的主题 KStream
  • 为什么在 10 个 Java 线程中递增一个数字不会得到 10 的值?

    我不明白 a 的值为0 为什么 a 不是10 那段代码的运行过程是怎样的 是否需要从Java内存模型来分析 这是我的测试代码 package com study concurrent demo import lombok extern sl
  • 以相反的顺序打印任何集合中的项目?

    我在 使用 Java 进行数据结构和问题解决 一书中遇到以下问题 编写一个例程 使用 Collections API 以相反的顺序打印任何 Collection 中的项目 不要使用 ListIterator 我不会把它放在这里 因为我想让有
  • Java中Gson、JsonElement、String比较

    好吧 我想知道这可能非常简单和愚蠢 但在与这种情况作斗争一段时间后 我不知道发生了什么 我正在使用 Gson 来处理一些 JSON 元素 在我的代码中的某个位置 我将 JsonObject 的 JsonElements 之一作为字符串获取
  • java中如何知道一条sql语句是否执行了?

    我想知道这个删除语句是否真的删除了一些东西 下面的代码总是执行 else 是否删除了某些内容 执行此操作的正确方法是什么 public Deleter String pname String pword try PreparedStatem
  • 将类转换为 JSONObject

    我有好几堂这样的课 我想将类转换为 JSONObject 格式 import java io Serializable import com google gson annotations SerializedName public cla
  • Java AES 256 加密

    我有下面的 java 代码来加密使用 64 个字符密钥的字符串 我的问题是这会是 AES 256 加密吗 String keyString C0BAE23DF8B51807B3E17D21925FADF273A70181E1D81B8EDE
  • Android 认为我没有关闭数据库!为什么?

    我有一个 SQLiteDatabase 数据成员 我在 onCreate 中初始化它 并在 onPause onStop 和 onDestroy 中调用 close 它在 onResume 中重新初始化 它似乎运行得很好 但当我查看调试器时
  • 在java程序中使用c++ Dll

    我正在尝试使用System LoadLibrary 使用我用 C 编写的一个简单的 dll UseDllInJava java import com sun jna Library import com sun jna Native imp
  • 如何自定义舍入形式

    我的问题可能看起来很简单 但仍然无法得到有效的东西 我需要自定义 Math round 舍入格式或其他格式以使其工作如下 如果数字是 1 6 他应该四舍五入到 1 如果大于或等于 1 7 他应该四舍五入到 2 0 对于所有其他带有 6 的小
  • Android - 存储对ApplicationContext的引用

    我有一个静态 Preferences 类 其中包含一些应用程序首选项和类似的内容 可以在那里存储对 ApplicationContext 的引用吗 我需要该引用 以便我可以在不继承 Activity 的类中获取缓存文件夹和类似内容 你使用的
  • 从三点求圆心的算法是什么?

    我在圆的圆周上有三个点 pt A A x A y pt B B x B y pt C C x C y 如何计算圆心 在Processing Java 中实现它 我找到了答案并实施了一个可行的解决方案 pt circleCenter pt A
  • 确定 JavaFX 中是否消耗了事件

    我正在尝试使用 JavaFX 中的事件处理来做一些非滑雪道的事情 我需要能够确定手动触发事件后是否已消耗该事件 在以下示例中 正确接收了合成鼠标事件 但调用 Consumer 不会更新该事件 我对此进行了调试 发现 JavaFX 实际上创建
  • Selenium 单击在 Internet Explorer 11 上不起作用

    我尝试在 Internet Explorer 上单击 selenium 但它不起作用 我努力了element click moveToElement element click build perform javascript没事了 事实上
  • 检测到 JVM 正在关闭

    我有一个使用 addShutdownHook 处理 Ctrl C 的 Swing 应用程序 它工作正常 直到我的关闭任务之一调用一个在正常情况下更改 JLabel 文本的函数 此时它挂起 我认为问题是 Swing EDT 已终止或正在等待某
  • GAE 无法部署到 App Engine

    我正在尝试从 Eclipse 发布 Web 应用程序 我在 GAE 上创建了四个项目 可以通过登录我的帐户并查看控制台来查看它们 我已经改变了appengine web xml到项目的应用程序 ID 如果我将其更改为 GAE 上第一个创建的

随机推荐

  • LetCode#1给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。JAVA

    题目 给定一个整数数组 nums 和一个整数目标值 target 请你在该数组中找出 和为目标值 的那 两个 整数 并返回它们的数组下标 你可以假设每种输入只会对应一个答案 但是 数组中同一个元素在答案里不能重复出现 你可以按任意顺序返回答
  • Vue-cli快速创建vue项目

    使用vue cli需要的环境 nodejs 一 什么是vue cli 还记得最初使用webpack搭建项目的痛苦了吗 各种配置 去配置我们的各种loader 插件 项目运行脚本 非常难受 今天我们就学习vue cli 这个东西 再无webp
  • 直接替换Springboot jar包中的文件

    Linux Springboot 在不重新打包的情况下 修改配置文件 直接vim xxx jar包后 可以通过 关键字来查找文件 查找到以后 通过方向键进行控制 enter进入文件 i进入插入模式 修改文件 保存等操作和vim的操作一样 修
  • spdlog日志安装与使用

    spdlog是一个开源的且只需要头文件即可使用的跨平台日志库 适用于c 11版本 1 安装步骤 1 源码下载 git clone https github com gabime spdlog 2 拷贝头文件 cp rf spdlog inc
  • Java中的“static“关键字

    写在前面 前阵子看到一篇有关于Java中static的使用和介绍 讲的比较全面 近几日忽然想起早先工作碰到的有关static的相关问题 自己也总结一下吧 static是我们项目中经常用到的关键字 也是Java中非常重要的一个关键字 stat
  • 单片机多任务程序的框架

    下面说的单片机也主要是51与台系芯片 也就是现在的工作 其它应用不在讨论范围 才识有限 也敬请前辈们勘误 指教 一个真正意义的单片机系统肯定会是多任务的 怎样建立一个多任务程序的框架 1 前后台系统 前台是中断程序 后台是执行主程序 当中断
  • java 图书馆管理系统(超低配版)

    目录 图书管理系统要求 思路 show 方法 add 方法 search方法 delete 方法 代码 接口 Book类 Reader类 User类 Informatica类 BookList类 ReaderList类 UserList类
  • 借助py2neo将csv文件导入neo4j

    下载安装建议 戳我 启动neo4j 安装好之后以管理员权限启动 cmd 输入 neo4j bat console 启动 neo4j 在浏览器端可以打开 http localhost 7474 则说明启动成功 关于py2neo 先来了解些 p
  • this指针概念

    c 中成员变量和成员函数是分开存储的 每一个非静态成员函数只会诞生一份函数实例 也就是说多个同类型的对象会共用一块代码 那么问题是 这一块代码是如何区分哪个对象调用自己呢 c 通过特殊的对象指针 this指针 解决上述问题 this指针指向
  • 数组判断任意出现的重复值

    1 题目背景 给你一个整数数组nums 如果任一值在数组中出现至少两次 返回true 如果数组中每个元素互不相同 返回false 2 代码实现 public class Solution public static void main St
  • socat开放端口&&net命令&&sc命令

    socat socat是一个多功能的网络工具 名字来源于SOcket CAT 是netcat的增强版 扩展了设计 并有新的实现 工具下载地址 http www dest unreach org socat 演示如何开放端口 1 如下所示 开
  • Java动态执行代码字符串

    1 步骤 假如我们需要动态执行一段字符串形式的java代码 大概需要这样几步 生成文件 具体的文件 内存中的文件 调用javac编译 通过反射执行 2 生成文件 确定字符串的形式 字符串已经是完整的 java形式 那么就不再需要再做操作 写
  • 尼克劳斯·沃斯奖 (Niklaus Wirth Award) Pascal最具价值贡献者奖Marco Cantú获奖感言:关于Pascal和Delphi历史的个人观点

    这是一篇很长的博客文章 其中包含我在萨拉曼卡国际Pascal帕斯卡大会上发表的演讲 这是一部Pascal的历史 但是是从非常个人的角度来看的 在开始演讲之前 我要感谢国际Pascal大会的组织者 特别是塞尔吉奥 他们为举办这次活动付出了巨大
  • QT图片叠加CompositionMode效果一览

    QPixmap tmpPix pix size tmpPix fill Qt transparent QPainter p1 tmpPix p1 setCompositionMode QPainter CompositionMode Sou
  • C++操作Redis的简单例子

    相信做过服务端开发的应该都知道Redis的大名 它是一个开源的使用ANSI C语言编写 支持网络 可基于内存亦可持久化的日志型 Key Value数据库 我们后台是用C 开发的 问了下他们 用的缓存框架有Redis SSDB 今天看了几个帖
  • linux kill掉其他的登录用户

    为什么80 的码农都做不了架构师 gt gt gt 我们在LINUX下可以使用多个用户在不同地方连接上LINUX服务器 这样也出现一个问题就是会有多个用户登陆到服务器上了 在系统中我们可以使用命令who 或者w 来查看当前有多少个用户登陆了
  • 华为云云耀云服务器 L 实例评测|配置教程 + 用 Python 简单绘图

    文章目录 Part I Introduction Chap I 云耀云服务器 L 实例简介 Chap II 参与活动步骤 Part II 配置 Chap I 初步配置 Chap II 配置安全组 Part III 简单使用 Chap I V
  • 快速入门 Logback

    简介 Logback 旨在作为流行的 log4j 项目的继承者 它是由 log4j 创始人 Ceki Gulcu 设计的 同时它也是 SpingBoot 项目的默认日志框架 安装 因为 logback 需要和 slf4j 一起使用 所以总共
  • react antv g2 图表更新踩坑记录

    记录一下 antv g2 踩的坑 1 声明一个chart let charts useRef 2 通过调接口获取数据源 3 判断是否第一次获取数据源 if 是否第一次获取数据源 enterpriseColumn res data else
  • 踩坑,发现一个ShardingJdbc读写分离的BUG

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