庫存扣減還有這麼多方案?

昨天一篇《庫存扣多了,到底怎麼整》,核心觀點是:

  • 用“設置庫存”替代“扣減庫存”,以保證冪等性
  • 使用CAS樂觀鎖,在“設置庫存”時加上原始庫存的比對,避免數據不一致


文章非常多朋友留言發表觀點,“架構師之路”能引發不少同學思考,甚是欣慰。

原以為兩個核心觀點應該是沒有疑義的,結果很多朋友說方案不好,今天交流下部分回覆的方案,個人的一些看法。

留言一

是否能使用

update stock set num=num-$count where sid=$sid and stock>=$count;

的方式扣減庫存?

回答

:這個方案無法保證冪等性,有可能出現重複扣減。

留言二

把庫存放到reids裡,利用redis的事務性來扣減庫存。

分析

redis是如何實現事務操作的?

本質也是樂觀鎖。

在redis客戶端執行:

$num = GET key

$num = $num - $count

SET key $num

在併發量大的時候,會遇到和《庫存扣多了,到底怎麼整》文章中一樣的併發一致性問題。

redis的WATCH和EXEC可以提供類似事務的機制:

  • WATCH觀察key是否被改動
  • 如果提交時key被改動,EXEC將返回null,表示事務失敗

上面保證一致性的庫存扣減可能類似於這樣執行:

WATCH key

$num = GET key

$num = $num - $count

MULTI

SET key $num

EXEC

在WATCH之後,EXEC執行之前,如果key的值發生變化,則EXEC會失敗。

redis的WATCH為何能夠保證事務性,本質上,它使用的就是樂觀鎖CAS機制。

大部分情況下,redis不同的客戶端會訪問不同的key,所以WATCH碰撞的概率會比較小,在秒殺的業務場景,即使使用WATCH,調用側仍然需要重試。

在CAS機制這一點上,redis和mysql相比沒有額外的優勢。

redis的性能之所以高

,還是redis內存訪問與mysql數據落盤的差異導致的。內存訪問的不足是,數據具備“易失性”,如果重啟,可能導致數據的丟失。當然redis也可以固化數據,難道每次都刷盤?redis真心沒法當作mysql用。

最後,redis用單線程來避免物理鎖,但mysql多線程也有多線程併發的優勢。

回答:可以使用redis的事務性扣減庫存,但在CAS機制上比mysql沒有優勢,高性能是因為其內存存儲的原因,帶來的副作用是數據有丟失風險,具體怎麼用,還得結合業務折衷(任何脫離業務的架構設計都是耍流氓)。

留言三

支持冪等能否使用客戶端token,業務流水?

能否使用時間戳,版本號來保證一致性?

回答:可以。

留言四

能否使用隊列,在數據庫側串行執行,降低鎖衝突?

回答:可以。

留言五

能否使用事務?

回答:容易死鎖,吞吐量很低,不建議。

留言六

能否使用分佈式鎖解決,例如setnx, mc, zookeeper?

回答:可以,但吞吐量真的高麼。

留言六

文章重點講了冪等性和一致性,沒有深入展開講高吞吐,利用緩存抗讀請求,利用水平擴展增加性能是提升吞吐量的根本方案。

回覆:很中肯。

留言1-6代表了評論的多個觀點,由於時間有限,《庫存扣多了,到底怎麼整》許多地方沒有講清楚,大夥見諒。

如果本文讓你對redis的事務操作有個重新認識,幫忙點贊和轉發喲。


文章轉載自58沈劍,若有問題請聯繫我刪除。


分享到:


相關文章: