关于数据库:面试被问到分布式锁-我这样回答-面试官让我回去等通知

34次阅读

共计 3309 个字符,预计需要花费 9 分钟才能阅读完成。

<center> 前言 </center>

  说到锁,在平时的工作中,次要是应用 synchronized 关键字,或者相干的一些类库来实现同步,但这都是基于单机利用而言的,当咱们的利用多实例部署时,这时候就须要用到分布式锁了,罕用的分布式锁次要是基于 redis 的分布式锁和基于 zookeeper 的分布式锁及基数据库的分布式锁,前俩个次要基于中间件的个性来实现,明天介绍一下基于数据库的分布式锁的实现,在一些并发不高的场景下比拟实用。

<center> 注释 </center>

  首先须要在数据库中建设好数据表,相干的字段如下所示

CREATE TABLE IF NOT EXISTS `lock_tbl`(
   `lock_id` INT NOT NULL, -- 主键且次要字段不可少
   `des_one` VARCHAR(20), -- 可有可无
   `des_two` VARCHAR(20), -- 可有可无
   PRIMARY KEY (`lock_id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

   接着咱们应用单实例利用,编写一个接口,去买一个表里的商品,大抵思路就是:读取库存,库存减一,回写数据库,返回胜利,其外围代码如下:

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);
    }
}

  在单个实例外面加个 synchronized 后齐全 失常 的减库存,而后咱们 启动两个实例 后应用 postman 对接口进行压测,呈现如下状况:

  通过截图可知上述程序曾经呈现 超卖 景象, 接下来进行革新,应用数据库层面的锁,咱们晓得向一张表中插入俩条雷同主键的数据,只可能胜利一条,因为主键具备约束性,所以利用这个特点,当咱们向数据库插入胜利时,即代表获取到锁,从而去运行咱们的业务代码,当咱们的业务代码运行完时,咱们把数据库的该条记录进行删除,即代表开释锁,从而其余线程即有机会获取到锁,再去跑业务代码,这样即便运行的是俩个实例,同一时间也只能一个线程去运行业务代码,也就不会呈现超卖这种状况了。上面给出加锁和解锁的代码:

// 上锁。因为上锁失败的话会间接返回失败,并不会再次获取
// 是非阻塞的,这里利用循环实现阻塞。@Override
    public boolean tryLock() {
        // 这里的 Lock 就是简略的一个 POJO 对象映射到数据库中一张表的字段
        Lock lock = new Lock();
        lock.setLockId(1);
        // 通过 while 循环来实现阻塞
        while (true) {
            try {
                // 首先查问一下主键为 1 的数据是否存在,如果存在则阐明锁曾经被占用了
                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";
    }
}

再次将程序启动,应用 postman 简略做下压测,发现曾经失常进行减库存了。后果如下图所示

<center>存在的问题</center>
<lu>
<li>1、如果有一台实例拿到锁后宕机了,锁未能及时开释,那么其余实例将永远无奈获取到锁。</li>
<li>2、不可重入,一台实例拿到锁后,想再次获取该锁时会失败 </li>
</lu>

<center>如何解决</center>
<lu>
<li>1、对于存在实例宕机导致锁无奈开释的问题,能够在插入数据的时候将以后的一个工夫戳也插入数据库中,而后启一个定时工作,定期去扫表,同时设定一个锁的超时工夫(该超时工夫肯定要大于失常的接口调用工夫),将超时的记录进行删除。</li>
<li>2、对于不可重入,能够在表中插入数据的时候减少实例和线程相干的信息,当获取锁时进行判断,如果相符则间接获取锁。</li>
</lu>

乐观锁

  乐观锁简略了解就是在任何状况下都是乐观的认为申请 临界资源 的时候都会与其余线程发生冲突,因而每次都是加乐观锁,这种锁具备强烈的强占性和排他性。上述的例子中所加的锁就是乐观锁即先取锁再拜访,MySql 自带的乐观锁是 For Update,应用 For Update 能够显示的减少行锁,但乐观锁会让数据库额定的开销,同时减少 死锁 的危险。

乐观锁

  乐观锁简略了解就是每次线程申请 临界资源 时都认为不会有其余线程与其竞争,只有在数据进行提交的时候才进行竞争,在检测数据抵触时并不依赖数据库自身的锁机制,不影响申请的性能。上述例子咱们能够在数据库表中减少一个 Version 版本号,对于要进行批改的数据,先从数据库中将改 Version 的版本号查出来,而后批改的时候带上该版本号一起批改

SELECT VERSION FROM TABLE_A  -- 假如这里查出来 version 的值是 OldVersion
UPDATE TABLE_A SET COUNT = COUNT -1, VERSION = VERSION + 1 WHERE VERSION = OldVersion

总结

  并发不是特地高的状况下能够思考应用基于数据库的分布式锁,尽量采纳乐观锁的形式以进步利用的吞吐量。

/ 感激反对 /

以上便是本次分享的全部内容,心愿对你有所帮忙 ^_^

喜爱的话别忘了 分享、点赞、珍藏 三连哦~

欢送关注公众号 程序员巴士,一辆乏味、有范儿、有温度的程序员巴士,涉猎大厂面经、程序员生存、实战教程、技术前沿等内容,关注我,交个敌人吧!

正文完
 0