這可能是我在博客園的第一篇認真寫的文章,由於之前的公司工作太忙,一直沒有時間管理,平時登錄博客也只是把不常見問題的解決辦法記錄一下,現在離職了,時間較為富裕,在準備新面試之前將去年遇到的難點一一梳理一下。 高併發業務場景在電商系統中經常出現,尤其是庫存方面,搞不好就要超賣,給公司造成直接的經濟損失, ...
這可能是我在博客園的第一篇認真寫的文章,由於之前的公司工作太忙,一直沒有時間管理,平時登錄博客也只是把不常見問題的解決辦法記錄一下,現在離職了,時間較為富裕,在準備新面試之前將去年遇到的難點一一梳理一下。
高併發業務場景在電商系統中經常出現,尤其是庫存方面,搞不好就要超賣,給公司造成直接的經濟損失,雖然解釋權在公司,但這也對用戶的體驗不好,下麵我會將去年遇到的高併發搶紅包解決方案與代碼寫下來。
假設我們模擬3萬會員均攤20萬元現金紅包,並假設流量點在同一時間,同時涌入,如果按照正常的業務邏輯的話,發生超賣的情況是必須的,如果從代碼層面限制,比如引入synchronize、lock一類的鎖機制控制的話,首先在分散式系統中是不支持的,僅適合單機,單機又容易引發單點故障,性能等問題,所以此種做法不推薦。
1、悲觀鎖
這裡我們使用資料庫內部機制提供的一種鎖的辦法,在多線程併發競爭期間,如果有一條線程占有了控制權,那麼其他線程將無法獲取直到線程釋放控制權再次競爭,這種做法能完全解決超賣問題的發生,但隨之而來的是:
線程、鎖頻繁的掛起釋放會急劇消耗CPU資源,使得性能下降,在高併發環境中,會帶來非常恐怖的後果。
但這種做法不是不可以用,需要考慮實際業務以及流量,例如大額交易,通過風險控制系統引導,超過上千萬的單筆交易,通知人工監控是一個方案,引入悲觀鎖來加強安全也是可行的。
實現方法:
select xxx from xxx where xxx = xxx for update
如果條件為主鍵索引,那麼此次獲得的鎖將是一個行級鎖,如果是非主鍵欄位,那麼鎖機制可能會把整張表鎖定,這個結果是不一定的,例如mysql資料庫就會自己根據實際情況選擇哪一種更適合,有可能你是主鍵索引,應該是得到行鎖,但如果mysql認為表鎖更合適,你獲得的將是表鎖。具體業務具體分析。
2、樂觀鎖
樂觀鎖是一種非阻塞線程併發的機制,他的實現不會依賴資料庫內部機制,解決了之前悲觀鎖阻塞、線程頻繁掛起恢復問題,樂觀鎖使用的是CAS原理,在Java語言中concurrent包就是建立在CAS基礎上的。
什麼是CAS?
官方:CAS,compare and swap的縮寫,中文翻譯成比較並交換。
概述:對多線程共用資源,先取得舊值保存,在提交時進行取現有值與原有值進行比較
什麼是ABA?
講到CAS原理,就會引出另一個問題,那就是經典ABA問題,這個東西介紹起來篇幅太大,具體的請網上搜索資料,簡單來講就是由於多線程之間業務邏輯問題會導致取到的舊值發生改變,存在回退的可能性,解決辦法是在數據表中加入一個非關鍵的version欄位,強制遞增,沒有回退操作,ABA問題就解決了。
實現方法:
update xxx set xxx = xxx, version = version + 1 where xxx = xxx and version = ?
經過測試,這種做法跟一開始不用任何鎖的性能是基本一致的,但是這種做法會大大的提高失敗率,如果業務要求不嚴格的話,到這基本就可以解決我們的問題,那麼接下來該怎麼解決失敗率的問題?
可重入鎖:
這僅僅是一種機制,就好比我騎單車在XXX路停下,那麼下次我怎麼才能再找到這輛單車?用最原始的方法,我在這個地方畫個圈圈什麼的標記一下,那麼可重入鎖也是這樣實現。
我們暫時不用可重鎖,目前他的最好實現是利用zookeeper的子節點來實現,我們只是單獨實現重入機制
在最外層加一層for迴圈來重試即可,此操作冪等的,所以數據不會錯亂,一般情況下只要上一步實現就可以了,為了保險可以加入重入機制