昨天一篇《
庫存扣多了,到底怎么整》,核心觀點(diǎn)是:
?
用“設(shè)置庫存”替代“扣減庫存”,以保證
冪等性?使
用CAS樂觀鎖,在“設(shè)置庫存”時(shí)加上原始庫存的比對,
避免數(shù)據(jù)不一致文章非常多朋友留言發(fā)表觀點(diǎn),“架構(gòu)師之路”能引發(fā)不少同學(xué)思考,甚是欣慰。
原以為兩個(gè)核心觀點(diǎn)應(yīng)該是沒有疑義的,結(jié)果很多朋友說方案不好,今天交流下部分回復(fù)的方案,個(gè)人的一些看法。
留言一是否能使用
update stock set num=num-$count where sid=$sid
and stock>=$count;
的方式扣減庫存?
回答:這個(gè)方案
無法保證冪等性,有可能出現(xiàn)重復(fù)扣減。
留言二把庫存放到reids里,利用redis的事務(wù)性來扣減庫存。
分析:
redis是如何實(shí)現(xiàn)事務(wù)操作的?本質(zhì)也是樂觀鎖。
在redis客戶端執(zhí)行:
$num = GET key
$num = $num - $count
SET key $num
在并發(fā)量大的時(shí)候,會(huì)遇到和《
庫存扣多了,到底怎么整》文章中一樣的并發(fā)一致性問題。
redis的WATCH和EXEC可以提供類似事務(wù)的機(jī)制:
?WATCH觀察key是否被改動(dòng)
?如果提交時(shí)key被改動(dòng),EXEC將返回null,表示事務(wù)失敗
上面保證一致性的庫存扣減可能類似于這樣執(zhí)行:
WATCH key
$num = GET key
$num = $num - $count
MULTI
SET key $num
EXEC
在WATCH之后,EXEC執(zhí)行之前,如果key的值發(fā)生變化,則EXEC會(huì)失敗。
redis的WATCH為何能夠保證事務(wù)性,本質(zhì)上,它使用的就是樂觀鎖CAS機(jī)制。
大部分情況下,redis不同的客戶端會(huì)訪問不同的key,所以WATCH碰撞的概率會(huì)比較小,在秒殺的業(yè)務(wù)場景,即使使用WATCH,調(diào)用側(cè)仍然需要重試。
在CAS機(jī)制這一點(diǎn)上,redis和mysql相比沒有額外的優(yōu)勢。
redis的
性能之所以高,還是
redis內(nèi)存訪問與mysql數(shù)據(jù)落盤的差異導(dǎo)致的。內(nèi)存訪問的不足是,數(shù)據(jù)具備“易失性”,如果重啟,
可能導(dǎo)致數(shù)據(jù)的丟失。當(dāng)然redis也可以固化數(shù)據(jù),難道每次都刷盤?redis真心沒法當(dāng)作mysql用。
最后,redis用單線程來避免物理鎖,但mysql多線程也有多線程并發(fā)的優(yōu)勢。
回答:可以使用redis的事務(wù)性扣減庫存,但在CAS機(jī)制上比mysql沒有優(yōu)勢,高性能是因?yàn)槠鋬?nèi)存存儲(chǔ)的原因,帶來的副作用是數(shù)據(jù)有丟失風(fēng)險(xiǎn),具體怎么用,還得結(jié)合業(yè)務(wù)折衷(
任何脫離業(yè)務(wù)的架構(gòu)設(shè)計(jì)都是耍流氓)。
留言三支持冪等能否使用客戶端token,業(yè)務(wù)流水?
能否使用時(shí)間戳,版本號(hào)來保證一致性?
回答:可以。
留言四能否使用隊(duì)列,在數(shù)據(jù)庫側(cè)串行執(zhí)行,降低鎖沖突?
回答:可以。
留言五能否使用事務(wù)?
回答:容易死鎖,吞吐量很低,不建議。
留言六能否使用分布式鎖解決,例如setnx, mc, zookeeper?
回答:可以,但吞吐量真的高么。
留言七文章重點(diǎn)講了冪等性和一致性,沒有深入展開講高吞吐,
利用緩存抗讀請求,利用水平擴(kuò)展增加性能是提升吞吐量的根本方案。
回復(fù):很中肯。
留言1-7代表了評論的多個(gè)觀點(diǎn),由于時(shí)間有限,《
庫存扣多了,到底怎么整》許多地方?jīng)]有講清楚,大伙見諒。
更多建議: