一、 背景:我的项目是分布式的,当订单失败要退款时,由于分布式高并发的环境,一笔订单在设备租借失败确认多次上报后会发生多次退款请求导致账户异常。
二、解决方案:使用redis缓存来实现分布式项目加锁控制。
三、了解下redis几个原子命令:
setnx 先判断键是否存在,key存在设置失败,返回0;否则成功返回1
getset 获取旧的值,设置新的值
expire 设置键的有效期
del 删除键
四、实现方案流程图:
如上图所示:之前部分步骤一样,只是当获取锁失败后,使用get获取当前锁的value=lockvalueA,如果当前的时间>lockvalueA,说明setnx时timeout现在已经超时了,此时有权利获取锁;此时为true再使用getset方法重新设置一个当前的value值并返回旧的value=lockvalueB,这时做判断,如果lockvalueB==null(锁已经没有了,可以获取到锁)或者lockvalueA==lockvalueB(最新获取的B与之前得到的A相等,在这个过程中锁没有变化)满足则获取锁成功,执行expire业务逻辑,否则结束。
五、代码演示:
public class GlobalConfig {
// redis 分布式锁
public static final String REDIS_LOCK_KEY="REDIS_LOCK_KEY";
// redis 分布式锁过期时间
public static final String REDIS_LOCK_KEY_EXPIRE_TIME="60";
}
private boolean lock() {
Integer expireTime = Integer.valueOf(GlobalConfig.REDIS_LOCK_KEY_EXPIRE_TIME);
Long setnx = redisService.setnx(GlobalConfig.REDIS_LOCK_KEY, System.currentTimeMillis() + expireTime * 1000 + ""); //不存在就设置key的值为过期时间1分钟加上当前时间
if (setnx != null && setnx > 0) {
redisService.expire(GlobalConfig.REDIS_LOCK_KEY, expireTime);// 设置过期时间1分钟
return true;
} else {
String lockvalueAString = redisService.getString(GlobalConfig.REDIS_LOCK_KEY);
if(!StringUtil.isNullOrEmpty(lockvalueAString)){
Long lockvalueA = Long.valueOf(lockvalueAString);
if(lockvalueA!=null && System.currentTimeMillis()>lockvalueA){
String lockvalueB = redisService.getSet(GlobalConfig.REDIS_LOCK_KEY, System.currentTimeMillis() + 60 * 1000 + "");
if(lockvalueB==null || lockvalueAString.equals(lockvalueB)){
redisService.expire(GlobalConfig.REDIS_LOCK_KEY, expireTime);// 设置过期时间1分钟
return true;
}
}
}
}
return false;
}
private void unlock(){
redisService.del(GlobalConfig.REDIS_LOCK_KEY);
}
//调用
if(lock()){
业务代码逻辑
unlock();
}