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