基于Redis实现分布式锁

目录

一、使用set key value ex time nx 命令

二、使用set key value ex time nx加(UUID作为不同线程的标识) 命令

三、解决Redis锁的误删问题

四、Lua脚本解决原子问题


一、使用set key value ex time nx 命令

通过redisTempate的opsForValue方法设置key值,并以当前的线程id作为值

    public static final String KEY_PREFIX="lock:";
    public boolean getLock(long timeoutSec) {
        long id = Thread.currentThread().getId();
        Boolean result = redisTemplate.opsForValue()
        .setIfAbsent(name,id+"", timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(result);
    }
    public void unLock() {
        redisTemplate.delete(KEY_PREFIX+name);
    }

这种方式能够解决一些情况下的线程安全问题,但是可能会有新的问题。

二、使用set key value ex time nx加(UUID作为不同线程的标识) 命令

在集群模式下,会有多个jvm,很可能会出现线程id冲突的情况,需要加入UUID来更好的区分不同服务的线程。我用的是hutool工具生成的UUID。

    public static final String KEY_PREFIX="lock:";
    public static final String ID_PREFIX= UUID.randomUUID().toString(true)+"-";
    public boolean getLock(long timeoutSec) {
        String threadId = ID_PREFIX+Thread.currentThread().getId();
        Boolean result = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(result);

    }
    public void unLock() {
        redisTemplate.delete(KEY_PREFIX+name);
    }

当这个redis的key时间设置过短,导致过期时间到了自动删除。但是如果这时业务还没有执行完,新的进程会进来。当业务执行完毕后,会删除新线程的key值也就是释放的不是自己线程的锁。这就要使用第三种方式来改进。

三、解决Redis锁的误删问题

在释放锁之前判断是不是当前线程所创建的锁。

    public void unLock() {
        String threadId = ID_PREFIX+Thread.currentThread().getId();
        String id = (String)redisTemplate.opsForValue().get(KEY_PREFIX + name);
        if(threadId.equals(id)) {
            redisTemplate.delete(KEY_PREFIX + name);
        }
    }

这种解决还是不够完美,因为可能有一种很小的概率。当一个线程进入判断后准备释放锁的时候,遇到了线程阻塞,(jvm的GC机制),而在阻塞时间的过程中,触发了锁的超时释放。这时另一个线程拿到了锁,但是第一个线程阻塞完毕,释放锁,结果就是把第二个线程的锁给释放了,第二个业务还没结束,这时第三个线程又进来,导致并发问题。

产生的原因是,判断和删除这两个操作可以被打断,想要不被打断就要让这两个指令为一个原子操作。这里我用到的是lua脚本解决

四、Lua脚本解决原子问题

unlock.lua

Lua脚本在Redis中执行具有一定的原子性。

if(redis.call('get',KEY[1])==AVG[1]) then
    return  redis.call('del',KEY[1])
end
return 0
    public void unLock() {
        String threadId = ID_PREFIX+Thread.currentThread().getId();
        String id = (String)redisTemplate.opsForValue().get(KEY_PREFIX + name);
        redisTemplate.execute(UNLOCKSCRIPT, Collections.singletonList(id),threadId);
    }

以上的解决方案可以解决大多数的问题,比如满足了互斥,安全性,高可用性等。

但是还有一些问题,比如不可重入,同一个线程无法多次获取同一个锁

不可重试,获取锁时只发送一次请求就结束了,没有重试机制

超时释放,可能出现业务还没执行完,锁就释放的情况

主从一致性,当redis集群配置了主从一致,主节点宕机,新的从节点变为了主节点。但是主节点的锁数据还没有同步到从节点,导致新的线程进入拿到锁,导致并发问题。

解决办法就是使用Redisson框架,具体看这篇文章

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值