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>