redis分布锁的一些问题解析

我在使用redis分布式锁的时候,遇到过的一些问题,如下:

加锁

解锁实现方式一:

/**
 * @param key   锁的key,全局唯一的key
 * @param value 锁的value,开始value和key值一样
 * @return 加锁成功或失败
 **/
public boolean tryLock(String key, String value) {
	// 第一步设置key,如果存在就设置失败,加锁失败
    Long set = jedisCluster.setnx(key, value);
    if (1 == set) {
    	// 第二步,设置key的过期时间
        jedisCluster.expire(key, 10);
        return true;
    }

    return false;
}

1、这种写法的问题就是不是原子性的,如果服务A在执行到第一步过后,服务挂了,那么就会造成锁没有设置过期时间,就一直存在,其他服务就拿不到锁了,会造成“死锁”;

2、同时也还会有另外一个问题,就是value和key一样,这种情况会导致其他服务解锁非自己服务的锁。应该要遵循“解铃还须系铃人”,谁加的锁应该就由谁来解。解决办法就是value设为当前服务请求的全局唯一requestId。

解锁实现方式二:

/**
 * @param key   锁的key,全局唯一的key
 * @param value 锁的value,全局唯一的requestId
 * @return 加锁成功或失败
 */
public boolean lock(String key, String value) {
    return redisTemplate.opsForValue().setIfAbsent(key, value, 10, TimeUnit.SECONDS);
}

这种实现方式解决上面方式一的非原子性问题;但是也还是会有一种情况下会有问题:
如果服务逻辑处理时间比较长,还没有处理完,这个时候key的过期时间已经到了,其他服务就能拿到锁了,可能会造成数据的一致性问题。
解决方案,可以通过过期时间续约的方式,监听过期时间,如果逻辑还没有处理完,就给过期时间续约。

解锁

解锁实现方式一

/**
 * @param key 锁key
 * @return 解锁是否成功
 */
public boolean unLock(String key) {
    Long del = jedisCluster.del(key);
    if (1 == del) {
        return true;
    }

    return false;
}

问题:
这种解锁方式的问题,在于在直接了,会导致误删不是自己加的锁;改进方式如下:

解锁方式二

/**
 * @param key   锁key
 * @param value 锁的value,全局唯一的requestId
 * @return 解锁是否成功
 */
public boolean unLock(String key, String value) {
	// 第一步比较value
    if (value.equals(jedisCluster.get(key))) {
    	// 第二步删除key
        Long del = jedisCluster.del(key);
        if (1 == del) {
            return true;
        }
    }

    return false;
}

这种方式解决上面的问题,但是不是原子性的;在第一步服务挂了,过后就会解锁失败了。

改进如下(可以利用lue脚本,redis执行时原子性的):
正确的解锁方式

/**
 * 正确解锁
 *
 * @param key       锁key
 * @param requestId 锁value
 * @return 解锁是否成功
 */
public boolean unLock(String key, String requestId) {
    String lue = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    Object result = jedisCluster.eval(lue, Collections.singletonList(key), Collections.singletonList(requestId));

    if (1L == result) {
        return true;
    }
    return false;
}

好了以上就是遇到的一些问题,如果有不对的理解有误差希望可以指出,互相学习!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值