redis鎖和等待鎖隨機毫秒數解決程式呼叫方控制執行的先後順序,避免併發操作造成的資料不一致

oktokeep發表於2024-11-27

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

相關文章