redis锁和等待锁随机毫秒数解决程序调用方控制执行的先后顺序,避免并发操作造成的数据不一致
现象: 向第三方服务调用接口,比如更换商品换货,需要先取消,然后再新增操作。 同时可能存在修改并发操作(同时操作换货和修改操作),在取消和新增的间隙中做了修改操作,引起脏数据等数据不一致的问题。 导致修改的数据,在新增操作后,未生效。
解决方案: 基于的前提是在程序接口的调用方来控制先后执行顺序,服务提供方本身提供的是取消,新增,修改3个独立的接口,只是业务上需要将取消和新增组合起来使用。 redis锁定5秒来处理,控制加锁优化
期望:将取消和新增作为一个“事务”来处理,只有这一个“换货”的操作完成之后,才允许做修改操作。
//伪代码
@Autowired
private StringRedisTemplate stringRedisTemplate;
//修改接口{
String redisKey = "key" + orderDTO.getOrdernumber();
Boolean haskey = stringRedisTemplate.hasKey(redisKey);
LocalDateTime startTime = LocalDateTime.now();
LocalDateTime endTime = startTime.plusSeconds(5);
//已被锁定,直接返回,等待 + redis锁释放和程序时间5秒双重判断,避免redis释放锁异常导致永远在等待的现象。
while ( haskey && (startTime.isBefore(endTime) || startTime.isEqual(endTime)) ) {
try {
long time = (long) (Math.random() * 1000);
Thread.sleep(time);
//重新查询
haskey = stringRedisTemplate.hasKey(redisKey);
//重新刷新时间
startTime = LocalDateTime.now();
log.info("testlogger >> 修改订单 判断锁存在,orderNo=[{}],haskey=[{}],waitTime=[{}]",orderDTO.getOrdernumber(),haskey,time);
} catch (InterruptedException e) {
log.error("exceotion:",e);
}
}
//继续修改操作 ……
}
//换货接口(取消和新增){
String redisKey = "key" + orderDTO.getOrdernumber();
Boolean haskey = stringRedisTemplate.hasKey(redisKey);
try {
//加锁
if(haskey) {
//已被锁定,直接返回,无需重复加锁
log.info("testlogger 锁已经存在,无需重复加锁,orderNo=[{}]",cancelFlowOrderDTO.getOrdernumber());
} else {
stringRedisTemplate.opsForValue().set(redisKey, "1", 5, TimeUnit.SECONDS);
log.info("testlogger 锁不存在,需要加锁,orderNo=[{}]",cancelFlowOrderDTO.getOrdernumber());
}
//换货操作(取消和新增)……
}catch (Exception e) {
log.error("取消新增接口异常:",e);
if(haskey != null && haskey) {
stringRedisTemplate.delete(redisKey);
log.info("testlogger 锁存在,异常释放锁,orderNo=[{}]",cancelFlowOrderDTO.getOrdernumber());
}
}finally {
if(haskey != null && haskey) {
//释放锁
stringRedisTemplate.delete(redisKey);
log.info("testlogger 锁存在,正常释放锁,orderNo=[{}]",cancelFlowOrderDTO.getOrdernumber());
}
}
}