分布式锁的场景及解决方案

分布式锁的使用场景:超卖问题

库存一定的商品,在并发请求时即存在超卖问题,例如:大促、秒杀

分布式锁的场景及解决方案_第1张图片

单进程解决方案

在同一进程下,只需要通过java内置的锁Synchronized和ReentranLock即可。

其核心思想就是保证多个线程互斥访问count变量,从而解决超卖问题。

集群场景

问题分析

为了提升性能,减缓单机压力,会将该程序部署到多台机器上运行。这时三个用户的操作就可能打在三台机器上。在集群场景下由于资源不共享,如果依然只采用java单进程的锁,那么每个程序都会获取到一把锁,多个程序同时访问库存,造成超卖问题。

解决方案

想要解决问题,依然是找到一种互斥访问库存的解决方案。

在单机单进程环境下,所有的资源都是存在jvm中,jvm具有上帝视角,因此可以实现jvm加锁互斥访问。

但在多机集群环境下,程序彼此之间互不相识,资源独立。因此需要找到一份公共的空间去沟通协调,类似于生活中的第三方组织介入,这个第三方可以通过Redis/MySql/Zookeeper来实现。这也就是分布式锁的实现思路。

当多个进程访问库存时,首先通过第三方获取到一把分布式锁,获取锁成功的才可以访问库存,否则需等待锁释放。

Redis实现分布式锁 

通过Redis中的缓存来实现分布式锁,即setNx命令,通过该命令可以设置一份key-value至redis中,多个程序访问库存时,通过判断是否设置过库存相关的key来判断是否执行命令,达到分布式锁的目的。

//加锁
private boolean tryLock(final String key, final String value) {
    try {
        MiddlewareCacheResult result
            = middlewareCache.setNx(key, value, exipireTime);
        return result.isSuccess();
    } catch (Exception e) {
        log.error("setNX error, key : {}", key,e);
    }
    return false;
}

//释放锁
public static void releaseLock(String lockKey){
    delete(lockKey);
}

在设置过期时间后又会导致两个问题。

        锁已经过期释放,但程序还没有执行结束。

由于程序未执行结束,过期释放了锁,同时另一个程序获取到了锁,此时第一个程序执行结束,再次释放了第二个程序的锁,导致程序逻辑错误。

解决方案:

        当其他程序尝试获取锁时,如果程序未执行结束,则延长锁的过期时间。

public boolean tryLock(int expireSecond) throws InterruptedException{
    //锁到期时间
    if (this.setNX(lockKey, value, expireSecond)) {
        // lock acquired
        this.expire(lockKey,expireMsecs/1000);
        return true;
    }
    return false;
}

设置value为当前用户相关信息,每次获取锁时判断下value值是否和当前用户信息匹配,防止锁释放的乱套。

redis锁性能提升思路

可以使用分段锁机制,当商品库存非常多时,比如库存有100个,可以每10个库存加一把锁,比如id在1-10是第一把锁,id在11-20为第二把锁,通过10把锁来增加高并发的思路。

Redis集群问题

在Redis集群中使用主从机制时,A程序在主节点获取到锁后,数据还未同步至从节点,此时主节点宕机,另一个B程序在从节点中请求锁,由于数据尚未同步,在B程序获取锁成功,A程序和B程序同时访问,依然造成超卖问题。

分布式锁的场景及解决方案_第2张图片

红锁

为了解决上述集群问题,引入红锁机制,红锁中多个节点独立,没有主从依赖关系。一般都是奇数个节点。

1.获取当前时间。

2.依次获取N个节点的锁。

3.判断是否获取锁成功。 如果client在上述步骤中获取到了(N/2 + 1)个节点锁,并且每个锁的过期时间都是大于0的,则获取锁成功,否则失败。失败时释放锁。

4.释放锁。 对所有节点发送释放锁的指令。

红锁问题

在上述红锁中依然会出现问题,比如对于如下五个节点,当某程序获取到A、B、C三个节点的锁后,这时C节点宕机,同时立即被人重启,这时另外一个程序获取C节点的锁时成功(由于刚刚被重启),该程序同时获取D、E的锁成功,它得到了C、D、E的锁,按照约定获取超过半数节点的锁即可执行,两个程序同时访问互斥资源,依然会造成超卖问题。

因此一般会有约定,当redis挂掉后,会通过手册约定一个时间后再重启,一般在该重启时间后程序锁会过期或者运行完毕,不会对程序造成影响,一般不能立即重启。

总结

通过简单的超卖问题,引入一系列解决方案,伴随着场景越来越复杂,也会带来越来越多的细节问题,在实际开发中,要多思考高并发带来的问题,注意互斥资源的访问。

 

你可能感兴趣的:(redis,java基础,综合,分布式,分布式锁,redis分布式锁,redis)