應用Redis分布式鎖解決重復通知的問題


研究背景:

這幾天被支付寶充值后通知所產生的重復處理問題搞得焦頭爛額, 一周連續發生兩次重復充錢的杯具, 發事故郵件發到想吐。。為了挽回程序員的尊嚴, 我用了Redis的鎖機制。

事故場景:

支付寶下單 -> 客戶支付 -> 回調我方接口通知支付結果

服務器節點: 2個

事故發生原因: 回調我方接口后, 第一次通知還未處理完, 第二次通知又來了(間隔幾秒),未對通知進行判定重復,導致兩個節點均處理了通知, 給客戶加了兩次錢。。

解決方案:

1. 基於數據庫的原子性操作原理控制數據只能被處理一次。

方法: 由於數據能被處理的條件是 pay_record.status = 'paying', 即支付狀態為待支付中, 才能更新數據為支付成功。

修改前的更新SQL: update pay_record set status = 'succ' where id = #{id};

修改后的更新SQL: update pay_record set status = 'succ' where id = #{id} and status = 'paying';

通過對返回值是否 > 0判斷是否有數據被更新成功, 如果有,則執行后續的給錢包加錢操作,否則不處理。

 

2. 基於Redis的分布式鎖setnx方法控制同時只能有一個線程處理加錢邏輯

// 第一次通知,設置緩存
if(jedis.setnx(DONKEY_ALICALLBACK_NOTICE + outTradeNo, outTradeNo) > 0) {
    // 設置生效時長(因為setnx沒有生效時間的入參)
    jedis.expire(DONKEY_ALICALLBACK_NOTICE + outTradeNo, 3600 * 3);
    LOGGER.warn("key = {} 新增緩存成功, 進入處理..", DONKEY_ALICALLBACK_NOTICE + outTradeNo);
// 錢包加錢操作
addMoney(outTradeNo); }
else { // 新增緩存失敗, 不處理 LOGGER.warn("key = {} 新增緩存失敗, 不處理", DONKEY_ALICALLBACK_NOTICE + outTradeNo); return; }
setnx: 當key不存在時設置成功,返回1, 否則返回0, 這個操作是線程安全的, 可以查看到它的源代碼如下:
public Long setnx(String key, String value) {
    this.checkIsInMultiOrPipeline();
    this.client.setnx(key, value);
    return this.client.getIntegerReply();
}
this.checkIsInMultiOrPipeline()方法源碼:
protected void checkIsInMultiOrPipeline() {
    if(this.client.isInMulti()) {
        throw new JedisDataException("Cannot use Jedis when in Multi. Please use Transation or reset jedis state.");
    } else if(this.pipeline != null && this.pipeline.hasPipelinedResponse()) {
        throw new JedisDataException("Cannot use Jedis when in Pipeline. Please use Pipeline or reset jedis state .");
    }
}

 

總結:

通過jedis.class源碼分析可知, redis的大部分的方法均實現了線程安全,都是單線程操作, 故使用redis作為分布式鎖效果很好, 也很輕量級。

 

完畢~~

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM