分布式锁

简介:

分布式集群系统演化的结果,是多线程多进程的分布在不同主机。导致原单机部署情况下的并发控制锁策略失效。

大部分Web应用都是依赖于Java的开发框架,而 Java 并没有提供分布式锁的支持。 为了解决这个问题,需要选择一种跨JVM的互斥机制来控制共享资源的访问。

分布式锁与它的不同方案,就是要解决这个问题。

目前的主流方案有:
1、基于数据库实现分布式锁
2、基于缓存实现分布式锁
3、基于Zookeeper

追求性能,基于缓存实现的分布式锁最好
希望可靠性,选择Zookeeper更胜一筹

使用redis实现分布式锁

setnx 就是redis实现分布式锁的命令

setnx lock 1
-->(integer) 1
setnx lock 1
-->(integer) 0

设置了lock之后,继续访问锁的操作。

如果锁在,会等待重试。锁不在时,才会放行其他请求。 只有把锁解开后(del lock),才能继续其他操作。

死锁问题:

锁释放会出现死锁问题。 自动化解决死锁问题,可以根据具体业务,给锁设定一个合理的过期时间:

setnx lock 1 //设置锁
expire lock 10 // 10秒后锁过期

考虑到上锁操作原子性的必要性,应该将设置锁和设置过期时间在一条指令中执行

set lock 1 nx ex 10 // nx上锁 ex过期时间

实例代码:

public void testLock(){
    // 1 获取锁 3秒后过期
    Boolean lock = redisTemplage.opsForValue().setIfAbsent("lock","1",3,TimeUnit.SECONDS);
    // 2 获取锁成功、查询num的值
    if(lock){
        Object value = redisTemplate.opsForValue().get("num");
        // 判断num为空return
        if(StringUtils.isEmpty(value))
            return;
        //将redis的num加一(业务操作),并手动释放锁
        redisTemplate.opsForValue().set("num",++num);
        redisTemplate.delete("lock");
    }else{
        // 获取锁失败,0.1s后再获取
        try{
            Thread.sleep(100);
             testLock();
        }catch(...)...
    }
}

使用UUID,给锁唯一的标识:

X设置锁后,在手动释放前宕机。Y此时在X设置的锁自动释放后,上了锁。 X在功能恢复后,会将Y设置的锁释放。

为了避免这个问题的出现,应该给锁加uuid。

String uuid = new UUID.RandomUUID().toString();
Boolean lock = redisTemplage.opsForValue().setIfAbsent("lock",uuid,3,TimeUnit.SECONDS);
if(get(lock)==uuid)
redisTemplate.delete("lock");
else{
    ~~~
}

但删除操作缺少原子性 在删除时,锁过期自动释放。此时上锁后,锁会被前置的删除操作删除。

(共享锁的问题)

LUA脚本:

弱脚本在执行时,其他指令会等待。

所以用LUA脚本可以保证删除操作的原子性。

分布式锁需要满足

-互斥性。在任意时刻,只有一个客户端能持有锁。

-不会发生死锁。

-加锁和解锁过程必须有原子性。

上一篇
下一篇