【商城限時秒殺系統總結】php
在高併發狀況下的秒殺優化,咱們知道當併發數達到必定量的時候,會對數據庫服務器帶來很大的壓力,那麼如何緩解這些壓力以及提升併發的QPS就是整個項目的解決重點,也是咱們優化系統的目標。html
源碼地址:https://github.com/pitt1997/Seckill前端
項目的亮點:git
1.使用分佈式Seesion,能夠實現讓多臺服務器同時能夠響應。github
2.使用redis作緩存提升訪問速度和併發量,減小數據庫壓力,利用內存標記減小redis的訪問。golang
3.使用頁面靜態化,加快用戶訪問速度,提升QPS,緩存頁面至瀏覽器,先後端分離下降服務器壓力。ajax
4.使用消息隊列完成異步下單,提高用戶體驗,削峯和降流。redis
5. 安全性優化:雙重md5密碼校驗,秒殺接口地址的隱藏,接口限流防刷,數學公式驗證碼。算法
主要知識點:sql
分佈式Seesion
咱們的秒殺服務,實際的應用可能不止部署在一個服務器上,而是分佈式的多臺服務器,這時候假如用戶登陸是在第一個服務器,第一個請求到了第一臺服務器,可是第二個請求到了第二個服務器,那麼用戶的session信息就丟失了。
解決:session同步,不管訪問那一臺服務器,session均可以取獲得,利用redis緩存的方法,另外使用一個redis服務器專門用於存放用戶的session信息。這樣就不會出現用戶session丟失的狀況。(每次須要session,從緩存中取便可)
redis緩解數據庫壓力
本項目大量的利用了緩存技術,包括用戶信息緩存(分佈式session),商品信息的緩存,商品庫存緩存,訂單的緩存,頁面緩存,對象緩存減小了對數據庫服務器的訪問。
通用緩存key封裝
大量的緩存引用也出現了一個問題,如何識別不一樣模塊中的緩存(key值重複,如何辨別是不一樣模塊的key)
解決:利用一個抽象類,定義BaseKey(前綴),在裏面定義緩存key的前綴以及緩存的過時時間從而實現將緩存的key進行封裝。讓不一樣模塊繼承它,這樣每次存入一個模塊的緩存的時候,加上這個緩存特定的前綴,以及能夠統一制定不一樣的過時時間。
頁面靜態化(先後端分離)
頁面靜態化的主要目的是爲了加快頁面的加載速度,將商品的詳情和訂單詳情頁面作成靜態HTML(純的HTML),數據的加載只須要經過ajax來請求服務器,而且作了靜態化HTML頁面能夠緩存在客戶端的瀏覽器。
消息隊列完成異步下單
使用消息隊列完成異步下單,提高用戶體驗,削峯和降流
思路:
1.系統初始化,把商品庫存數量stock加載到Redis上面來。
2.後端收到秒殺請求,Redis預減庫存,若是庫存已經到達臨界值的時候,就不須要繼續請求下去,直接返回失敗,即後面的大量請求無需給系統帶來壓力。
3.判斷這個秒殺訂單造成沒有,判斷是否已經秒殺到了,避免一個帳戶秒殺多個商品,判斷是否重複秒殺。
4.庫存充足,且無重複秒殺,將秒殺請求封裝後消息入隊,同時給前端返回一個code (0),即表明返回排隊中。(返回的並非失敗或者成功,此時還不能判斷)
5.前端接收到數據後,顯示排隊中,並根據商品id輪詢請求服務器(考慮200ms輪詢一次)。
6.後端RabbitMQ監聽秒殺MIAOSHA_QUEUE的這名字的通道,若是有消息過來,獲取到傳入的信息,執行真正的秒殺以前,要判斷數據庫的庫存,判斷是否重複秒殺,而後執行秒殺事務(秒殺事務是一個原子操做:庫存減1,下訂單,寫入秒殺訂單)。
7.此時,前端根據商品id輪詢請求接口MiaoshaResult,查看是否生成了商品訂單,若是請求返回-1表明秒殺失敗,返回0表明排隊中,返回>0表明商品id說明秒殺成功。
安全性優化
雙重md5密碼校驗,秒殺接口地址的隱藏,接口限流防刷,數學公式驗證碼。
優雅的代碼編寫
接口的輸出結果作了一個Result封裝
對錯誤的代碼作了一個CodeMsg的封裝
訪問緩存作了一個key的封裝
項目難點及問題解決:
1. 使用JMeter作壓測的時候開啓5000個線程,系統跑不起來,出現異常
緣由:修改配置文件中redis的配置項poolMaxTotal 將其設置成1000。
#redis配置項
redis.poolMaxTotal=1000
redis.poolMaxldle=500
redis.poolMaxWait=500
1
2
3
4
2.使用了大量緩存,那麼就存在緩存擊穿和緩存雪崩以及緩存一致性等問題?
緩存穿透指的是對某個必定不存在的數據進行請求,該請求將會穿透緩存到達數據庫。
解決方案:對這些不存在的數據緩存一個空數據,對這類請求進行過濾。
緩存雪崩指的是因爲數據沒有被加載到緩存中,或者緩存數據在同一時間大面積失效(過時),又或者緩存服務器宕機,致使大量的請求都到達數據庫。
解決方案:
爲了防止緩存在同一時間大面積過時致使的緩存雪崩,能夠經過觀察用戶行爲,合理設置緩存過時時間來實現;
爲了防止緩存服務器宕機出現的緩存雪崩,可使用分佈式緩存,分佈式緩存中每個節點只緩存部分的數據,當某個節點宕機時能夠保證其它節點的緩存仍然可用。
也能夠進行緩存預熱,避免在系統剛啓動不久因爲還未將大量數據進行緩存而致使緩存雪崩。
例如:首先針對不一樣的緩存設置不一樣的過時時間,好比session緩存,在userKey這個前綴中,設置是30分鐘過時,而且每次用戶響應的話更新緩存時間。這樣每次取session,都會延長30分鐘,相對來講,就減小了緩存過時的概率
緩存一致性要求數據更新的同時緩存數據也可以實時更新。
解決方案:
在數據更新的同時當即去更新緩存,首先嚐試從緩存讀取,讀到數據則直接返回;若是讀不到,就讀數據庫,並將數據會寫到緩存,並返回。
在讀緩存以前先判斷緩存是不是最新的,若是不是最新的先進行更新,須要更新數據時,先更新數據庫,而後把緩存裏對應的數據失效掉(刪掉)。
3.大量的使用緩存,對於緩存服務器,也有很大的壓力,思考如何減小redis的訪問?
在redis預減庫存的時候,內存中維護一個isOvermap做爲一個內存標記,當沒有庫存的時候,將其置爲true。每次秒殺業務訪問redis以前,查一下map標記,若是true說明沒有庫存,就直接返回失敗,無需再去請求redis服務器。
4.在高併發請求的業務場景,大量請求來不及處理,甚至出現請求堆積時候?
消息隊列,用來異步處理請求。每次請求過來,先不去處理請求,而是放入消息隊列,而後在後檯布置一個監聽器,分別監聽不一樣業務的消息隊列,有消息來的時候,才進行秒殺業務邏輯。這樣防止多個請求同時操做的時候,數據庫鏈接過多的異常。
5.怎麼保證一個用戶不能重複下單?
解決:秒殺訂單表中創建一個惟一索引(所引是用戶Id與商品goodsId),使得第一個記錄能夠插入,第二個則出錯,而後經過事務回滾,防止一個用戶同時發出多個請求的處理,秒殺到多個商品。
惟一索引,便是惟一的意思,在數據庫表結構中對字段添加惟一索引後進行數據庫進行存儲操做時數據庫會判斷庫中是否已經存在此數據,不存在此數據時才能進行插入操做。
這雖然是個小技能,但實際上在業務開發中是個很實用的技能,好比在高併發業務中,數據庫如何杜絕數據併發插入兩條相同的訂單號呢?添加一個惟一索引固然是最快捷的方法之一,固然是添加索引仍是經過業務代碼去解決因公司業務而定
6.怎麼解決超賣現象?
超賣場景:不一樣用戶在讀請求的時候,發現商品庫存足夠,而後同時發起請求,進行秒殺操做,減庫存,致使庫存減爲負數。
最簡單的方法,更新數據庫減庫存的時候,進行庫存限制條件,在reduceStock(GoodsVo goodsvo)這個方法裏,sql要多加一個stock_count > 0 ,使用數據庫特性來保證超賣的問題,只有stock_count還大於0的時候纔去讀stock_count而後減1操做
@Update("update miaosha_goods set stock_count=stock_count-1 where goods_id=#{goodsId} and stock_count>0")
public void reduceStock(MiaoshaGoods goods);
1
2
7.頁面靜態化的過程及什麼是瀏覽器緩存?
將HTML靜態頁面緩存在客戶端瀏覽器,只有數據經過ajax異步調用接口來獲取,僅僅交互的是部分數據,減小了帶寬,也加快用戶訪問的速度。
瀏覽器緩存就是把一個已經請求過的Web資源(如html頁面,圖片,js,數據等)拷貝一份副本儲存在瀏覽器中。緩存會根據進來的請求保存輸出內容的副本。當下一個請求來到的時候,若是是相同的URL,緩存會根據緩存機制決定是直接使用副本響應訪問請求,仍是向源服務器再次發送請求。比較常見的就是瀏覽器會緩存訪問過網站的網頁,當再次訪問這個URL地址的時候,若是網頁沒有更新,就不會再次下載網頁,而是直接使用本地緩存的網頁。只有當網站明確標識資源已經更新,瀏覽器纔會再次下載網頁。
8.秒殺架構設計理念?
限流:鑑於只有少部分用戶可以秒殺成功,因此要限制大部分流量,只容許少部分流量進入服務後端。
削峯:對於秒殺系統瞬時會有大量用戶涌入,因此在搶購一開始會有很高的瞬間峯值。高峯值流量是壓垮系統很重要的緣由,因此如何把瞬間的高流量變成一段時間平穩的流量也是設計秒殺系統很重要的思路。實現削峯的經常使用的方法有利用緩存和消息中間件等技術。
異步處理:秒殺系統是一個高併發系統,採用異步處理模式能夠極大地提升系統併發量,其實異步處理就是削峯的一種實現方式。
內存緩存:秒殺系統最大的瓶頸通常都是數據庫讀寫,因爲數據庫讀寫屬於磁盤IO,性能很低,若是可以把部分數據或業務邏輯轉移到內存緩存,效率會有極大地提高。
可拓展:固然若是咱們想支持更多用戶,更大的併發,最好就將系統設計成彈性可拓展的,若是流量來了,拓展機器就行了。像淘寶、京東等雙十一活動時會增長大量機器應對交易高峯。
9.秒殺系統架構設計思路?
將請求攔截在系統上游,下降下游壓力:秒殺系統特色是併發量極大,但實際秒殺成功的請求數量卻不多,因此若是不在前端攔截極可能形成數據庫讀寫鎖衝突,最終請求超時。
利用緩存:利用緩存可極大提升系統讀寫速度。
消息隊列:消息隊列能夠削峯,將攔截大量併發請求,這也是一個異步處理過程,後臺業務根據本身的處理能力,從消息隊列中主動的拉取請求消息進行業務處理。
10.假如減了庫存用戶沒有支付,庫存怎麼還原繼續參加搶購?
設定一個最長付款時間,好比30分鐘,後臺有個定時任務(使用定時器Timer),輪訓超過30分鐘的待付款訂單(數據庫裏面斷定訂單狀態),而後關閉訂單,恢復庫存。
本文分享自微信公衆號 - golang算法架構leetcode技術php(golangLeetcode)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。