秒殺在互聯網電商場景中經常遇到,里面涉及到的知識點很多,一個核心的思路還是將請求放到內存中排隊,按服務器接受到請求的順序串行化的處理請求,從而緩解數據庫事物的壓力。
秒殺算是多線程掙錢臨界資源的綜合運用,不一定非要用多線程來解決,其本質是要在諸多約束情況下,盡可能高效的解決問題。
秒殺系統涉及到的知識點
-
高並發,cache,鎖機制
-
基於緩存架構redis,Memcached的先進先出隊列。
-
稍微大一點的秒殺,肯定是分布式的集群的,並發來自於多個節點的JVM,synchronized所有在JVM上加鎖是不行了
-
數據庫壓力
-
秒殺超賣問題
-
如何防止用戶來刷, 黑名單?IP限制?
-
利用memcached的帶原子性特性的操作做並發控制
比如有10件商品要秒殺,可以放到緩存中,讀寫時不要加鎖。 當並發量大的時候,可能有25個人秒殺成功,這樣后面的就可以直接拋秒殺結束的靜態頁面。進去的25個人中有15個人是不可能獲得商品的。所以可以根據進入的先后順序只能前10個人購買成功。后面15個人就拋商品已秒殺完。
比如某商品10件物品待秒. 假設有100台web服務器(假設web服務器是Nginx + Tomcat),n台app服務器,n個數據庫
第一步 如果Java層做過濾, 可以在每台web服務器的業務處理模塊里做個計數器AtomicInteger(10)=待秒商品總數,decreaseAndGet()>=0的繼續做后續處理, <0的直接返回秒殺結束頁面,這樣經過第一步的處理只剩下100台*10個=1000個請求。
第二步, memcached 里以商品id作為key的value放個10, 每個web服務器在接到每個請求的同時, 向memcached服務器發起請求, 利用memcached的decr(key,1)操作返回值>=0的繼續處理, 其余的返回秒殺失敗頁面,這樣經過第二步的處理只剩下100台中最快速到達的10個請求。
第三步, 向App服務器發起下單操作事務。
第四步, App服務器向商品所在的數據庫請求減庫存操作(操作數據庫時可以 "update table set count=count-1 where id=商品id and count>0;" update 成功記錄數為1, 再向訂單數據庫添加訂單記錄, 都成功后提交整個事務, 否則的話提示秒殺失敗,用戶進入支付流程。
看看淘寶的秒殺
一、前端
面對高並發的搶購活動,前端常用的三板斧是【擴容】【靜態化】【限流】
-
擴容:加機器,這是最簡單的方法,通過增加前端池的整體承載量來抗峰值。
-
靜態化:將活動頁面上的所有可以靜態的元素全部靜態化,並盡量減少動態元素。通過CDN來抗峰值。
-
限流:一般都會采用IP級別的限流,即針對某一個IP,限制單位時間內發起請求數量。或者活動入口的時候增加游戲或者問題環節進行消峰操作。
-
有損服務:最后一招,在接近前端池承載能力的水位上限的時候,隨機拒絕部分請求來保護活動整體的可用性。
二、那么后端的數據庫在高並發和超賣下會遇到什么問題呢
-
首先MySQL自身對於高並發的處理性能就會出現問題,一般來說,MySQL的處理性能會隨着並發thread上升而上升,但是到了一定的並發度之后會出現明顯的拐點,之后一路下降,最終甚至會比單thread的性能還要差。
-
其次,超賣的根結在於減庫存操作是一個事務操作,需要先select,然后insert,最后update -1。最后這個-1操作是不能出現負數的,但是當多用戶在有庫存的情況下並發操作,出現負數這是無法避免的。
-
最后,當減庫存和高並發碰到一起的時候,由於操作的庫存數目在同一行,就會出現爭搶InnoDB行鎖的問題,導致出現互相等待甚至死鎖,從而大大降低MySQL的處理性能,最終導致前端頁面出現超時異常。
針對上述問題,如何解決呢? 淘寶的高大上解決方案:
I:關閉死鎖檢測,提高並發處理性能。
II:修改源代碼,將排隊提到進入引擎層前,降低引擎層面的並發度。
III:組提交,降低server和引擎的交互次數,降低IO消耗。
解決方案1:將存庫從MySQL前移到Redis中,所有的寫操作放到內存中,由於Redis中不存在鎖故不會出現互相等待,並且由於Redis的寫性能和讀性能都遠高於MySQL,這就解決了高並發下的性能問題。然后通過隊列等異步手段,將變化的數據異步寫入到DB中。
優點:解決性能問題
缺點:沒有解決超賣問題,同時由於異步寫入DB,存在某一時刻DB和Redis中數據不一致的風險。
解決方案2:引入隊列,然后將所有寫DB操作在單隊列中排隊,完全串行處理。當達到庫存閥值的時候就不在消費隊列,並關閉購買功能。這就解決了超賣問題。
優點:解決超賣問題,略微提升性能。
缺點:性能受限於隊列處理機處理性能和DB的寫入性能中最短的那個,另外多商品同時搶購的時候需要准備多條隊列。
解決方案3:將寫操作前移到MC中,同時利用MC的輕量級的鎖機制CAS來實現減庫存操作。
優點:讀寫在內存中,操作性能快,引入輕量級鎖之后可以保證同一時刻只有一個寫入成功,解決減庫存問題。
缺點:沒有實測,基於CAS的特性不知道高並發下是否會出現大量更新失敗?不過加鎖之后肯定對並發性能會有影響。
解決方案4:將提交操作變成兩段式,先申請后確認。然后利用Redis的原子自增操作,同時利用Redis的事務特性來發號,保證拿到小於等於庫存閥值的號的人都可以成功提交訂單。然后數據異步更新到DB中。
優點:解決超賣問題,庫存讀寫都在內存中,故同時解決性能問題。
缺點:由於異步寫入DB,可能存在數據不一致。另可能存在少買,也就是如果拿到號的人不真正下訂單,可能庫存減為0,但是訂單數並沒有達到庫存閥值。
總結
1、前端三板斧【擴容】【限流】【靜態化】
2、后端兩條路【內存】+【排隊】
https://mp.weixin.qq.com/s/rO4Ric1bpNJgwuBFO8x3gw