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:锁不具备拥有者标识,即任何客户端都可以解锁。