成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

并發扣款,如何保證一致性?

精選
開發
同一個用戶在并發“查詢,邏輯計算,扣款”的情況下,余額可能出現不一致,有什么優化方法么?今天和大家聊一聊這個問題。

?有朋友問我:

沈老師,我們有個業務,同一個用戶在并發“查詢,邏輯計算,扣款”的情況下,余額可能出現不一致,請問有什么優化方法么?

今天和大家聊一聊這個問題。

畫外音:文章較長,建議提前收藏。

問題一:用戶扣款的業務場景是怎樣的??

用戶購買商品的過程中,要對余額進行查詢與修改,大致的業務流程如下:

第一步,從數據庫查詢用戶現有余額:

SELECT money FROM t_yue WHERE uid=$uid;

不妨設查詢出來的$old_money=100元。

第二步,業務層實施業務邏輯計算,比如:

(1)先查詢購買商品的價格,例如是80元;

(2)再查詢產品是否有活動,以及活動折扣,例如是9折;

(3)比對余額是否足夠,足夠時才往下走;

if($old_money> 80*0.9){
$new_money=$old_money-80*0.9=28
} else {
return "Not enough minerals";
}

第三步,將數據庫中的余額進行修改。

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

在并發量低的情況下,這個流程沒有任何問題,原有金額100元,購買了80元的九折商品(72元),剩余28元。

問題二:同一個用戶,并發扣款可能出現什么問題?

在分布式環境中,如果并發量很大,這種“查詢+修改”的業務有一定概率出現數據不一致。

極限情況下,可能出現這樣的異常流程:

步驟一,業務1和業務2并發查詢余額,是100元。

圖片

畫外音:這些并發查詢,是在不同的站點實例/服務實例上完成的,進程內互斥鎖肯定解決不了。

步驟二,業務1和業務2并發進行邏輯計算,算出各自業務的余額,假設業務1算出的余額是28元,業務2算出的余額是38元。

圖片

步驟三,業務1對數據庫中的余額先進行修改,設置成28元。

業務2對數據庫中的余額后進行修改,設置成38元。

圖片

此時異常出現了,原有金額100元,業務1扣除了72元,業務2扣除了62元,最后剩余38元。畫外音:假設業務1先寫回余額,業務2再寫回余額。

問題三:有什么常見的解決方案?

對于此案例,同一個用戶,并發扣款時,有小概率會出現異常,可以對每一個用戶進行分布式鎖互斥,例如:在redis/zk里搶到一個key才能繼續操作,否則禁止操作。

這種悲觀鎖方案確實可行,但要引入額外的組件(redis/zk),并且會降低吞吐量。對于小概率的不一致,有沒有樂觀鎖的方案呢?

對并發扣款進行進一步的分析發現:

(1) 業務1寫回時,舊余額100,這是一個初始狀態;新余額28,這是一個結束狀態。理論上只有在舊余額為100時,新余額才應該寫回成功。

而業務1并發寫回時,舊余額確實是100,理應寫回成功。

(2) 業務2寫回時,舊余額100,這是一個初始狀態;新余額28,這是一個結束狀態。理論上只有在舊余額為100時,新余額才應該寫回成功。

可實際上,這個時候數據庫中的金額已經變為28了,所以業務2的并發寫回,不應該成功。

如何低成本實施樂觀鎖?

在set寫回的時候,加上初始狀態的條件compare,只有初始狀態不變時,才允許set寫回成功,Compare And Set(CAS),是一種常見的降低讀寫鎖沖突,保證數據一致性的方法。

此時業務要怎么改?

使用CAS解決高并發時數據一致性問題,只需要在進行set操作時,compare初始值,如果初始值變換,不允許set成功。

具體到這個case,只需要將:

UPDATE t_yue SET money=$new_money 

WHERE uid=$uid;

升級為:

UPDATE t_yue SET money=$new_money 

WHERE uid=$uid AND money=$old_money;

即可。

并發操作發生時:

業務1執行:

UPDATE t_yue SET money=28 

WHERE uid=$uid AND money=100;

業務2執行:

UPDATE t_yue SET money=38 

WHERE uid=$uid AND money=100;

