redis分布式锁需要注意的有两点:
一、加锁时要给锁(redis key)设置超时时间,防止程序因为意外停止,没有及时释放锁,而造成死锁。对于加锁和设置锁有效期,由于操作为两步操作,为保证操作的原子性,必须同步完成,防止极端情况,加锁成功未设置锁有效期,程序突然退出,造成死锁。使用jedis.set(lockKey, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);实现。
二、解锁必须判定权限,通过设置一个uuid来实现,检查uuid一致才能认定该线程可以对锁进行解锁,防止B线程将A线程设置的锁进行了解锁操作。检查uuid与解锁两步同样要保证操作原子性,防止出现A线程检查时uuid相同但是还未解锁前,锁恰好过期,B线程获取到锁,A线程才开始解锁(因为之前A线程已经通过了权限uuid的校验,所以允许解锁),造成错误(情况极端,但是在网络不稳定情况下是可能出现的),A线程将属于B线程的锁释放掉了。为保证解锁时两个步骤操作的原子性,使用lua脚本来实现。 下面是示例代码:
package com.lingli.jewelry.tools.lock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import java.util.Collections;
/**
* @author anjl
* @date 11:30 2018/10/9
* description:
*/
@Component
public class RedisLock {
/**
* log
*/
private Logger logger = LoggerFactory.getLogger(RedisLock.class);
/**
* 防止出现死锁,设置锁超时时间
*/
private static final int EXPIRE_TIME = 10 * 1000;
/**
* 线程获取锁失败后,每隔20毫秒再次尝试获取锁
*/
private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 20;
/**
* 锁等待时间,防止线程饥饿,超时还是无法获取锁,则结束
*/
private static final int TIMEOUT_MACES = 11 * 1000;
/**
* 释放锁lua脚本
*/
private static final String SCRIPT;
static {
SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] "
+ "then return redis.call('del', KEYS[1]) "
+ "else return 0 end";
}
public boolean lock(Jedis jedis, String lockKey, String lockValue) throws InterruptedException {
return lock(jedis, lockKey, lockValue, EXPIRE_TIME);
}
private boolean lock(Jedis jedis, String lockKey, String lockValue, int expireTime) throws InterruptedException {
int timeout = TIMEOUT_MACES;
//key添加_LOCK后缀,区分不同
lockKey += LOCK;
while (timeout >= 0) {
if (tryLock(jedis, lockKey, lockValue, expireTime)) {
logger.debug("get lock success! lockKey = {}", lockKey);
return true;
}
timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}
logger.warn("get redis lock timeout, lockKey = {}", lockKey);
return false;
}
/**
* 释放锁
*
* @param lockKey lockKey
* @param value value
* @return boolean
*/
@SuppressWarnings("all")
public boolean releaseLock(Jedis jedis, String lockKey, String value) {
lockKey += LOCK;
Object result = jedis.eval(SCRIPT, Collections.singletonList(lockKey), Collections.singletonList(value));
if (RELEASE_SUCCESS.equals(result)) {
logger.debug("release lock success!,lockKey = {} value = {}", lockKey, value);
return true;
}
logger.warn("release lock failure!,lockKey = {} value = {}", lockKey, value);
return false;
}
/**
* 获取锁
*
* @param lockKey lockKey
* @param value value
* @param expireTime:单位-秒
* @return boolean
*/
private boolean tryLock(Jedis jedis, String lockKey, String value, int expireTime) {
String result = jedis.set(lockKey, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
return LOCK_SUCCESS.equals(result);
}
private static final String LOCK_SUCCESS = "OK";
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;
private static final String LOCK = "_LOCK";
}