redis实现分布式锁

阅读更多
package com.dev.tool.log.service;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisDataException;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Created by kenny.dong on 2018/3/31.
 */
public class JedisClientTest {
    private final Logger logger = LoggerFactory.getLogger(JedisClientTest.class);
   /**
            * 缓存host
    */
    private String redisHost="xxx";

    /**
     * 缓存端口
     */
    private int  redisPort=6379;

    /**
     * 最大活跃数
     */
    private int maxActive=500;
    /**
     * 最大空闲数
     */
    private int maxIdle=200;
    /**
     * 最大等待时间 毫秒
     */
    private int maxWait=1000;
    /**
     * 获取前是否检测
     */
    private boolean testOnBorrow=true;

    /**
     * 超时时间
     */
    private int timeOut=1000;

    //访问密码
    private String             AUTH           = null;

    private JedisPool jedisPool=null;

    /** 纳秒 */
    private static final long MICRO_SECOND = 1000 * 1000L;

    /**
     * 参考资料:https://blog.csdn.net/jj546630576/article/details/74910343
     * 释放资源,如果是Jedis3.0 以下版本,请注意returnResource用法。
     *参考资料:https://www.cnblogs.com/wangxin37/p/6397783.html
     */
    @Test
    public void distributedLock() throws InterruptedException {
        initJedisPool();
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 4; i++) {//5个任务
            exec.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        String key = "kennyJedisTest";
                        acquireLock(key,3);
                        TimeUnit.MILLISECONDS.sleep(1);
                        releaseLock(key);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        exec.shutdown();  //关闭线程池
        Thread.currentThread().sleep(10000);
    }

