基于数据库的分布式锁实现

续上次用nginx搭建好反向代理负载均衡的俩个实例后,我在项目中关联了如下这张表:

然后写了个接口,去买一个表里的商品,大致思路就是:读取库存,库存减一,回写数据库,返回成功。在单个实例里面加个synchronized后完全正常的减库存,然后当我启动俩个实例后多次疯狂调用接口后出现如下情况:

出现问题的代码如下,其主要原因是俩个实例如果在查询库存时候同时查询数据库的话,由于减库存后没来得及更新,俩次查询的结果是一致的,从而更新库存的值也是一直的,导致卖出俩个商品而只减了一次库存,出现了超卖的情况!

public class StockServiceImpl implements StockService{

    @Autowired
    StockMapper stockMapper;

    @Override
    public Stock selectByPrimaryKey(Integer goodsId) {
        return stockMapper.selectByPrimaryKey(goodsId);
    }
    // 加锁也只能保证单个实例线程安全性
    public synchronized void byGoods() throws InterruptedException {
        // 这里写死,数据库里就一条记录且ID为1,拿到数据
        Stock stock = selectByPrimaryKey(1);
        // 获取到商品的库存
        Long goodsStock = stock.getGoodsStock();
        // 减库存
        goodsStock -= 1;
        stock.setGoodsStock(goodsStock);
        // 为了将问题放大这里睡上几秒 拉长查库存和更新库存的之间的时间间隔
        Thread.sleep(3000);
        // 更新
        updateByPrimaryKeySelective(stock);
        // 输出
        System.out.println("更新后库存为:" + goodsStock);
    }

    @Override
    public int updateByPrimaryKeySelective(Stock record) {
        return stockMapper.updateByPrimaryKeySelective(record);
    }
}

接下来进行改造,使用数据库层面的“锁”,我们知道向一张表中出入俩条相同主键的数据,只可能成功一条,因为主键具有约束性,所以利用这个特点,当我们向数据库插入成功时,即代表获取到锁,从而去运行我们的业务代码,当我们的业务代码运行完时,我们把数据库的该条记录进行删除,即代表释放锁,从而其他线程即有机会获取到锁,再去跑业务代码,这样即使运行的是俩个实例,同一时间也只能一个线程去运行业务代码,也就不会出现超卖这种情况了。下面给出加锁和解锁的代码:

// 上锁,由于上锁失败的话会直接返回失败,并不会再次
 // 获取,是非阻塞的,这里利用循环实现阻塞。
    @Override
    public boolean tryLock() {
        Lock lock = new Lock();
        lock.setLockId(1);
        while (true) {
            try {
                if (lockMapper.selectByPrimaryKey(1) == null) {
                    int i = lockMapper.insert(lock);
                    if (i == 1) {
                    return true;
                    }
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
    // 解锁代码
    @Override
    public void unLock() {
        deleteByPrimaryKey(1);
    }

对service层的购买商品的代码就行加锁:

    // 买商品
    public void byWithLock() throws InterruptedException {
        // 上锁
       lockService.tryLock();
       // 业务代码
       byGoods();
       // 释放锁并跳出循环
       lockService.unLock();
    }

controller层的代码:

@RestController
public class LoadBalance {
    @Autowired
    StockServiceImpl stockService;
    @RequestMapping("/balance")
    public String balance() {
        try {
            stockService.byWithLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "success";
    }
}

再次跑代码,疯狂点接口后俩个实例控制台输出满意结果如下:

总结:

1、通过insert插入数据库是非阻塞的,这里采用while循环“不优雅”的实现了阻塞。数据库自己也可以使用“for update”来实现阻塞。

2、这种数据库层面的锁其实是很粗糙的,非常依赖于数据库,如果数据库宕机,那么是没有办法再使用锁的。

3、如果有线程加锁后运行业务代码时出现如数据库断电恢复,或者请求超时等导致数据库的锁没能解锁,这时我们可以给每个锁一个时间,定时清理超时的锁。

本文分享自微信公众号 - AVAJ(gh_ccf596cb9b66)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏卓文见识

条件竞争(Race condition)漏洞挖掘技巧及实战案例全汇总

条件竞争漏洞(Race condition)官方概念是“发生在多个线程同时访问同一个共享代码、变量、文件等没有进行锁操作或者同步操作的场景中。” 这个漏洞存在于...

27320
来自专栏changxin7

07.Django学习之model进阶

使用Python 的切片语法来限制查询集记录的数目 。它等同于SQL 的LIMIT 和OFFSET 子句。

10530
来自专栏changxin7

05.Django基础五之django模型层(一)单表操作

      创建名为book的app,在book下的models.py中创建模型:

9710
来自专栏测试游记

测试开发进阶(二十二)

让图中的 Persons变为中文:修改 projects.models.Person保存后刷新页面

7830
来自专栏IT码农

记一次多字段排序的sql使用if语句问题

SELECT department_id,order_expected_amount,administrator_id, case administrator_...

7620
来自专栏eadela

oracle--ORA常见报错 常见错误地址ORA-01034和ORA-27101的解决办法

出现ORA-01034和ORA-27101的原因是多方面的:主要是oracle当前的服务不可用,shared memory realm does not exi...

9840
来自专栏changxin7

08.Django基础六之ORM中的锁和事务

    select_for_update(nowait=False, skip_locked=False) #注意必须用在事务里面,至于如何开启事务,我们看下...

11940
来自专栏changxin7

06.Django基础五之django模型层(二)多表操作

    一对一、多对一、多对多 ,用book表和publish表自己来想想关系,想想里面的操作,加外键约束和不加外键约束的区别,一对一的外键约束是在一对多的约束...

6720
来自专栏eadela

oracle--少见操作、如何调整dos窗口大小、字符集设置

操作系统环境变量针对语言项设置有几个,我经常设置的是这两个LANG和NLS_LANG。

7320
来自专栏Hi, Python

搭建一个简易的成绩管理系统的数据库

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

5910

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励