事务代码中加synchronized锁引发的bug

背景

最近解决了个BUG,由于历史背景,在某一个产品里的用户中心有两套系统,两套系统还使用了两个不同的数据库,所以创建用户的时候会有一个新数据库到旧数据库同步的操作。

具体的流程是用户在页面注册了新用户,请求被新用户中心系统a处理,然后通过消息组件同步到用户中心系统b中,用户只要修改了用户的信息不论是手机号、年龄、姓名等等都会异步触发同步机制,一切听起来都很不合理中透露着合理。

BUG从现象上看是出现了一个用户在旧数据库中出现多条的情况。最烦查这种没有具体堆栈报错的bug,因为搞不好得捋一遍祖传的屎山代码。。。

代码分析

出现这种情况肯定是在接受消息的方法那边处理,因为消息组件难免出现重复发送的情况,一般都会在消费端做幂等处理。

@Transactional
public void consumer(String msg) {
    UserDTO userdto = JsonUtil.toObject(msg, UserDTO.class);
    // 一大堆屎山业务逻辑。。。。。
    synchronized(this) {
        //操作数据库读写
        usercenter.save()
    }
}

这里乍一看感觉没啥问题,多看两眼觉得有点奇怪怪的又说不上哪里怪,其实问题就出在了这里。要么说synchronized关键字要慎重使用,

理性分析一波,@Transactional是通过AOP的方式实现对数据库的事务管理,而synchronized代码块又是在一个事务内,就会出现第一个线程释放锁后但是事务还没来得及提交,第二个线程就进入同步代码块获取到未提交的数据库数据。有点类似脏读。

那怎么解决呢

解决方案

这好办,我直接套个方法不就得了,把锁提到方法外面

public synchronized consumer(String msg) {
    consumer(msg);
}

@Transactional
public void consumer(String msg) {
    UserDTO userdto = JsonUtil.toObject(msg, UserDTO.class);
    // 一些业务判断逻辑。。。。。
    //操作数据库读写
    usercenter.save()
}

这样又会出现另一个问题,你会发现事务失效了,同类内方法互调使用不了AOP包装后的事务方法。看起来一个很好改的小BUG,改起来还得细心点,不然解决一个旧BUG出来新BUG。

这里有三个思路吧,大家有好思路也可以提一下:

  1. 拆成两个类,保存用户一个类,synchronized方法放到另一个类
  2. 自己注入自己,使用AOP包装后的对象调用consumer方法
  3. 手动管理事务

这种老功能最怕乱改代码,所以尽量选择改动量最少的方法,最终使用第二种方法解决

@Autowire
UserServiceImpl userServiceImpl;

public synchronized consumerProxy(String msg) {
    userServiceImpl.consumer(msg);
}

@Transactional
public void consumer(String msg) {
    UserDTO userdto = JsonUtil.toObject(msg, UserDTO.class);
    // 一些业务判断逻辑。。。。。
    //操作数据库读写
    usercenter.save()
}
相关推荐
hogenlaw15 分钟前
Oracle从入门到放弃
数据库·oracle
卡兰芙的微笑28 分钟前
get_property --Cmakelist之中
前端·数据库·编辑器
Z_W_H_40 分钟前
【PostgreSQL】安装及使用(Navicat/Arcgis),连接(C#)
数据库·postgresql
豆姐姐1 小时前
金九银十,分享一波用例设计、数据库、编程笔试题!
自动化测试·数据库·测试用例·软件测试面试
计算机程序设计开发1 小时前
计算机毕业设计公交站点线路查询网站登录注册搜索站点线路车次/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序
数据库·vue.js·spring boot·课程设计·计算机毕业设计
waterHBO2 小时前
ER 图 Entity-Relationship (ER) diagram 101 电子商城 数据库设计
数据库
青云交2 小时前
大数据新视界 --大数据大厂之Kubernetes与大数据:容器化部署的最佳实践
数据库·kubernetes·容器编排·资源管理·大数据处理·扩展性、故障恢复·存储持久化·监控、日志管理、性能提升
liangbm32 小时前
MATLAB系列07:输入/输入函数
开发语言·数据库·笔记·matlab·函数·自定义函数·matlab函数
skate2 小时前
达梦disql支持上翻历史命令-安装rlwrap
数据库
慕容潇湘2 小时前
Oracle事物
数据库·oracle