這兩個操作同時進行時,只可能有一個執行成功。

怎么判斷哪個并發執行成功,哪個并發執行失敗呢?

set操作,其實無所謂成功或者失敗,業務能通過affect rows來判斷:

  • 寫回成功的,affect rows為1;
  • 寫回失敗的,affect rows為0;

高并發“查詢并修改”的場景,可以用CAS(Compare and Set)的方式解決數據一致性問題。對應到業務,即在set的時候,加上初始條件的比對即可。

優化不難,只改了半行SQL,但確實能解決問題。

問題四:能不能使用直接扣減的方法

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;

這樣是否可行?

很遺憾,仍然不行。

這個方案不冪等。

那什么是冪等性??

聊冪等性之前,先看另一個測試用例的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;
}

這是不是一個好的測試用例?這個用例存在什么問題?

你會發現,相同條件下,這個測試用例執行兩次,得到的結果不一樣:

  • 第一次執行,第一次造數據,調用接口,注冊成功;
  • 第二次執行,又造了一次相同的數據,調用接口,注冊會失敗;

這不是一個好的測試用例,多次執行結果不同。

什么是冪等性??

相同條件下,執行同一請求,得到的結果相同,才符合冪等性。

畫外音:Google一下,比我解釋得更好,但意思應該說清楚了。

如何將上面的測試用例改為符合“冪等性”的測試用例呢??

只需要加一行代碼:

bool TestCase_RegisterUser(){
//造一些假數據
long uid=123;
String name=’shenjian’;
//先刪除這個偽造的用戶
DeleteUser(uid);
//調用被測試的接口
bool result= RegisterUser(uid,name);
//預期注冊成功,對結果進行斷言判斷
Assert(result,true);
//返回測試結果
return result;
}

這樣,在相同條件下,不管這個用例執行多少次,得到的測試結果都是相同的。

讀請求,一般是冪等的。

寫請求,視情況而定:

  • 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;

當然,又會有朋友抬杠了,我從來不重試!!!

畫外音:額,這是合格,還是不合格呢?

你可以決定業務代碼怎么寫,你不能決定底層框架代碼怎么寫:

  • 站點框架有沒有自動重試?
  • 服務框架有沒有自動重試?
  • 服務連接池,數據庫連接池有沒有自動重試?

畫外音:

  • 服務化分層的架構中,建議只入口層重試,服務層不要重試,防止雪崩;
  • dubbo底層,調用超時是默認重試的,這個設計不好;?

因此,在有重試的架構體系里,冪等性是需要考慮的一個問題。

因此,扣款和充值業務,一般使用:

select&set,配合CAS方案

而不使用:

set money-=X方案

問題五:CAS方案,會不會存在ABA問題?

什么是ABA問題?

CAS樂觀鎖機制確實能夠提升吞吐,并保證一致性,但在極端情況下可能會出現ABA問題。

考慮如下操作:

  • 并發1(上):獲取出數據的初始值是A,后續計劃實施CAS樂觀鎖,期望數據仍是A的時候,修改才能成功
  • 并發2:將數據修改成B
  • 并發3:將數據修改回A
  • 并發1(下):CAS樂觀鎖,檢測發現初始值還是A,進行數據修改

上述并發環境下,并發1在修改數據時,雖然還是A,但已經不是初始條件的A了,中間發生了A變B,B又變A的變化,此A已經非彼A,數據卻成功修改,可能導致錯誤,這就是CAS引發的所謂的ABA問題。

余額操作,出現ABA問題并不會對業務產生影響,因為對于“余額”屬性來說,前一個A為100余額,與后一個A為100余額,本質是相同的。

但其他場景未必是這樣,舉一個堆棧操作的例子:

圖片

并發1(上):讀取棧頂的元素為“A1”

圖片

并發2:進行了2次出棧

圖片

并發3:又進行了1次出棧

并發1(下):實施CAS樂觀鎖,發現棧頂還是“A1”,于是修改為A2

圖片

此時會出現系統錯誤,因為此“A1”非彼“A1”

ABA問題可以怎么優化?

