天天看點

并發扣款一緻性,幂等性問題,這個話題還沒聊完!!!

《并發扣款,如何保證資料的一緻性?》

,分享了同一個使用者并發扣款時,有一定機率出現資料不一緻,可以使用CAS樂觀鎖的方式,在不降低吞吐量,并且隻有少量修改的情況下,保證資料的一緻性。

文章釋出不到24小時,就有近200的評論。

 其中,問的比較多的是ABA問題,這個問題已經在

《并發扣款一緻性優化,CAS下ABA問題,這個話題還沒聊完!!!》

中擴充。 其次,問的比較多的是作業題,為什麼一定要用select&set的方式進行餘額寫回:

UPDATE t_yue SET money=$new_money WHERE uid=$uid AND money=$old_money;

 為什麼不能采用直接扣減的方法:

UPDATE t_yue SET money=money-$diff WHERE uid=$uid;

 很人說,在并發情況下,會将money扣成負數。 為了保證餘額不被扣成負數,再加一個where條件:

UPDATE t_yue SET money=money-$diff WHERE uid=$uid AND money-$diff>0;

這樣是否可行?畫外音:額,撇開業務不談,這個SQL用列做運算,其實是不好的,建議使用:

UPDATE t_yue SET money=money-$diff WHERE uid=$uid AND money>$diff;

 畫外音:說明絕大部分同學,能夠回答正确作業。 聊幂等性之前,先看另一個測試用例的case。 假設有一個服務接口,注冊新使用者:

bool RegisterUser($uid, $name){

         //檢視uid是否已經存在

         select uid from t_user where uid=$uid;

         //不是新使用者,傳回失敗

         if(rows>0)return false;

         else{

                   //把新使用者插入使用者表                   insert into t_user values($uid, $name);                   //傳回成功                   return true;         }} 有一個測試工程師,對該接口寫了一個測試用例:

bool TestCase_RegisterUser(){

         //造一些假資料

         long uid=123;

         String name='shenjian';

         //調用被測試的接口

         bool result= RegisterUser(uid,name);

         //預期注冊成功,對結果進行斷言判斷

         Assert(result,true);

         //傳回測試結果

         return result;

}

 這是不是一個好的測試用例?

這個用例存在什麼問題?你會發現,相同條件下,這個測試用例執行兩次,得到的結果不一樣:(1)第一次執行,第一次造資料,調用接口,注冊成功;(2)第二次執行,又造了一次相同的資料,調用接口,注冊會失敗;這不是一個好的測試用例,多次執行結果不同。 什麼是幂等性?相同條件下,執行同一請求,得到的結果相同,才符合幂等性。畫外音:Google一下,比我解釋得更好,但意思應該說清楚了。 如何将上面的測試用例改為符合“幂等性”的測試用例呢? 隻需要加一行代碼:

         String name=’shenjian’;

         //先删除這個僞造的使用者

         DeleteUser(uid);

這樣,在相同條件下,不管這個用例執行多少次,得到的測試結果都是相同的。 是不是對幂等性有點感覺了。 讀請求,一般是幂等的。

寫請求,視情況而定:

  • insert x,一般來說不是幂等的,重複插入得到的結果不一定一樣
  • delete x,一般來說是幂等的,删除多次得到的結果仍相同
  • set a=x是幂等的
  • set a=a-x不是幂等的

 是以,這麼扣減餘額:UPDATE t_yue SET money=$new_money WHERE uid=$uid AND money=$old_money;是幂等操作。 要是這麼扣減餘額:UPDATE t_yue SET money=money-$diff WHERE uid=$uid AND money-$diff>0;不是幂等操作。 聊到這裡,或許有朋友要擡杠了,測試用例會重複執行,扣款怎麼會重複執行呢?重試。 重試,是異常處理裡很常見的手段。 你在寫業務的時候有沒有寫過這樣的代碼:

result = DoSomething();

if(false==result || TIMEOUT){

         //錯誤,或者逾時,重試一次

         result= DoSomething();

return result;

 當然,又會有朋友擡杠了,我從來不重試!!!畫外音:額,這是合格,還是不合格呢? 你可以決定業務代碼怎麼寫,你不能決定底層架構代碼怎麼寫:(1)站點架構有沒有自動重試?(2)服務架構有沒有自動重試?(3)服務連接配接池,資料庫連接配接池有沒有自動重試?畫外音:(1)服務化分層的架構中,建議隻入口層重試,服務層不要重試,防止雪崩;(2)dubbo底層,調用逾時是預設重試的,這個設計不好; 是以,在有重試的架構體系裡,幂等性是需要考慮的一個問題。 現在該懂了,為啥扣款和充值業務,一般使用:

  • select&set,配合CAS方案

而不使用:

  • set money-=X方案

畫外音:充了100電話費,怎麼多了200塊?

知其然,知其是以然,希望大家有收獲。

本文轉自“架構師之路”公衆号,58沈劍提供。