lua腳本能保證redis的原子性操作,redis使用springboot的redistemplate
/**
* create by abel
* create date 2018/11/16 11:28
* describe:請輸入項目描述
*/
public class RedisLockService {
private static Logger logger = LoggerFactory.getLogger(RedisLockService.class);
private RedisTemplate<String, Object> redisTemplate;
private static final Long SUCCESS = 1L;
/**
* 鎖的key
*/
private String lockKey;
/**
* 預設過期時間/ms
*/
private int expireTime = 30 * 1000;
/**
* 預設重試時間/ms
*/
private int retryTime = 60 * 1000;
/**
* 預設睡眠時間/ms
*/
private int sleepTime = 200;
/**
* uuid
*/
private static final String requestId = UUID.randomUUID().toString().replace("-", "");
/**
* 是否鎖定标志
*/
private volatile boolean locked = false;
/**
* 構造器
*
* @param redisTemplate
* @param lockKey 鎖的key
*/
public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey;
}
/**
* 構造器
*
* @param redisTemplate
* @param lockKey 鎖的key
* @param retryTime 擷取鎖的逾時時間
*/
public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey, int retryTime) {
this(redisTemplate, lockKey);
this.retryTime = retryTime;
}
/**
* 構造器
*
* @param redisTemplate
* @param lockKey 鎖的key
* @param retryTime 擷取鎖的逾時時間
* @param expireTime 鎖的有效期
*/
public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey, int retryTime, int expireTime) {
this(redisTemplate, lockKey, retryTime);
this.expireTime = expireTime;
}
/**
* 構造器
*
* @param redisTemplate
* @param lockKey 鎖的key
* @param retryTime 擷取鎖的逾時時間
* @param expireTime 鎖的有效期
* @param sleepTime 鎖的睡眠期
*/
public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey, int retryTime, int expireTime, int sleepTime) {
this(redisTemplate, lockKey, retryTime, expireTime);
this.sleepTime = sleepTime;
}
public String getLockKey() {
return lockKey;
}
/**
* 擷取鎖
*
* @return
*/
private boolean lock() {
try {
long startTime = System.currentTimeMillis();
while (true) {
if (this.setNx()) {
locked = true;
logger.info("[" + lockKey + "]redis get lock success");
return true;
}
//這一步是key存在傳回false時執行,原因可能是key未删除或者未到過期時間
//在這個時間(retryTime)内,可以重試多次,如果這個時間内鎖還未釋放,那麼需要主動釋放
if (System.currentTimeMillis() - startTime > retryTime) {
locked = false;
this.del();
logger.info("[" + lockKey + "]redis get lock error");
return false;
}
Thread.sleep(sleepTime);
}
} catch (InterruptedException e) {
logger.error("[" + lockKey + "]redis lock error::{}", e);
locked = false;
return false;
}
}
/**
* 釋放鎖
*
* @return
*/
private boolean unLock() {
try {
//删除key
boolean delFlag = this.del();
logger.info("[" + lockKey + "]redis unlock success->" + delFlag);
return delFlag;
} catch (Exception e) {
logger.error("[" + lockKey + "]redis unlock error::{}", e);
return false;
}
}
/**
* lua腳本執行能保證原子性:
* 1.如果key不存在,設定key成功,同時設定過期時間,傳回true;
* 2.如果key存在,設定key失敗,傳回false;
*
* @return true-成功,false-失敗
*/
private boolean setNx() {
String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then " +
" if redis.call('get',KEYS[1])==ARGV[1] then " +
" return redis.call('expire',KEYS[1],ARGV[2]) " +
" else " +
" return 0 " +
" end " +
"end";
RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
//對非string類型的序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId, String.valueOf(expireTime));
return SUCCESS.equals(result);
}
/**
* 删除key
*
* @return true-成功,false-失敗
*/
private boolean del() {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then " +
" return redis.call('del', KEYS[1])" +
"else " +
" return 0 " +
"end";
RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
return SUCCESS.equals(result);
}
/**
* 匿名類包裝:無傳回值
*
* @param runnable 需要鎖住的運作代碼
* @return true 獲鎖成功;false 獲鎖失敗
*/
public boolean wrap(Runnable runnable) {
if (lock()) {
try {
runnable.run();
} catch (Exception e) {
logger.error(e.getMessage(), e);
if (e instanceof BusinessException)
throw (BusinessException) e;
else
throw new BusinessException(ErrorCode.SERVER_BUSY, e.getMessage());
} finally {
unLock();
}
return true;
} else
return false;
}
/**
* 匿名類包裝:帶傳回值
*/
public <V> V wrap(Callable<V> callable) {
if (lock()) {
try {
return callable.call();
} catch (Exception e) {
logger.error(e.getMessage(), e);
if (e instanceof BusinessException)
throw (BusinessException) e;
else
throw new BusinessException(ErrorCode.SERVER_BUSY, e.getMessage());
} finally {
unLock();
}
} else
return null;
}
}
使用方法:
String lockKey = "key";
RedisLockService lock = new RedisLockService(redisTemplate, lockKey);
boolean result = lock.wrap(() -> {
try {
//代碼塊
} catch (Exception e) {
logger.info("lock error::{}", e);
if (e instanceof BusinessException) {
if (ErrorCode.SERVER_ERROR.equals(((BusinessException) e).getErrorCode())) {
throw new BusinessException(ErrorCode.SERVER_ERROR, "lock error");
}
}
}
});
if (!result) {
throw new BusinessException(ErrorCode.SERVER_ERROR, "lock error");
}