    private JedisPool initJedisPool(){
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxActive);
        config.setMaxIdle(maxIdle);
        config.setMaxWaitMillis(maxWait);
        config.setTestOnBorrow(testOnBorrow);
        jedisPool = new JedisPool(config, redisHost, redisPort, timeOut, AUTH);
        return jedisPool;
    }

    /**
     * 获得分布式锁
     * @param key
     * @param timeout
     * @return
     * @throws Exception
     */
    public boolean acquireLock(final String key, final long timeout) throws Exception {
        Assert.notNull(key, "key required");
        Assert.notNull(key, "timeout required");
        try {
            /**
             * 执行过程:
             * 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁
             * 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值
             */
            long wait = timeout * 1000;
            while (wait>0) {
                long currentRedisTime = getTime(key);
                long expireTime = timeout * MICRO_SECOND + currentRedisTime + 1;
                logger.info("{}线程 {} begin..., expire:{},redisTime:{}", Thread.currentThread().getName(),key, expireTime,currentRedisTime);
                if (setnx(key, String.valueOf(expireTime)) == 1) {
                    logger.info("{}线程 {} 获得了锁, expire:{}",Thread.currentThread().getName(), key, expireTime);
                    return true;
                }
                String existValue = get(key);//获得已存在的key值的value
                if (!StringUtils.isEmpty(existValue) && NumberUtils.parseNumber(existValue,Long.class) <= currentRedisTime) {//value代表的锁时间超时
                    // 设置现在的锁到期时间,并返回上一个锁到期时间
                    String oldValue = getSet(key, String.valueOf(expireTime));
                    if (NumberUtils.parseNumber(oldValue,Long.class) < currentRedisTime) {
                        logger.info("{}线程 {}获得了锁,oldValue: {}, expire:{},redisTime:{}", Thread.currentThread().getName(),key, oldValue, expireTime,currentRedisTime);
                        return true;
                    }
                }
                logger.info("{}线程 请求{}阻塞中,循环遍历",Thread.currentThread().getName(), key);
                /**
                 * 使用随机时间100 <= x < 150,可以防止饥饿进程的出现,即,当同时到达多个进程,
                 * 只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些线程,也以同样的频率申请锁,这将可能导致前面来的锁得不到满足.
                 * 使用随机的等待时间可以一定程度上保证公平性
                 */
                long randomWait = Math.round(Math.random() * (150 - 100)) + 100;
                TimeUnit.MILLISECONDS.sleep(randomWait);
                wait -= randomWait;
            }
        } catch (Throwable e) {
            logger.error("{}线程 acquire lock error : " + e.getMessage(), Thread.currentThread().getName(),e);
            throw new Exception(e);
        }
        return false;
    }

    /**
     * 释放锁
     * @param key
     */
    public void releaseLock(String key) {
        try {
            if (!StringUtils.isEmpty(key) && del(key) == 1) {
                logger.info("{}线程 释放了锁,key is {}",Thread.currentThread().getName(), key);
            }
        } catch (Exception e) {
            logger.error("{}线程 release lock error :" + e.getMessage(),Thread.currentThread().getName(), e);
        }
    }

    /**
     * 在redis取得当前时的方法为执行time命令
           127.0.0.1:6382> time
          1) "1495780564"
          2) "894089"
          第一行为以 UNIX 时间戳格式表示已经过去的秒数
          第二行为当前这一秒已经过去的微秒数
     * @param key
     * @return
     */
    public Long getTime(String key) {
        //boolean broken = false;
        Jedis jedis = null;
        Long currentRedisTime = null;
        try {
            jedis = jedisPool.getResource();
            List redisTime = jedis.time();
            currentRedisTime = NumberUtils.parseNumber(redisTime.get(0), Long.class) * 1000000L +  NumberUtils.parseNumber(redisTime.get(1), Long.class);
        } catch (Exception e) {
            logger.error("redis connect error,key:" + key, e);
            if (jedis != null) {
                 jedis.close();
            }
            //broken = handleJedisException(e);
            throw e;
        } finally {
            if (jedis != null) {
                 jedis.close();
            }
            //closeResource(jedis,broken);
        }
        return currentRedisTime;
    }

    /**
     * 不存在时设置缓存
     * @param key
     * @param value
     * @return 1 if the key was set 0 if the key was not set
     */
    public Long setnx(String key, String value) {
        Jedis jedis=null;
        Long returnLong=null;
        //boolean broken = false;
        try{
            jedis=jedisPool.getResource();
            returnLong= jedis.setnx(key, value);
        }catch (Exception e){
            logger.error("redis_setnx error,key:" + key, e);
            if(jedis!=null){
                jedis.close();
            }
            //broken = handleJedisException(e);
            throw e;
        }finally {
            if(jedis!=null){
                jedis.close();
            }
            //closeResource(jedis,broken);
        }
        return returnLong;
    }

    public String get(String key){
        //boolean broken = false;
        Jedis jedis=null;
        String returnStr=null;
        try{
            jedis=jedisPool.getResource();
            returnStr= jedis.get(key);
        }catch (Exception e){
            logger.error("redis_get error,key:" + key, e);
            if(jedis!=null){
                jedis.close();
            }
           // broken = handleJedisException(e);
            throw e;
        }finally {
            if(jedis!=null){
                jedis.close();
            }
            //closeResource(jedis,broken);
        }
        return returnStr;
    }

    /**
     * set并返回旧值
     * @param key
     * @param value
     * @return
     */
    public String getSet(String key, String value) {
        Jedis jedis=null;
        String returnStr=null;
       // boolean broken = false;
        try{
            jedis=jedisPool.getResource();
            returnStr= jedis.getSet(key, value);
        }catch (Exception e){
            logger.error("redis_getSet error,key:" + key + " value:" + value, e);
            if(jedis!=null){
                jedis.close();
            }
           // broken = handleJedisException(e);
            throw e;
        }finally {
            if(jedis!=null){
                jedis.close();
            }
            //closeResource(jedis,broken);
        }
        return returnStr;
    }

    public Long del(String key){
        Jedis jedis=null;
        Long returnLong=null;
        //boolean broken = false;
        try{
            jedis=jedisPool.getResource();
            returnLong= jedis.del(key);
        }catch (Exception e){
            logger.error("redis_del error,key:" + key, e);
            if(jedis!=null){
               jedis.close();
            }
            //broken = handleJedisException(e);
            throw e;
        }finally {
            if(jedis!=null){
                jedis.close();
            }
            //closeResource(jedis,broken);
        }
        return returnLong;
    }

    /**
     * Handle jedisException, write log and return whether the connection is broken.
     */
    protected boolean handleJedisException(Exception jedisException) {
        if (jedisException instanceof JedisConnectionException) {
            logger.error("Redis connection  lost.", jedisException);
        } else if (jedisException instanceof JedisDataException) {
            if ((jedisException.getMessage() != null) && (jedisException.getMessage().indexOf("READONLY") != -1)) {
                logger.error("Redis connection are read-only slave.", jedisException);
            } else {
                // dataException, isBroken=false
                return false;
            }
        } else {
            logger.error("Jedis exception happen.", jedisException);
        }
        return true;
    }
    /**
     * Return jedis connection to the pool, call different return methods depends on the conectionBroken status.
     */
    protected void closeResource(Jedis jedis, boolean conectionBroken) {
        try {
            if (conectionBroken) {
                jedisPool.returnBrokenResource(jedis);
            } else {
                jedisPool.returnResource(jedis);
            }
        } catch (Exception e) {
            logger.error("return back jedis failed, will fore close the jedis.", e);
            //JedisUtils.destroyJedis(jedis);
        }
    }
}

 

你可能感兴趣的:(分布式锁,redis)