基于redis的分布式锁

redis分布式锁需要注意的有两点:

一、加锁时要给锁(redis key)设置超时时间,防止程序因为意外停止,没有及时释放锁,而造成死锁。对于加锁和设置锁有效期,由于操作为两步操作,为保证操作的原子性,必须同步完成,防止极端情况,加锁成功未设置锁有效期,程序突然退出,造成死锁。使用jedis.set(lockKey, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);实现。

二、解锁必须判定权限,通过设置一个uuid来实现,检查uuid一致才能认定该线程可以对锁进行解锁,防止B线程将A线程设置的锁进行了解锁操作。检查uuid与解锁两步同样要保证操作原子性,防止出现A线程检查时uuid相同但是还未解锁前,锁恰好过期,B线程获取到锁,A线程才开始解锁(因为之前A线程已经通过了权限uuid的校验,所以允许解锁),造成错误(情况极端,但是在网络不稳定情况下是可能出现的),A线程将属于B线程的锁释放掉了。为保证解锁时两个步骤操作的原子性,使用lua脚本来实现。 下面是示例代码:

package com.lingli.jewelry.tools.lock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import java.util.Collections;

/**
 * @author anjl
 * @date 11:30 2018/10/9
 * description:
 */
@Component
public class RedisLock {

    /**
     * log
     */
    private Logger logger = LoggerFactory.getLogger(RedisLock.class);
    /**
     * 防止出现死锁,设置锁超时时间
     */
    private static final int EXPIRE_TIME = 10 * 1000;
    /**
     * 线程获取锁失败后,每隔20毫秒再次尝试获取锁
     */
    private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 20;
    /**
     * 锁等待时间,防止线程饥饿,超时还是无法获取锁,则结束
     */
    private static final int TIMEOUT_MACES = 11 * 1000;
    /**
     * 释放锁lua脚本
     */
    private static final String SCRIPT;

    static {
        SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] "
                + "then return redis.call('del', KEYS[1]) "
                + "else return 0 end";
    }

    public boolean lock(Jedis jedis, String lockKey, String lockValue) throws InterruptedException {
        return lock(jedis, lockKey, lockValue, EXPIRE_TIME);
    }

    private boolean lock(Jedis jedis, String lockKey, String lockValue, int expireTime) throws InterruptedException {
        int timeout = TIMEOUT_MACES;
        //key添加_LOCK后缀,区分不同
        lockKey += LOCK;
        while (timeout >= 0) {
            if (tryLock(jedis, lockKey, lockValue, expireTime)) {
                logger.debug("get lock success! lockKey = {}", lockKey);
                return true;
            }
            timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
            Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
        }
        logger.warn("get redis lock timeout, lockKey = {}", lockKey);
        return false;
    }

    /**
     * 释放锁
     *
     * @param lockKey lockKey
     * @param value value
     * @return boolean
     */
    @SuppressWarnings("all")
    public boolean releaseLock(Jedis jedis, String lockKey, String value) {
        lockKey += LOCK;
        Object result = jedis.eval(SCRIPT, Collections.singletonList(lockKey), Collections.singletonList(value));
        if (RELEASE_SUCCESS.equals(result)) {
            logger.debug("release lock success!,lockKey = {} value = {}", lockKey, value);
            return true;
        }
        logger.warn("release lock failure!,lockKey = {} value = {}", lockKey, value);
        return false;
    }

    /**
     * 获取锁
     *
     * @param lockKey lockKey
     * @param value value
     * @param expireTime:单位-秒
     * @return boolean
     */
    private boolean tryLock(Jedis jedis, String lockKey, String value, int expireTime) {
        String result = jedis.set(lockKey, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        return LOCK_SUCCESS.equals(result);
    }

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;
    private static final String LOCK = "_LOCK";

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值