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

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



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值