秒杀系统优化方案(下)吐血整理
接上篇秒殺系統優化方案(上)吐血整理
3. 深入優化設計
3.1? ?初始方案問題分析
在前面針對數據庫的優化中,由于數據庫行級鎖存在競爭造成大量的串行阻塞,我們使用了存儲過程(或者觸發器)等技術綁定操作,整個事務在MySQL端完成,把整個熱點執行放在一個過程當中一次性完成,可以屏蔽掉網絡延遲時間,減少行級鎖持有時間,提高事務并發訪問速度。
可是問題時并發的流量實際上都是直接穿透讓MYSQL自己去抗,比如說庫存是否賣完以及用戶是否重復秒殺都完全是靠查詢數據庫去判斷,造成數據庫不必要的負擔非常大,然而這些都可以放在緩存做一個標記在服務層進行攔截,對于中小規模的并發還可以,但是真正的超高并發,顯然這個還不完善。
3.2??? 優化的方向和思路
方向:將請求盡量攔截在系統上游
傳統秒殺系統之所以掛,請求都壓倒了后端數據層,數據讀寫鎖沖突嚴重,并發高響應慢,幾乎所有請求都超時,流量雖大,下單成功的有效流量甚小【一趟火車其實只有2000張票,200w個人來買,基本沒有人能買成功,請求有效率為0】?
思路:限流和削峰
限流:屏蔽掉無用的流量,允許少部分流量流向后端。
削峰:瞬時大流量峰值容易壓垮系統,解決這個問題是重中之重。常用的消峰方法有異步處理、緩存和消息中間件等技術。
?
異步處理:秒殺系統是一個高并發系統,采用異步處理模式可以極大地提高系統并發量,其實異步處理就是削峰的一種實現方式。
緩存:秒殺系統本身是一個典型的讀多寫少的應用場景【一趟火車其實只有2000張票,200w個人來買,最多2000個人下單成功,其他人都是查詢庫存,寫比例只有0.1%,讀比例占99.9%】,非常適合使用緩存。
消息隊列:消息隊列可以削峰,將攔截大量并發請求,這也是一個異步處理過程,后臺業務根據自己的處理能力,從消息隊列中主動的拉取請求消息進行業務處理。
3.3???前端優化
3.3.1? ?靜態資源緩存
1. 頁面靜態化
對商品詳情和訂單詳情進行頁面靜態化處理,頁面是存在html,動態數據是通過接口從服務端獲取,實現前后端分離,靜態頁面無需連接數據庫打開速度較動態頁面會有明顯提高。
2.頁面緩存
通過CDN緩存靜態資源,來抗峰值。不使用CDN的話也可以通過在手動渲染得到的html頁面緩存到redis。
3.3.2? ?限流手段
1. 使用數學公式驗證碼
描述:點擊秒殺前,先讓用戶輸入數學公式驗證碼,驗證正確才能進行秒殺。
好處:
1)防止惡意的機器人和爬蟲
2)分散用戶的請求
實現:
1)前端通過把商品id作為參數調用服務端創建驗證碼接口
2)服務端根據前端傳過來的商品id和用戶id生成驗證碼,并將商品id+用戶id作為key,生成的驗證碼作為value存入redis,同時將生成的驗證碼輸入圖片寫入imageIO讓前端展示。
3)將用戶輸入的驗證碼與根據商品id+用戶id從redis查詢到的驗證碼對比,相同就返回驗證成功,進入秒殺;不同或從redis查詢的驗證碼為空都返回驗證失敗,刷新驗證碼重試
?
2. 禁止重復提交
用戶提交之后按鈕置灰,禁止重復提交?
3.4? ? 中間代理層
可利用負載均衡(例如反響代理Nginx等)使用多個服務器并發處理請求,減小服務器壓力。
3.5? ? ?后端優化
3.5.1? ?控制層(網關層)
限制同一UserID訪問頻率:盡量攔截瀏覽器請求,但針對某些惡意攻擊或其它插件,在服務端控制層需要針對同一個訪問uid,限制訪問頻率。
1.??? 利用緩存
設置緩存有效時間,在緩存中計數,如果在緩存的有效時間內請求的次數超了的話,就返回請求訪問太頻繁。
2.??? 利用RateLimiter
RateLimiter是guava提供的基于令牌桶算法的限流實現類,通過調整生成token的速率來限制用戶頻繁訪問秒殺頁面,從而達到防止超大流量沖垮系統。(令牌桶算法的原理是系統會以一個恒定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當桶里沒有令牌可取時,則拒絕服務。
3.5.2? ?服務層
當用戶量非常大的時候,攔截流量后的請求訪問量還是非常大,此時仍需進一步優化。
1.??? 業務分離:將秒殺業務系統和其他業務分離,單獨放在高配服務器上,可以集中資源對訪問請求抗壓?!獞玫牟鸱?/p>
2.??? 采用消息隊列緩存請求:將大流量請求寫到消息隊列緩存,利用服務器根據自己的處理能力主動到消息緩存隊列中抓取任務處理請求,數據庫層訂閱消息減庫存,減庫存成功的請求返回秒殺成功,失敗的返回秒殺結束。
3.??? 利用緩存應對讀請求:對于讀多寫少業務,大部分請求是查詢請求,所以可以讀寫分離,利用緩存分擔數據庫壓力。
4.??? 利用緩存應對寫請求:緩存也是可以應對寫請求的,可把數據庫中的庫存數據轉移到Redis緩存中,所有減庫存操作都在Redis中進行,然后再通過后臺進程把Redis中的用戶秒殺請求同步到數據庫中。
可以將緩存和消息中間件 組合起來,緩存系統負責接收記錄用戶請求,消息中間件負責將緩存中的請求同步到數據庫。
?
方案:本地標記 + redis預處理 + RabbitMQ異步下單 + 客戶端輪詢
描述:通過三級緩沖保護,1、本地標記 2、redis預處理 3、RabbitMQ異步下單,最后才會訪問數據庫,這樣做是為了最大力度減少對數據庫的訪問。
實現:
3.5.3? 數據庫層
? 數據庫層是最脆弱的一層,一般在應用設計時在上游就需要把請求攔截掉,數據庫層只承擔“能力范圍內”的訪問請求。所以,上面通過在服務層引入隊列和緩存,讓最底層的數據庫高枕無憂。但依然可以進行如下方向的優化:
?對于秒殺系統,直接訪問數據庫的話,存在一個【事務競爭優化】問題,可使用存儲過程(或者觸發器)等技術綁定操作,整個事務在MySQL端完成,把整個熱點執行放在一個過程當中一次性完成,可以屏蔽掉網絡延遲時間,減少行級鎖持有時間,提高事務并發訪問速度。
?
3.7? 優化秒殺流程
上面的秒殺流程對應的流程圖如下:
步驟1到12,主體是redis預減庫存,生成消息隊列:
?
步驟13到14是處理消息隊列:
步驟15,是客戶端請求秒殺結果:
?
4. 問題解析
1.????? 如何解決庫存的超賣問題?
賣超原因:
(1)一個用戶同時發出了多個請求,如果庫存足夠,沒加限制,用戶就可以下多個訂單。(2)減庫存的sql上沒有加庫存數量的判斷,并發的時候也會導致把庫存減成負數。
解決辦法:
(1):在后端的秒殺表中,對user_id和goods_id加唯一索引,確保一個用戶對一個商品絕對不會生成兩個訂單。
(2):我們的減庫存的sql上應該加上庫存數量的判斷
數據庫自身是有行級鎖的,每次減庫存的時候判斷count>0,它實際上是串行的執行update的,因此絕對不會賣超!。
UPDATE seckill
??????? SET number = number-1
??????? WHERE seckill_id=#{seckillId}
??????? AND start_time <#{killTime}
??????? AND end_time >= #{killTime}
??????? AND number > 0;
?2.? ??如何解決少賣問題—Redis預減成功而DB扣庫存失敗?
前面的方案中會出現一個少賣的問題。Redis在預減庫存的時候,在初始化的時候就放置庫存的大小,redis的原子減操作保證了多少庫存就會減多少,也就會在消息隊列中放多少。
現在考慮兩種情況:
1)數據庫那邊出現非庫存原因比如網絡等造成減庫存失敗,而這時redis已經減了。
2)萬一一個用戶發出多個請求,而且這些請求恰巧比別的請求更早到達服務器,如果庫存足夠,redis就會減多次,redis提前進入賣空狀態,并拒絕。不過這兩種情況出現的概率都是非常低的。
兩種情況都會出現少賣的問題,實際上也是緩存和數據庫出現不一致的問題!
但是我們不是非得解決不一致的問題,本身使用緩存就難以保證強一致性:
在redis中設置庫存比真實庫存多一些就行。
3.???秒殺過程中怎么保證redis緩存和數據庫的一致性?
在其他一般讀大于寫的場景,一般處理的原則是:緩存只做失效,不做更新。
采用Cache-Aside pattern:
失效:應用程序先從cache取數據,沒有得到,則從數據庫中取數據,成功后,放到緩存中。
更新:先把數據存到數據庫中,成功后,再讓緩存失效。
?4.??Redis中的庫存如何與DB中的庫存保持一致?
Redis中的數量不是庫存,它的作用僅僅時候只是為了阻擋多余的請求透傳到db,起到一個保護DB的作用。因為秒殺商品的數量是有限的,比如只有10個,讓1萬個請求去訪問DB是沒有意義的,因為最多只有10個請求會下單成功,剩余的9990個請求都是無效的,是可以不用去訪問db而直接失敗的。
因此,這是一個偽問題,我們是不需要保持一致的。
?5.???為什么要隱藏秒殺接口?
html是可以被右鍵->查看源代碼,如果秒殺地址寫死在源文件中,是很容易就被惡意用戶拿到的,就可以被機器人利用來刷接口,這對于其他用戶來說是不公平的,我們也不希望看到這種情況。所以我們可以控制讓用戶在沒有到秒殺時間的時候不能獲取到秒殺地址,只返回秒殺的開始時間。
當到秒殺時間的時候才返回秒殺地址即seckill_id以及根據seckill_id和salt加密的MD5,前端再次拿著seckill_id和MD5才能執行秒殺。假如用戶在秒殺開始前猜測到秒殺地址seckill_id去請求秒殺,也是不會成功的,因為它拿不到需要驗證的MD5。這里的MD5相當于是用戶進行秒殺的憑證。
6.???一個秒殺系統,500用戶同時登陸訪問服務器A,服務器B如何快速利用登錄名(假設是電話號碼或者郵箱)做其他查詢?
主從復制,讀寫分離
?
轉載于:https://www.cnblogs.com/xiangkejin/p/9351501.html
總結
以上是生活随笔為你收集整理的秒杀系统优化方案(下)吐血整理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 游戏设计、原型与开发:基于Unity与C
- 下一篇: Windows下安装和配置tomca(免