redis 分布式锁

package com.mobile.web.portal.util.lock;

import redis.clients.jedis.Jedis;

public class JedisLock {

	private int timeoutMsecs;

	private String lockKey;

	private static long expireMsecs = 1000 * 60 * 1; // min 锁持有超时

	public JedisLock(String lockKey, Integer timeoutMsecs) {
		this.timeoutMsecs = timeoutMsecs;
		this.lockKey = lockKey;
	}

	public synchronized boolean acquire(Jedis jedis)
			throws InterruptedException {
		int timeout = timeoutMsecs;
		while (timeout >= 0) {
			long expires = System.currentTimeMillis() + expireMsecs + 1;
			String expiresStr = String.valueOf(expires); // 锁到期时间
			if (jedis.setnx(lockKey, expiresStr) == 1) {
				return true;
			}
			String currentValueStr = jedis.get(lockKey); // redis里的时间
			// 表示已经锁失效,要重新设置锁
			if (currentValueStr != null
					&& Long.parseLong(currentValueStr) < System
							.currentTimeMillis()) {
				// 判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
				// lock is expired
				String oldValueStr = jedis.getSet(lockKey, expiresStr);
				// 获取上一个锁到期时间,并设置现在的锁到期时间,;
				// 只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
				if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
					// 如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
					return true;
				}
			}
			timeout -= 100;
			Thread.sleep(100);
		}
		return false;
	}

	public void unLock(Jedis jedis) {
		jedis.del(lockKey);
	}
}

  

package com.mobile.web.portal.util.lock;

import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisShardInfo;

import com.zhicall.care.system.basic.BeanFactory;

public class RequestValidate {

	private RedisTemplate<Object, Object> redisTemplate = (RedisTemplate<Object, Object>) BeanFactory.getInstance().getBean("redisTemplate");
	
	private static final String ERR_MSG = "www!";
	private static final int TIMEOUTMSECS = 300;
	/**
	 * 使用redis 分布式锁对请求验证
	 * 		
	 * @return
	 */
	public String validate(long a, String b){
		String lockKey = a+":"+b+"_lock";
		int timeoutMsecs = TIMEOUTMSECS;
		JedisLock lock = new JedisLock(lockKey, timeoutMsecs);
		Jedis jedis = getJedis();
		try {
			boolean flag = lock.acquire(jedis);
			if(!flag){
				return ERR_MSG;
			}
		} catch (InterruptedException e) {
			return ERR_MSG;
		}finally {
			jedis.disconnect();
		}
		return null;
	}
	
	private Jedis getJedis(){
		JedisConnectionFactory factory = (JedisConnectionFactory)redisTemplate.getConnectionFactory();
		JedisShardInfo shardInfo = factory.getShardInfo();
		Jedis jedis = shardInfo.createResource();
		return jedis;
	}
	
	public void unLock(long a, String b){
		String lockKey = a+":"+b+"_lock";
		int timeoutMsecs = TIMEOUTMSECS;
		JedisLock lock = new JedisLock(lockKey, timeoutMsecs);
		Jedis jedis = getJedis();
		try {
			lock.unLock(jedis);
		}finally {
			jedis.disconnect();
		}
	}
}

  

 

-------------------------------------------------------------------------------------------------------------------------

更新

新的redis分布式锁,基于redis2.6版本以上

package com.mobile.web.util.redis;

import java.util.Collections;

import redis.clients.jedis.Jedis;

/**
 * 新的Redis分布式锁实现
 * 基于jedis版本2.8.0以上
 * @author lsnBin
 * @date 2018/01/17
 */
public class JedisTool {

	private static final String LOCK_SUCCESS = "OK";
	
	/** 即当key不存在时,我们进行set操作  */
	private static final String SET_IF_NOT_EXIST = "NX";
	
	private static final String SET_WITH_EXPIRE_TIME = "PX";
	
	private static final Long RELEASE_SUCCESS = 1L;
	
	/**
	 * 尝试获取分布式锁
	 * @param jedis Redis客户端
	 * @param lockKey 锁
	 * @param requestId 请求标识
	 * @param expireTime 超期时间
	 * @return 是否获取成功
	 */
	public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
		
		String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
		
		if (LOCK_SUCCESS.equals(result)) {
			return true;
		}
		
		return false;
	}
	
	/**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

    	//Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行
    	//首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。
    	//eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。
    	
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        
        return false;

    }
}

  

替换原因

 1、释放锁存在:直接使用redis.del()方法删除锁,不限判断拥有者而直接解锁的方式,会导致任何客户端都可以随时解锁,即时锁不是它的。
 2、获取锁存在:
2.1:由于是客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步.
 2.2:当锁过期的时候,如果多个客户端同时执行jedis.getSet()方法,那么虽然最终只有一个客户端可以加锁,但是这个客户端的锁的过期时间可能被其他客户端覆盖。
 2.3:锁不具备拥有者标识,即任何客户端都可以解锁。

posted @ 2016-12-29 09:32  斌灬小生不才  阅读(199)  评论(0编辑  收藏  举报