ABA問題導致的原因,是CAS過程中只簡單進行了“值”的校驗,在有些情況下,“值”相同不會引入錯誤的業務邏輯(例如余額),有些情況下,“值”雖然相同,卻已經不是原來的數據了(例如堆棧)。

因此,CAS不能只比對“值”,還必須確保是原來的數據,才能修改成功。

常見的實踐是,將“值”比對,升級為“版本號”的比對,一個數據一個版本,版本變化,即使值相同,也不應該修改成功。

余額并發讀寫例子,引入版本號的具體實踐如下:

(1) 余額表要升級。

t_yue(uid, money)

升級為:

t_yue(uid, money, version)

(2) 查詢余額時,同時查詢版本號。

SELECT money FROM t_yue WHERE sid=$sid

升級為:

SELECT money,version FROM t_yue WHERE sid=$sid

假設有并發操作,都會將版本號查詢出來

(3) 設置余額時,必須版本號相同,并且版本號要修改。

舊版本“值”比對:

UPDATE t_yue SET money=38 
WHERE uid=$uid AND money=100

升級為“版本號”比對:

UPDATE t_yue SET money=38, version=$version_new 
WHERE uid=$uid AND version=$version_old

此時假設有并發操作,首先操作的請求會修改版本號,并發操作會執行失敗。

畫外音:version通用,本例是強行用version舉例而已,實際上本例可以用余額“值”比對。

總結?

  • select&set業務場景,在并發時會出現一致性問題
  • 冪等性是一個需要考慮的問題
  • 基于“值”的CAS樂觀鎖,可能導致ABA問題
  • CAS樂觀鎖,必須保證修改時的“此數據”就是“彼數據”,應該由“值”比對,優化為“版本號”比對

思路比結論重要。?

責任編輯:趙寧寧
相關推薦

2019-08-30 12:46:10

并發扣款查詢SQL

2024-01-10 08:01:55

高并發場景悲觀鎖

2024-12-26 15:01:29

2023-09-07 08:11:24

Redis管道機制

2020-08-05 08:46:10

NFS網絡文件系統

2025-03-27 08:20:54

2019-09-08 22:45:48

并發扣款一致性冪等性

2020-06-01 22:09:48

緩存緩存同步緩存誤用

2021-03-04 06:49:53

RocketMQ事務

2023-05-26 07:34:50

RedisMySQL緩存

2024-08-20 16:13:52

2023-12-11 12:27:31

并發Zookeeper數據

2019-09-05 08:54:38

一致性CASABA

2020-04-01 15:50:17

TiDBMySQL數據庫

2019-10-16 00:06:08

CPU內存存儲

2021-07-21 15:50:42

Serverless 業務部署

2021-12-14 07:15:57

MySQLRedis數據

2024-01-22 08:52:00

AQS雙異步數據一致性

2022-03-29 10:39:10

緩存數據庫數據

2024-10-28 12:41:25

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩欧美三区 | 一级毛片免费完整视频 | 亚洲精品久久久一区二区三区 | 美女视频h | 黄色成人在线观看 | 久久国产成人 | 精品av| 国产精品久久亚洲 | 欧美日韩1区2区 | 狠狠综合网| 日韩精品免费在线 | 国产精品久久国产精品 | 爽爽免费视频 | 亚洲一区二区三区免费在线观看 | 国产美女福利在线观看 | 在线亚州 | 精品国产乱码久久久久久闺蜜 | 91精品国产综合久久久久久首页 | 国产小视频在线 | 久草.com | av毛片在线播放 | 日本在线小视频 | 91社区在线观看 | 国产精品美女久久久久久久网站 | 欧美激情区 | 四虎影视在线 | 久久久久国产一区二区三区四区 | 青青草这里只有精品 | 日韩精品成人 | 三区在线 | 国产精品永久久久久久久www | 激情欧美日韩一区二区 | 免费精品 | 日韩视频一区二区三区 | 日韩在线中文字幕 | 亚洲欧美激情精品一区二区 | 中文字幕一区二区三区在线视频 | 日韩欧美在线观看 | 国产乱码精品一区二区三区五月婷 | 欧美精品网 | 免费在线观看h片 |