欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

分布式锁

程序员文章站 2022-06-21 17:41:25
...

之前在涉及到多线程下的共享资源不安全的问题,我们一般都是采用锁来进行解决的,可见——并发编程 ,但是一旦我们的系统设计到分布式架构,在分布式环境下,锁是脱离JVM控制的,在介绍分布式锁之前,我们先来看一看电商中常见的业务场景,如下:


系统A是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用户下单。由于系统有一定的并发,所以会预先将商品的库存保存在Redis中,用户下单的时候会更新redis的库存。此时系统架构如下:
分布式锁
但是这样一来会产生一个问题:假如某个时刻,Redis里面的某个商品库存为1,此时两个请求同时到来,其中一个请求执行到上图的第3步,更新数据库的库存为0,但是第4步还没有执行。


而另外一个请求执行到了第2步,发现库存还是1,就继续执行第3步。这样的结果,是导致卖出了2个商品,然而其实库存只有1个。很明显不对啊!这就是典型的库存超卖问题。


此时,我们很容易想到解决方案:用锁把2、3、4步锁住,让他们执行完之后,另一个线程才能进来执行第2步。
分布式锁
按照上面的图,在执行第2步时,使用Java提供的 synchronized 或者 ReentrantLock 来锁住,然后在第4步执行完之后才释放锁。这样一来,2、3、4 这3个步骤就被“锁”住了,多个线程之间只能串行化执行。


但是好景不长,整个系统的并发飙升,一台机器扛不住了。现在要增加一台机器,如下图:
分布式锁
增加机器之后,系统变成上图所示,假设此时两个用户的请求同时到来,但是落在了不同的机器上,那么这两个请求是可以同时执行了,还是会出现库存超卖的问题。


因为上图中的两个A系统,运行在两个不同的JVM里面,他们加的锁只对属于自己JVM里面的线程有效,对于其他JVM的线程是无效的。因为Java提供的原生锁机制在多机部署场景下失效了,这是因为两台机器加的锁不是同一个锁(两个锁在不同的JVM里面)。


那么这里我们就保证两台机器加的锁是同一个锁,在整个系统提供一个全局、唯一的获取锁的“资源”,然后每个系统在需要加锁时,都去问该“资源”拿到一把锁,这样不同的系统拿到的就可以认为是同一把锁。至于该“资源”,可以是数据库,也可以 Zookeeper 或 Redis 。
分布式锁


那么我们就来看看如何实现分布式锁,这列我们就来看看最为简单的一种,我们以MySQL数据库来作为分布式环境下的见证人,用以协调资源。


首先我们在数据库中,新建了一个张表 db_lock,其中只有一个字段 id,并且我们将其设置为主键,如下:
分布式锁

public interface Lock {

    boolean tryLock();

    void lock();

    void unlock();
}
public abstract class AbstractLock implements Lock{

    @Override
    public void lock() {
        if (tryLock()) {
            System.out.println("获取Lock锁的资源成功");
        } else {
            //等待Lock的释放
            waitLock();
            //重新尝试获取锁资源
            lock();
        }
    }

    protected abstract void waitLock();
}
@Component
public class MysqlLock extends AbstractLock {

    private static final int ID_NUM = 1;

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Override
    protected void waitLock() {
        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            //do nothing
        }
    }

    @Override
    public boolean tryLock() {
        try {
            String sql = "insert into db_lock (id) values (?)";
            jdbcTemplate.update(sql,ID_NUM); //一条sql语句,是个原子性操作
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    @Override
    public void unlock() {
        String sql = "delete from db_lock where id = ?";
        jdbcTemplate.update(sql,ID_NUM);
    }
}

上述就简单的使用了一个基于MySQL的分布式锁了,其实现方式非常简单,但是我们一般我们肯定不会以MySQL来作为分布式锁的,因为其性能较差,无法适应高并发场景;容易出现死锁的情况。


所以我们一般来说会使用Zookeeper和Redis来实现分布式锁,有关Zookeeper和Redis实现分布式锁,我们之前也详细介绍过了,这里有兴趣的话,可以查看ZookeeperRedis

相关标签: 分布式架构