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(); ListredisTime = 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); } } }