redis分布式锁的实现方式

    前言:分布式锁的实现方式一般有三种,1:基于数据库的乐观锁。2:基于redis的分布式锁。3:基于zk的分布式锁,本文主要介绍第二种实现,由于以前一直是单机写笔记,所以第一次写有写的不好的地方欢迎大家指正。

    网上对于redis分布式锁的实现各有不同,今天分享的这种,不确定是不是最好的,但是个人觉得最易懂,好了废话不多说,贴公司错误例子跟正确写法。

//错误例子

public class RedisLock {

private static final Logger logger = LoggerFactory.getLogger(RedisLockNew.class);
private static final String REDIS_KEY_PREFIX = "redis_key_";
private static final String SET_IF_NOT_EXIST = "NX";//key不存在时,我们进行set操作;若key已经存在,则不做任何操作
private static final String SET_WITH_EXPIRE_TIME = "PX";//给key加一个过期的设置

@Autowired

private JedisCluster jedisCluster;

    /**
     * @description redis锁
     * @param key
     * @param seconds 锁过期时间(单位:秒)
     * @return
     */
public Boolean getLock(String key,Integer seconds) {
if(StringUtils.isBlank(key)){
logger.warn("key({}) cann't be blank(null or empty)!", key);
            return false;
}
String redis_key = REDIS_KEY_PREFIX+key;
String value = "1";//value可以任意,但是如果解锁方式是基于value的,可以设置成唯一标识
Boolean res;
try{
Integer failedStaus =  jedisCluster.ttl(redis_key).intValue();//判断是否过期失效
if(-1 == failedStaus){
jedisCluster.expire(redis_key, seconds);//重新设置过期时间
}
String result = jedisCluster.set(redis_key, value,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,(long) seconds);
res = JedisUtils.isStatusOk(result);
}catch(Exception e){
logger.error("setnx key:{},value:{},seconds:{}", new Object[]{redis_key, value, seconds, e});
            return false;
}
         return res;
}

/**
     * @description 释放锁
     * @param key
     * @param seconds 锁过期时间(单位:秒)
     * @return
     */
public void releaseLock(String key){
String redis_key = REDIS_KEY_PREFIX+key;
jedisCluster.del(redis_key);
logger.info("delete redis_key={}", redis_key);
}
}

 上面这段代码可以说写的很不严谨,而且有点冗余,相信细心的朋友已经发现一个问题了,如果存在两个客户端,客户端a在释放锁之前刚好锁失效了,客户端b获取了锁,然后a再执行del操作会导致a删了b的锁,所以这里删除锁的操作是不对的,还有一段超时的判断的代码并无实际意义,至少我看不出到底是什么意思。。。而且缺少重新获取锁的操作。。。,下面是我改造后的代码。

public class RedisLock {


private static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
private static final String LOCK_SUCCESS = "OK";
private static final String REDIS_KEY_PREFIX = "redis_key_";
private static final String SET_IF_NOT_EXIST = "NX";//key不存在时,我们进行set操作;若key已经存在,则不做任何操作

private static final String SET_WITH_EXPIRE_TIME = "PX";//给key加一个过期的设置

@Autowired
private JedisCluster jedisCluster;


/**
     * @description redis锁
     * @param key
     * @param seconds 锁过期时间(单位:秒)
     * @return
     */
public Boolean getNonBlockLock(String key,Integer seconds,String requestId) {
if(StringUtils.isBlank(key)){
logger.warn("key({}) cann't be blank(null or empty)!", key);
            return false;
}
String redis_key = REDIS_KEY_PREFIX+key;
String result = null;
try{
result = jedisCluster.set(redis_key, requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,(long) seconds);
if(LOCK_SUCCESS != result){//内部尝试获取锁
logger.info(">>>  没有获取到锁,正在尝试 。。。  <<<");
result = innerTryLock(redis_key, requestId,seconds);
}
}catch(Exception e){
logger.error("setnx key:{},value:{},seconds:{}", new Object[]{redis_key, requestId, seconds, e});
            return false;
}
return JedisUtils.isStatusOk(result);
}

/**
* @description 内部尝试获取锁
* @param redis_key
* @param value
* @param seconds
* @return
*/
public String innerTryLock(String redis_key,String requestId,Integer seconds){
String oldVal = jedisCluster.get(redis_key);
String result = "";
if(!StringUtils.isNotBlank(oldVal)){
result = jedisCluster.set(redis_key, requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,(long) seconds);
logger.info(">>>  尝试之后获取到了锁!   <<<");
}else{
logger.info(">>>  尝试之后没有获取到锁!   <<<");
}
return result;
}

/**
     * @description 释放锁
     * @param key
     * @param seconds 锁过期时间(单位:秒)
     * @return
     */
public void releaseLock(String key,String requestId){
/*String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(requestId));//luna脚本方式*/
String redis_key = REDIS_KEY_PREFIX+key;
if(requestId.equals(jedisCluster.get(redis_key))){
jedisCluster.del(redis_key);
logger.info("delete redis_key={}", redis_key);
}else{
logger.info("can not del lock cause it is being used,redis_key={}", redis_key);
}
}

}

如果有朋友想在本地做测试的话,我写了段粗略的测试代码,大家可以配合redis客户端进行测试。

public class testRedisLock {

public static void main(String[] args) {
Jedis jedisCluster=new Jedis("127.0.0.1",6379);
                                              //30 60 
Long setnx = jedisCluster.setnx("LOCK_NAME", String.valueOf(System.currentTimeMillis() + 60*1000));
        //获取锁成功
        if(setnx !=null && setnx.intValue()==1){
            System.out.println(">>>Task02成功获取到redis分布式锁<<<");
            System.out.println("执行业务>>>");
        }else {
        System.out.println("Task02>>>没有获取到锁,正在尝试<<<");
        String lockVal = jedisCluster.get("LOCK_NAME");//获取当前LOCK_NAME的时间
            //如果老的lockVal不为空,并且当前时间已经大于过期锁过期时间,则证明可以获取锁
            //100 
            if(lockVal !=null && System.currentTimeMillis()>Long.parseLong(lockVal)){
                //设置LOCK_NAME新的值,返回的是LOCK_NAME老的值
                String getOldSetVal = jedisCluster.getSet("LOCK_NAME", String.valueOf(System.currentTimeMillis() +60*1000));
                if(getOldSetVal ==null ||(getOldSetVal!=null && StringUtils.equals(lockVal,getOldSetVal))){
                System.out.println("Task02>>>尝试之后获取到了锁<<<");
                    //如果返回的getOldSetVal 为空则证明以前的锁已经被释放,则可以重新获取锁
                    //如果返回的getOldSetVal 不为空且lockVal == getOldSetVal ,返回的值和查询的时间过期值确实相等,那么证明我确实拿到这把锁
                System.out.println("执行业务>>>2<<<");
                }else{
                System.out.println("Task02>>>尝试之后没有获取到了锁>>>");
                }
            }else{
            System.out.println("Task02>>>尝试之后没有获取到了锁>>>");
            }
        }
        jedisCluster.close();
}

}

第一次写博客,还有很多不足,希望大家多多指正,一起进步。

猜你喜欢

转载自blog.csdn.net/weixin_36507118/article/details/81017584