您的位置:首页 > 数据库 > Redis

redis哨兵模式使用lua脚本实现分布式锁

2017-06-28 16:32 1061 查看
spring redis和redis包在设置key值的时候,都是先调用setnx设置值,成功就返回1,然后通过Expire设置超时时间,这样会出现一个
问题假如setnx成功,但是expire的时候,失败了,那么该值就会一直存在,这样会造成大的问题,这个问题怎么解决呢?我们可以通过redis lua脚本,让设置值和设置超时时间在redis服务端一次执行,就不会造成前面描述的问题。
下面是实现分布式锁的代码,采用的哨兵模式:
redis锁类代码:

package com.XXX.util.redis;

import java.util.Random;

import org.apache.log4j.Logger;

import com.XXX.util.Configure;

public class RedisLock {
private Logger log = Logger.getLogger(RedisLock.class);

public RedisLock() {

}

public RedisLock(String suffix) {
this.key = this.key + ":" + suffix;
}

private static final Configure INS = Configure.getInstans();

/**
* 默认超时时间(秒)
* 超时会抛弃当前锁里的所有线程
* 必要时需要补偿机制
*/
public static final long DEFAULT_TIMEOUT = Long.parseLong(INS.getValue("DEFAULT_TIMEOUT")) * 1000L;
/**
* 锁的超时时间(秒),过期删除
*/
public static final int LOCK_TIMEOUT = Integer.parseInt(INS.getValue("LOCK_TIMEOUT"));

private static final long ONE_MILLI_NANOS = 1000000L;
private static final long NANOS_TIMEOUT = DEFAULT_TIMEOUT * ONE_MILLI_NANOS;

// 锁状态标志
private boolean locked = false;
// 加锁标志
private static final String LOCKED = "TRUE";

private String key = "LOCK";
private RedisUtil redisUtil = RedisUtil.getInstance();

public static void test() {

}

private static final Random RANDOM = new Random();

public boolean lock() {
long nano = System.nanoTime();
try {
while ((System.nanoTime() - nano) < NANOS_TIMEOUT) {
if(redisUtil.setnxAndExpire(key, LOCKED, LOCK_TIMEOUT)) {
locked = true;

log.info("RedisLock lock : Get Lock key=" + key);

return locked;
}
// 短暂休眠,nano避免出现活锁
Thread.sleep(5, RANDOM.nextInt(500));
}
} catch (Exception e) {
log.error("RedisLock lock Get Lock Fail", e);
}
return false;
}

// 无论是否加锁成功,必须调用
public void unlock() {
if (locked) {
redisUtil.del(key);
log.info("RedisLock unlock : Del Lock key=" + key);
}
}

public static void main(String[] args) {
for(int i =0 ;i<100;i++){
RedisLock lock = new RedisLock("test1");
boolean lock1 = lock.lock();
System.out.println(lock1+""+i);
}
}
}

RedisUtil 工具类代码:

