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

来源:互联网 发布:管道保温计算软件 编辑:程序博客网 时间:2024/06/15 22:12

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();}}@Overridepublic 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 configredis.model=sentinelredis.master=st1redis.password=sentinel.urls=192.168.0.13:26379,192.168.0.13:26380,192.168.0.13:26381redis.maxTotal=50redis.maxIdle=10redis.maxWait=20000redis.minEvictableIdle=300000redis.numTestsPerEvictionRun=3redis.timeBetweenEvictionRunsMillis=60000redis.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



原创粉丝点击