一次乐观锁的的实际运用

2020-06-02 10:12:45 浏览数 (1)

这个月一直在做项目的的善后工作(20号离职),在当前这个项目上于ERP和PDM系统集成对接中,出现了许多小问题,让我感觉值得一提的是,我们OA系统的领料单流程在归档后需要对ERP物料进行扣减,但是其中物料扣减发生了错误,这是由于多人同时读写数据造成(实在不敢称之为并发)。下面我将分享一下整个过程以及解决方案。

在于外部系统对接的一开始,我们就约定从ERP的数据源获取物料的库存显示到表单中,用户根据实际领料来填写,到提交归档节点的时候,我们会再取一次最新的库存(因为流程到归档时间线可能会有点长,期间库存有可能已经被修改了),然后执行扣减回写到ERP数据源。

一开始觉得并没有问题,因为使用该流程的员工就是几十人,但是不幸的事情发生了:扣减不一致,员工A对于X物料扣减10,员工B对于X物料扣减5,X物料初始为500,正常来说剩余485。但是最终却成为495。虽然说这是一件概率小的时候,但是自己还是不应该将原因归咎于概率,所以需要彻底避免这个问题。

一开始想使用Redis锁来实现(Redis setNX 实现分布式锁大家可以搜一搜相关文章,之前个人使用Redis分布式锁的时候是使用SpringBoot RedisTemplate去实现),但是想想这个业务完全用不上这样来搞,所以想到了乐观锁(觉得悲观锁有点麻烦,相关概念我就不描述了)。

乐观锁使用版本号作为条件:即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。

那么我们来看具体的逻辑:使用for循环新增50条线程模拟并发访问(也可以使用jmeter进行接口访问测试)

代码语言:javascript复制
        for (int i = 0; i < 50; i  ) {
            int finalI = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    transactionalTest.setNum(finalI);
                }
            }).start();

        }

如果我们不考虑并发,那么Service层这样写

代码语言:javascript复制
 public void setNum(int num) {
  TestVO testVO = testDao.getNum();
   int data = testDao.updateNum(num   testVO.getNum(), testVO.getVersion());
  }

下面是执行前的数据

id

num

version

1

0

0

执行sql:

代码语言:javascript复制
UPDATE pri_admin set num = #{num},version=version 1 where id = 1

id

num

version

1

400

50

0 1 2 .... 49应该为1225,但是这里只有400可见并发修改数据出现了问题。

现在我们使用乐观锁实现数据的累加。

我们sql修改如下:比较上面的SQL多了一个and条件去匹配version

代码语言:javascript复制
UPDATE pri_admin set num = #{num},version=version 1 where id = 1 AND  version=#{version}

Service层修改如下,我们首先获取version和num,然后更新的时候匹配version,如果不匹配的话那么data就为0,此时进行递归进行更新,直到更新失败,我们也可以将线程存储到集合中,记录递归的访问次数,当次数达到一定数目后作为递归结束条件

代码语言:javascript复制
    public void setNum(int num) {
        TestVO testVO = testDao.getNum();
        int data = testDao.updateNum(num   testVO.getNum(), testVO.getVersion());
        if (data == 0) {
            System.out.println(Thread.currentThread().getName()   ":尝试继续更新");
            setNum(num);
        } else {
            System.out.println("成功");
        }

    }
    

我们再来看一下执行结果(先将数据库num置0):

id

num

version

1

1225

100

最近在整理面试题的题库,大家可以到公众号Java面试-Java面试-笔试面试进行查看,目前只整理了四篇(觉得整理起来好累),大家也可以到小程序中进行面试题查看。

0 人点赞