秒殺系統設計的5個要點

秒殺在互聯網電商場景中常常遇到,裏面涉及到的知識點不少,一個核心的思路仍是將請求放到內存中排隊,按服務器接受到請求的順序串行化的處理請求,從而緩解數據庫事物的壓力。

秒殺算是多線程掙錢臨界資源的綜合運用,不必定非要用多線程來解決,其本質是要在諸多約束狀況下,儘量高效的解決問題。前端


 

秒殺系統涉及到的知識點

  • 高併發,cache,鎖機制web

  • 基於緩存架構redis,Memcached的先進先出隊列。redis

  • 稍微大一點的秒殺,確定是分佈式的集羣的,併發來自於多個節點的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, 再向訂單數據庫添加訂單記錄, 都成功後提交整個事務, 不然的話提示秒殺失敗,用戶進入支付流程。

 

看看淘寶的秒殺

1、前端

面對高併發的搶購活動,前端經常使用的三板斧是【擴容】【靜態化】【限流】

  • 擴容:加機器,這是最簡單的方法,經過增長前端池的總體承載量來抗峯值。

  • 靜態化:將活動頁面上的全部能夠靜態的元素所有靜態化,並儘可能減小動態元素。經過CDN來抗峯值。

  • 限流:通常都會採用IP級別的限流,即針對某一個IP,限制單位時間內發起請求數量。或者活動入口的時候增長遊戲或者問題環節進行消峯操做。

  • 有損服務:最後一招,在接近前端池承載能力的水位上限的時候,隨機拒絕部分請求來保護活動總體的可用性。

 

2、那麼後端的數據庫在高併發和超賣下會遇到什麼問題呢

  • 首先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,可是訂單數並無達到庫存閥值。

總結

一、前端三板斧【擴容】【限流】【靜態化】

二、後端兩條路【內存】+【排隊】

https://mp.weixin.qq.com/s/rO4Ric1bpNJgwuBFO8x3gw

相關文章
相關標籤/搜索