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);
}
}

 上面这段代码缺少重新获取锁的操作,而且锁续期的操作是非原子性的,会出现两个线程拿到锁的情况,下面是我改造后的代码:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;



@Component
public class RedisLock implements Lock {


    @Autowired
    private JedisPool jedisPool;

    private static final String key="lock";

    private ThreadLocal<String> threadLocal=new ThreadLocal<>();

    private static AtomicBoolean isHappened = new AtomicBoolean(true);

    //加锁
    @Override
    public void lock() {
        boolean b = tryLock();  //尝试加锁
        if(b){
            //拿到了锁
            return;
        }
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    //尝试加锁
    @Override
    public boolean tryLock() {
        SetParams setParams=new SetParams();
        setParams.ex(2);  //2s,防止死锁
        setParams.nx();
        String s = UUID.randomUUID().toString();
        Jedis resource = jedisPool.getResource();
        String lock = resource.set(key, s,setParams);
//        String lock = resource.set(key,s,"NX","PX",5000);
        resource.close();
        if("OK".equals(lock)){
            //拿到了锁
            threadLocal.set(s);
            // 只开一次守护线程
            if(isHappened.get()){
                // 设置一个看门狗(守护线程),无限给锁续期
                ThreadUtil.newThread(new MyRUnble(jedisPool)).start();
                isHappened.set(false);
            }
            return true;
        }
        return false;
    }



    static class MyRUnble implements Runnable{

        private JedisPool jedisPool;
        public MyRUnble(JedisPool jedisPool){
            this.jedisPool=jedisPool;
        }
        @Override
        public void run() {
            Jedis jedis = jedisPool.getResource();
            while (true){
                Long ttl = jedis.ttl(key);
                if(ttl!=null && ttl>0){
                    jedis.expire(key, (int) (ttl+1));
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }


    //第一步判断设置时候的value 和 此时redis的value是否相同
    //解锁
    @Override
    public void unlock() throws Exception{
//        String script="if redis.call(\"get\",KEYS[1])==ARGV[1] then\n" +
//                "    return redis.call(\"del\",KEYS[1])\n" +
//                "else\n" +
//                "    return 0\n" +
//                "end";

        Jedis resource = jedisPool.getResource();
        //直接delete,如果key刚好过期了,另一个线程得到了key那就会被马上删除
        //但是还是不能保证原子性
//        if(resource.get(key).equals(threadLocal.get())){
//            resource.del(key);
//        }
        // 设置了永不过期,上述问题不会出现
        resource.del(key);

//        Object eval = resource.eval(script, Arrays.asList(key), Arrays.asList(threadLocal.get()));
//        if(Integer.valueOf(eval.toString())==0){
//            resource.close();
//            // 出现这种情况,可能出现两个线程拿到锁,执行逻辑,此时应该回滚,及锁过期了,但是业务还没执行完
//            // 解决方案可以给锁续期。使用守护线程,定时给锁续期
//            throw new Exception("解锁失败");
//        }
        resource.close();
    }

    @Override
    public Condition newCondition() {
        return null;
    }

}

 

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

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值