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