问题假如setnx成功,但是expire的时候,失 败了,那么该值就会一直存在,这样会造成大的问题, 这个问 题怎么解决呢?spring redis和redis包在设置key值的时候,都是先调用setnx设置值,成功就返回1,然后通过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