分布式锁:用于解决多线程并发冲突问题。
思路:
①利用redis单线程模型。当想进行一个操作A时,先查看redis中是否具有个操作的标记位,如果没有,那么先在redis中存放标记位。这样有其他的线程操作A时,因为存在标记,那么只能等待或是丢弃。
②为避免锁得不到释放的情况。比如获取到锁的线程处理数据时出现问题,而导致锁不能释放的状况,应该对redis存放的标记设置过期时间。这样,如果线程出现问题,锁不能及时释放,标记位到期后也能自动释放。注意标记位的过期时间应该设置为大于操作的时间,否则锁的功能并不能生效;同时也要注意标记的过期时间不能设置过大,否则出现问题后,释放锁的时间会很长。
③如果一个线程进行操作A时,发现已经有标记位,那么应该具有重试功能,如果操作可以丢失,那么可以设置间隔时间,进行n次重试,如果操作不可丢失,那么设置while(true)循环。
④某线程操作A结束时,要及时释放锁。
工具类:
/**
* 利用redis获取分布式锁(未获取锁的请求,允许丢弃!)
*
* @param redisKey 锁的key值
* @param expireInSecond 锁的自动释放时间(秒)
* @return
* @throws Exception
*/
public String simpleLock(final String redisKey, final int expireInSecond) throws Exception {
String lockValue = UUID.randomUUID().toString();
boolean flag = false;
if (StringUtils.isEmpty(redisKey)) {
throw new Exception("key is empty!");
}
if (expireInSecond <= 0) {
throw new Exception("expireInSecond must be bigger than 0");
}
try {
for (int i = 0; i < retryCount; i++) {
boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey, lockValue, expireInSecond, TimeUnit.SECONDS);
if (success) {
flag = true;
break;
}
try {
TimeUnit.MILLISECONDS.sleep(waitIntervalInMS);
} catch (Exception ignore) {
logger.warn("redis lock fail: " + ignore.getMessage());
}
}
if (!flag) {
throw new Exception(Thread.currentThread().getName() + " cannot acquire lock now ...");
}
return lockValue;
} catch (Exception e) {
logger.warn("get redis lock error, exception: " + e.getMessage());
throw e;
}
}
/**
* 利用redis获取分布式锁(未获取锁的请求,将在timeoutSecond时间范围内,一直等待重试)
*
* @param redisKey 锁的key值
* @param expireInSecond 锁的自动释放时间(秒)
* @param timeoutSecond 未获取到锁的请求,尝试重试的最久等待时间(秒)
* @return
* @throws Exception
*/
public String lock(final String redisKey, final int expireInSecond, final int timeoutSecond) throws Exception {
String lockValue = UUID.randomUUID().toString();
boolean flag = false;
if (StringUtils.isEmpty(redisKey)) {
throw new Exception("key is empty!");
}
if (expireInSecond <= 0) {
throw new Exception("expireInSecond must be greater than 0");
}
if (timeoutSecond <= 0) {
throw new Exception("timeoutSecond must be greater than 0");
}
if (timeoutSecond >= expireInSecond) {
throw new Exception("timeoutSecond must be less than expireInSecond");
}
try {
long timeoutAt = System.currentTimeMillis() + timeoutSecond * 1000;
while (true) {
boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey, lockValue, expireInSecond, TimeUnit.SECONDS);
if (success) {
flag = true;
break;
}
if (System.currentTimeMillis() >= timeoutAt) {
break;
}
try {
TimeUnit.MILLISECONDS.sleep(waitIntervalInMS);
} catch (Exception ignore) {
logger.warn("redis lock fail: " + ignore.getMessage());
}
}
if (!flag) {
throw new Exception(Thread.currentThread().getName() + " cannot acquire lock now ...");
}
return lockValue;
} catch (Exception e) {
logger.warn("get redis lock error, exception: " + e.getMessage());
throw e;
}
}
/**
* 锁释放
*
* @param redisKey
* @param lockValue
*/
public void unlock(final String redisKey, final String lockValue) {
if (StringUtils.isEmpty(redisKey)) {
return;
}
if (StringUtils.isEmpty(lockValue)) {
return;
}
try {
String currLockVal = (String) redisTemplate.opsForValue().get(redisKey);
if (currLockVal != null && currLockVal.equals(lockValue)) {
boolean result = redisTemplate.delete(redisKey);
if (!result) {
logger.warn(Thread.currentThread().getName() + " unlock redis lock fail");
} else {
logger.info(Thread.currentThread().getName() + " unlock redis lock:" + redisKey + " successfully!");
}
}
} catch (Exception je) {
logger.warn(Thread.currentThread().getName() + " unlock redis lock error:" + je.getMessage());
}
}