package com.XXX.util.redis;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.LoggerFactory;
import com.XXX.util.Configure;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
public class RedisUtil {
private static org.slf4j.Logger log = LoggerFactory.getLogger(RedisUtil.class);
private static final String Sentinel = "sentinel";

private static Configure INS = Configure.getInstans();

private static final String RedisModel = INS.getValue("redis.model");
private static final String SentinelsURL = INS.getValue("sentinel.urls");
private static final String RedisMaster = INS.getValue("redis.master");
private static final String RedisPassword = INS.getValue("redis.password");

private static final String MaxTotal = INS.getValue("redis.maxTotal");
private static final String MaxIdle = INS.getValue("redis.maxIdle");
private static final String MaxWait = INS.getValue("redis.maxWait");
private static final String MinEvictableIdle = INS.getValue("redis.minEvictableIdle");
private static final String NumTestsPerEvictionRun = INS.getValue("redis.numTestsPerEvictionRun");
private static final String TimeBetweenEvictionRunsMillis = INS.getValue("redis.timeBetweenEvictionRunsMillis");
private static final String BlockWhenExhausted = INS.getValue("redis.blockWhenExhausted");
//设置锁的lua脚本
private static final String SETNX_EXPIRE_SCRIPT = "if redis.call('setnx', KEYS[1], KEYS[2]) == 1 then\n"
+ "return redis.call('expire', KEYS[1], KEYS[3]);\n" + "end\n" + "return nil;";
private static RedisUtil redisUtil = new RedisUtil();

private JedisSentinelPool sentinelPool;

private RedisUtil() {
if (Sentinel.equals(RedisModel)) {
initSentinel();
}

}
public static RedisUtil getInstance() {
return redisUtil;
}

private void initSentinel() {
String[] url = SentinelsURL.split(",");

Set<String> sentinels = new HashSet<String>();
for (int i = 0; i < url.length; i++) {
sentinels.add(url[i]);
}
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(Integer.valueOf(MaxTotal));
poolConfig.setMaxIdle(Integer.valueOf(MaxIdle));
poolConfig.setMaxWaitMillis(Long.valueOf(MaxWait));
poolConfig.setMinEvictableIdleTimeMillis(Long.valueOf(MinEvictableIdle));
poolConfig.setNumTestsPerEvictionRun(Integer.valueOf(NumTestsPerEvictionRun));
poolConfig.setTimeBetweenEvictionRunsMillis(Long.valueOf(TimeBetweenEvictionRunsMillis));
poolConfig.setBlockWhenExhausted(Boolean.valueOf(BlockWhenExhausted));
poolConfig.setTestOnBorrow(true);
sentinelPool = StringUtils.isBlank(RedisPassword) ? new JedisSentinelPool(RedisMaster, sentinels, poolConfig)
: new JedisSentinelPool(RedisMaster, sentinels, poolConfig, RedisPassword);

log.info("init sentinelPool success,the sentinelPool=" + sentinelPool);
}

private Jedis getJedis() {
return getInstance().sentinelPool.getResource();
}

/**
* setnx带过期时间功能
*
* @param key
* 键名
* @param value
* 键值
* @param seconds
* 单位秒
* @see redis.clients.jedis.Jedis#setnx(String, String)
* @see redis.clients.jedis.Jedis#expire(String, int)
* @return 成功返回true,失败false
*/
public boolean setnxAndExpire(String key, String value, int seconds) {
Jedis redis = null;
try {
redis = getJedis();
Object result = redis.eval(SETNX_EXPIRE_SCRIPT, 3, key, value, seconds + "");
return result != null;
} catch (Throwable t) {
log.error("setnxAndExpire" + t.toString(), t);
return false;
} finally {
close(redis);
}
}
public Long del(String key) {
Jedis redis = null;
try {
redis = getJedis();
return redis.del(key);
} catch (Exception t) {
log.error("删掉key=" + key + "异常"+t.toString(), t);
return -1l;
} finally {
close(redis);
}
}

private void close(Jedis redis) {
if (redis != null) {
redis.close();
}
}

@Override
public String toString() {
return "RedisUtil [sentinelPool=" + sentinelPool + "]";
}

public static void main(String[] args) {
boolean b = RedisUtil.getInstance().setnxAndExpire("test", "true", 100);
System.out.println(b);
}
}
RedisUtil 配置文件:

#redis config
redis.model=sentinel
redis.master=st1
redis.password=
sentinel.urls=192.168.0.13:26379,192.168.0.13:26380,192.168.0.13:26381
redis.maxTotal=50
redis.maxIdle=10
redis.maxWait=20000
redis.minEvictableIdle=300000
redis.numTestsPerEvictionRun=3
redis.timeBetweenEvictionRunsMillis=60000
redis.blockWhenExhausted=true
#秒(s)
DEFAULT_TIMEOUT=30
#秒(s)
LOCK_TIMEOUT=2

pom配置:

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.6.2</version>
</dependency>

---------------------------------------------------------------------------版权声明------------------------------------------------------------------------------------------

版权声明:本文为博主原创文章,未经博主允许不得转载。博客地址:http://blog.csdn.net/mr_smile2014
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