巧用CAS解決數(shù)據(jù)一致性問(wèn)題

2018-09-06 17:57 更新
緣起:在高并發(fā)的分布式環(huán)境下,對(duì)于數(shù)據(jù)的查詢與修改容易引發(fā)一致性問(wèn)題,本文將分享一種非常簡(jiǎn)單但有效的優(yōu)化方法。

一、業(yè)務(wù)場(chǎng)景

業(yè)務(wù)場(chǎng)景為,購(gòu)買(mǎi)商品的過(guò)程要對(duì)余額進(jìn)行查詢與修改,大致的業(yè)務(wù)流程如下:

(1)從數(shù)據(jù)庫(kù)查詢用戶現(xiàn)有余額 SELECT money FROM t_yue WHERE uid=$uid,不妨設(shè)查詢出來(lái)的$old_money=100元

數(shù)據(jù)庫(kù)查詢用戶現(xiàn)有余額

(2)業(yè)務(wù)層實(shí)施業(yè)務(wù)邏輯,比如購(gòu)買(mǎi)一個(gè)80元的商品,并且打九折

if($old_money> 80*0.9) $new_money=$old_money-80*0.9=28

業(yè)務(wù)層實(shí)施業(yè)務(wù)邏輯
(3)將數(shù)據(jù)庫(kù)中的余額進(jìn)行修改 UPDAtE t_yue SET money=$new_money WHERE uid=$uid
數(shù)據(jù)庫(kù)中的余額進(jìn)行修改
在并發(fā)量低的情況下,這個(gè)流程沒(méi)有任何問(wèn)題,原有金額100元,購(gòu)買(mǎi)了80元的九折商品(72元),剩余28元。

二、潛在的問(wèn)題

在分布式環(huán)境中,如果并發(fā)量很大,這種“查詢+修改”的業(yè)務(wù)很容易出現(xiàn)數(shù)據(jù)不一致。極限情況下,可能出現(xiàn)這樣的異常流程:

(1)業(yè)務(wù)1和業(yè)務(wù)2同時(shí)查詢余額,是100元

業(yè)務(wù)1和業(yè)務(wù)2同時(shí)查詢余額
(2)業(yè)務(wù)1和業(yè)務(wù)2進(jìn)行邏輯計(jì)算,算出各自業(yè)務(wù)的余額,假設(shè)業(yè)務(wù)1算出的余額是28元,業(yè)務(wù)2算出的余額是38元
業(yè)務(wù)1和業(yè)務(wù)2的余額

(3)業(yè)務(wù)1對(duì)數(shù)據(jù)庫(kù)中的余額先進(jìn)行修改,設(shè)置成28元。

業(yè)務(wù)2對(duì)數(shù)據(jù)庫(kù)中的余額后進(jìn)行修改,設(shè)置成38元。

業(yè)務(wù)1和業(yè)務(wù)2的異常
此時(shí)異常出現(xiàn)了,原有金額100元,業(yè)務(wù)1扣除了72元,業(yè)務(wù)2扣除了62元,最后剩余38元。

三、問(wèn)題原因

高并發(fā)環(huán)境下,對(duì)同一個(gè)數(shù)據(jù)的并發(fā)讀(兩邊都讀出余額是100)與并發(fā)寫(xiě)(一個(gè)寫(xiě)回28,一個(gè)寫(xiě)回38)導(dǎo)致的數(shù)據(jù)一致性問(wèn)題。


四、原因分析

業(yè)務(wù)1的寫(xiě)回:原有金額100,這是一個(gè)初始狀態(tài),寫(xiě)回金額28,理論上只有在原有金額為100的時(shí)候才允許寫(xiě)回成功,這一步?jīng)]問(wèn)題。

業(yè)務(wù)2的寫(xiě)回:的原有金額100,這是一個(gè)初始狀態(tài),寫(xiě)回金額38,理論上只有在原有金額為100的時(shí)候才允許寫(xiě)回成功,可實(shí)際上,這個(gè)時(shí)候數(shù)據(jù)庫(kù)中的金額已經(jīng)變?yōu)?8了,這一步的寫(xiě)操作不應(yīng)該成功。


五、簡(jiǎn)易解決方案

在set寫(xiě)回的時(shí)候,加上初始狀態(tài)的條件compare,只有初始狀態(tài)不變時(shí),才允許set寫(xiě)回成功,這正是大家常說(shuō)的“Compare And Set”(CAS),是一種常見(jiàn)的降低讀寫(xiě)鎖沖突,保證數(shù)據(jù)一致性的方法。


六、業(yè)務(wù)的升級(jí)

業(yè)務(wù)線使用CAS解決高并發(fā)時(shí)數(shù)據(jù)一致性問(wèn)題,只需要在進(jìn)行set操作時(shí),compare一下初始值,如果初始值變換,不允許set成功。

對(duì)于上文中的業(yè)務(wù)場(chǎng)景,只需要將“UPDAtEt_yue SET money=$new_money WHERE uid=$uid”升級(jí)為

“UPDAtE t_yue SETmoney=$new_money WHERE uid=$uid AND money=$old_money”即可。

并發(fā)操作發(fā)生時(shí):

業(yè)務(wù)1執(zhí)行 => UPDAtE t_yue SET money=28 WHERE uid=$uid AND money=100

業(yè)務(wù)2執(zhí)行 => UPDAtE t_yue SET money=38 WHERE uid=$uid AND money=100

【這兩個(gè)操作同時(shí)進(jìn)行時(shí),只能有一個(gè)執(zhí)行成功】。


七、怎么判斷哪個(gè)執(zhí)行成功,哪個(gè)執(zhí)行失敗

set操作,其實(shí)無(wú)所謂成功或者失敗,業(yè)務(wù)能通過(guò)affect rows得知哪個(gè)修改沒(méi)有成功:

執(zhí)行成功的業(yè)務(wù),affect rows為1

執(zhí)行失敗的業(yè)務(wù),affect rows為0


八、總結(jié)

高并發(fā)“查詢并修改”的場(chǎng)景,可以用CAS(Compare and Set)的方式解決數(shù)據(jù)一致性問(wèn)題。對(duì)應(yīng)到業(yè)務(wù),即在set的時(shí)候,加上初始條件的比對(duì)。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)