秒殺應該考慮哪些問題
超賣問題
分析秒殺的業(yè)務場景激率,最重要的有一點就是超賣問題,假如備貨只有100個勿决,但是最終超賣了200乒躺,一般來講秒殺系統(tǒng)的價格都比較低,如果超賣將嚴重影響公司的財產(chǎn)利益低缩,因此首當其沖的就是解決商品的超賣問題嘉冒。
高并發(fā)
秒殺具有時間短、并發(fā)量大的特點咆繁,秒殺持續(xù)時間只有幾分鐘讳推,而一般公司都為了制造轟動效應,會以極低的價格來吸引用戶玩般,因此參與搶購的用戶會非常的多银觅。短時間內(nèi)會有大量請求涌進來,后端如何防止并發(fā)過高造成緩存擊穿或者失效坏为,擊垮數(shù)據(jù)庫都是需要考慮的問題究驴。
接口防刷
現(xiàn)在的秒殺大多都會出來針對秒殺對應的軟件慨仿,這類軟件會模擬不斷向后臺服務器發(fā)起請求,一秒幾百次都是很常見的纳胧,如何防止這類軟件的重復無效請求镰吆,防止不斷發(fā)起的請求也是需要我們針對性考慮的
秒殺url
對于普通用戶來講,看到的只是一個比較簡單的秒殺頁面跑慕,在未達到規(guī)定時間万皿,秒殺按鈕是灰色的,一旦到達規(guī)定時間核行,灰色按鈕變成可點擊狀態(tài)牢硅。這部分是針對小白用戶的,如果是稍微有點電腦功底的用戶芝雪,會通過F12看瀏覽器的network看到秒殺的url减余,通過特定軟件去請求也可以實現(xiàn)秒殺〕拖担或者提前知道秒殺url的人位岔,一請求就直接實現(xiàn)秒殺了。這個問題我們需要考慮解決
數(shù)據(jù)庫設計
秒殺有把我們服務器擊垮的風險堡牡,如果讓它與我們的其他業(yè)務使用在同一個數(shù)據(jù)庫中抒抬,耦合在一起,就很有可能牽連和影響其他的業(yè)務晤柄。如何防止這類問題發(fā)生擦剑,就算秒殺發(fā)生了宕機、服務器卡死問題芥颈,也應該讓他盡量不影響線上正常進行的業(yè)務
大量請求問題
按照1.2的考慮惠勒,就算使用緩存還是不足以應對短時間的高并發(fā)的流量的沖擊。如何承載這樣巨大的訪問量爬坑,同時提供穩(wěn)定低時延的服務保證纠屋,是需要面對的一大挑戰(zhàn)。我們來算一筆賬妇垢,假如使用的是redis緩存巾遭,單臺redis服務器可承受的QPS大概是4W左右肉康,如果一個秒殺吸引的用戶量足夠多的話闯估,單QPS可能達到幾十萬,單體redis還是不足以支撐如此巨大的請求量吼和。緩存會被擊穿涨薪,直接滲透到DB,從而擊垮mysql.后臺會將會大量報錯
秒殺系統(tǒng)的設計和技術方案
秒殺系統(tǒng)數(shù)據(jù)庫設計
針對提出的秒殺數(shù)據(jù)庫的問題,因此應該單獨設計一個秒殺數(shù)據(jù)庫炫乓,防止因為秒殺活動的高并發(fā)訪問拖垮整個網(wǎng)站刚夺。這里只需要兩張表献丑,一張是秒殺訂單表
秒殺系統(tǒng)數(shù)據(jù)庫設計
其實應該還有幾張表,商品表:可以關聯(lián)goods_id查到具體的商品信息侠姑,商品圖像创橄、名稱、平時價格莽红、秒殺價格等妥畏,還有用戶表:根據(jù)用戶user_id可以查詢到用戶昵稱、用戶手機號安吁,收貨地址等其他額外信息醉蚁,這個具體就不給出實例了。
秒殺url的設計
為了避免有程序訪問經(jīng)驗的人通過下單頁面url直接訪問后臺接口來秒殺貨品鬼店,我們需要將秒殺的url實現(xiàn)動態(tài)化网棍,即使是開發(fā)整個系統(tǒng)的人都無法在秒殺開始前知道秒殺的url。
具體的做法就是通過md5加密一串隨機字符作為秒殺的url妇智,然后前端訪問后臺獲取具體的url滥玷,后臺校驗通過之后才可以繼續(xù)秒殺。
秒殺頁面靜態(tài)化
將商品的描述巍棱、參數(shù)罗捎、成交記錄、圖像拉盾、評價等全部寫入到一個靜態(tài)頁面桨菜,用戶請求不需要通過訪問后端服務器,不需要經(jīng)過數(shù)據(jù)庫捉偏,直接在前臺客戶端生成倒得,這樣可以最大可能的減少服務器的壓力。
具體的方法可以使用freemarker模板技術夭禽,建立網(wǎng)頁模板霞掺,填充數(shù)據(jù),然后渲染網(wǎng)頁
單體redis升級為集群redis
秒殺是一個讀多寫少的場景讹躯,使用redis做緩存再合適不過菩彬。不過考慮到緩存擊穿問題,我們應該構建redis集群潮梯,采用哨兵模式骗灶,可以提升redis的性能和可用性。
使用nginx
nginx是一個高性能web服務器秉馏,它的并發(fā)能力可以達到幾萬耙旦,而tomcat只有幾百。通過nginx映射客戶端請求萝究,再分發(fā)到后臺tomcat服務器集群中可以提升并發(fā)能力免都。
精簡sql
典型的一個場景是在進行扣減庫存的時候锉罐,傳統(tǒng)的做法是先查詢庫存,再去update绕娘。這樣的話需要兩個sql脓规,而實際上一個sql我們就可以完成的。
可以用這樣的做法:update miaosha_goods set stock =stock-1 where goos_id ={#goods_id} and version = #{version} and sock>0;這樣的話险领,就可以保證庫存不會超賣并且一次更新庫存,還有注意一點這里使用了版本號的樂觀鎖抖拦,相比較悲觀鎖,它的性能較好舷暮。
redis預減庫存
很多請求進來态罪,都需要后臺查詢庫存,這是一個頻繁讀的場景∠旅妫可以使用redis來預減庫存复颈,在秒殺開始前可以在redis設值,比如redis.set(goodsId,100),這里預放的庫存為100可以設值為常量),每次下單成功之后,Integer stock = (Integer)redis.get(goosId); 然后判斷sock的值沥割,如果小于常量值就減去1;
不過注意當取消的時候,需要增加庫存耗啦,增加庫存的時候也得注意不能大于之間設定的總庫存數(shù)(查詢庫存和扣減庫存需要原子操作,此時可以借助lua腳本)下次下單再獲取庫存的時候,直接從redis里面查就可以了机杜。
接口限流
秒殺最終的本質(zhì)是數(shù)據(jù)庫的更新帜讲,但是有很多大量無效的請求,我們最終要做的就是如何把這些無效的請求過濾掉椒拗,防止?jié)B透到數(shù)據(jù)庫似将。限流的話,需要入手的方面很多:
前端限流
首先第一步就是通過前端限流蚀苛,用戶在秒殺按鈕點擊以后發(fā)起請求在验,那么在接下來的5秒是無法點擊(通過設置按鈕為disable)。這一小舉措開發(fā)起來成本很小堵未,但是很有效腋舌。
同一個用戶xx秒內(nèi)重復請求直接拒絕
具體多少秒需要根據(jù)實際業(yè)務和秒殺的人數(shù)而定,一般限定為10秒渗蟹。具體的做法就是通過redis的鍵過期策略块饺,首先對每個請求都從String value = redis.get(userId);如果獲取到這個
value為空或者為null,表示它是有效的請求雌芽,然后放行這個請求授艰。如果不為空表示它是重復性請求,直接丟掉這個請求膘怕。如果有效,采用redis.setexpire(userId,value,10).value可以是任意值想诅,一般放業(yè)務屬性比較好,這個是設置以userId為key,10秒的過期時間(10秒后,key對應的值自動為null)
令牌桶算法限流
接口限流的策略有很多岛心,我們這里采用令牌桶算法来破。令牌桶算法的基本思路是每個請求嘗試獲取一個令牌,后端只處理持有令牌的請求忘古,生產(chǎn)令牌的速度和效率我們都可以自己限定徘禁,guava提供了RateLimter的api供我們使用。
異步下單
為了提升下單的效率髓堪,并且防止下單服務的失敗送朱。需要將下單這一操作進行異步處理。最常采用的辦法是使用隊列干旁,隊列最顯著的三個優(yōu)點:異步驶沼、削峰、解耦争群。這里可以采用rabbitmq
回怜,在后臺經(jīng)過了限流、庫存校驗之后换薄,流入到這一步驟的就是有效請求玉雾。然后發(fā)送到隊列里,隊列接受消息轻要,異步下單复旬。下完單,入庫沒有問題可以用短信通知用戶秒殺成功冲泥。假如失敗的話,可以采用補償機制驹碍,重試。
服務降級
假如在秒殺過程中出現(xiàn)了某個服務器宕機凡恍,或者服務不可用幸冻,應該做好后備工作。之前的博客里有介紹通過Hystrix進行服務熔斷和降級咳焚,可以開發(fā)一個備用服務洽损,假如服務器真的宕機了,直接給用戶一個友好的提示返回革半,而不是直接卡死碑定,服務器錯誤等生硬的反饋。
這就是我設計出來的秒殺流程圖,當然不同的秒殺體量針對的技術選型都不一樣又官,這個流程可以支撐起幾十萬的流量延刘,如果是成千萬破億那就得重新設計了。比如數(shù)據(jù)庫的分庫分表六敬、隊列改成用kafka碘赖、redis增加集群數(shù)量等手段。