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");
}