<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>



      前言:秒殺系統(tǒng)相信很多人見過,比如京東或者淘寶的秒殺,小米手機(jī)的秒殺,那么秒殺系統(tǒng)的后臺是如何實(shí)現(xiàn)的呢?我們?nèi)绾卧O(shè)計(jì)一個(gè)秒殺系統(tǒng)呢?對于秒殺系統(tǒng)應(yīng)該考慮哪些問題?如何設(shè)計(jì)出健壯的秒殺系統(tǒng)?本期我們就來探討一下這個(gè)問題:



      ?

      博客的目錄

      一:秒殺系統(tǒng)應(yīng)該考慮的問題

      二:秒殺系統(tǒng)的設(shè)計(jì)和技術(shù)方案

      三:系統(tǒng)架構(gòu)圖

      四:總結(jié)

      一:秒殺應(yīng)該考慮哪些問題

      1.1:超賣問題

      ?
      ?分析秒殺的業(yè)務(wù)場景,最重要的有一點(diǎn)就是超賣問題,假如備貨只有100個(gè),但是最終超賣了200,一般來講秒殺系統(tǒng)的價(jià)格都比較低,如果超賣將嚴(yán)重影響公司的財(cái)產(chǎn)利益,因此首當(dāng)其沖的就是解決商品的超賣問題。

      1.2:高并發(fā)

      秒殺具有時(shí)間短、并發(fā)量大的特點(diǎn),秒殺持續(xù)時(shí)間只有幾分鐘,而一般公司都為了制造轟動效應(yīng),會以極低的價(jià)格來吸引用戶,因此參與搶購的用戶會非常的多。短時(shí)間內(nèi)會

      有大量請求涌進(jìn)來,后端如何防止并發(fā)過高造成緩存擊穿或者失效,擊垮數(shù)據(jù)庫都是需要考慮的問題。

      1.3:接口防刷


      現(xiàn)在的秒殺大多都會出來針對秒殺對應(yīng)的軟件,這類軟件會模擬不斷向后臺服務(wù)器發(fā)起請求,一秒幾百次都是很常見的,如何防止這類軟件的重復(fù)無效請求,防止不斷發(fā)起的請求也是需要我們針對性考慮的

      1.4:秒殺url


      對于普通用戶來講,看到的只是一個(gè)比較簡單的秒殺頁面,在未達(dá)到規(guī)定時(shí)間,秒殺按鈕是灰色的,一旦到達(dá)規(guī)定時(shí)間,灰色按鈕變成可點(diǎn)擊狀態(tài)。這部分是針對小白用戶的,如果是稍微有點(diǎn)電腦功底的用戶,會通過F12看瀏覽器的network看到秒殺的url,通過特定軟件去請求也可以實(shí)現(xiàn)秒殺?;蛘咛崆爸烂霘rl的人,一請求就直接實(shí)現(xiàn)秒殺了。這個(gè)問題我們需要考慮解決

      1.5:數(shù)據(jù)庫設(shè)計(jì)


      秒殺有把我們服務(wù)器擊垮的風(fēng)險(xiǎn),如果讓它與我們的其他業(yè)務(wù)使用在同一個(gè)數(shù)據(jù)庫中,耦合在一起,就很有可能牽連和影響其他的業(yè)務(wù)。如何防止這類問題發(fā)生,就算秒殺發(fā)生了宕機(jī)、服務(wù)器卡死問題,也應(yīng)該讓他盡量不影響線上正常進(jìn)行的業(yè)務(wù)

      1.6:大量請求問題


      按照1.2的考慮,就算使用緩存還是不足以應(yīng)對短時(shí)間的高并發(fā)的流量的沖擊。如何承載這樣巨大的訪問量,同時(shí)提供穩(wěn)定低時(shí)延的服務(wù)保證,是需要面對的一大挑戰(zhàn)。我們來算一筆賬,假如使用的是redis緩存,單臺redis服務(wù)器可承受的QPS大概是4W左右,如果一個(gè)秒殺吸引的用戶量足夠多的話,單QPS可能達(dá)到幾十萬,單體redis還是不足以支撐如此巨大的請求量。緩存會被擊穿,直接滲透到DB,從而擊垮mysql.后臺會將會大量報(bào)錯(cuò)

      二:秒殺系統(tǒng)的設(shè)計(jì)和技術(shù)方案

      2.1:秒殺系統(tǒng)數(shù)據(jù)庫設(shè)計(jì)


      針對1.5提出的秒殺數(shù)據(jù)庫的問題,因此應(yīng)該單獨(dú)設(shè)計(jì)一個(gè)秒殺數(shù)據(jù)庫,防止因?yàn)槊霘⒒顒拥母卟l(fā)訪問拖垮整個(gè)網(wǎng)站。這里只需要兩張表,一張是秒殺訂單表,一張是秒殺貨品表



      ??


      其實(shí)應(yīng)該還有幾張表,商品表:可以關(guān)聯(lián)goods_id查到具體的商品信息,商品圖像、名稱、平時(shí)價(jià)格、秒殺價(jià)格等,還有用戶表:根據(jù)用戶user_id可以查詢到用戶昵稱、用戶手機(jī)號,收貨地址等其他額外信息,這個(gè)具體就不給出實(shí)例了。

      2.2:秒殺url的設(shè)計(jì)


      為了避免有程序訪問經(jīng)驗(yàn)的人通過下單頁面url直接訪問后臺接口來秒殺貨品,我們需要將秒殺的url實(shí)現(xiàn)動態(tài)化,即使是開發(fā)整個(gè)系統(tǒng)的人都無法在秒殺開始前知道秒殺的url。具體的做法就是通過md5加密一串隨機(jī)字符作為秒殺的url,然后前端訪問后臺獲取具體的url,后臺校驗(yàn)通過之后才可以繼續(xù)秒殺。

      2.3:秒殺頁面靜態(tài)化


      將商品的描述、參數(shù)、成交記錄、圖像、評價(jià)等全部寫入到一個(gè)靜態(tài)頁面,用戶請求不需要通過訪問后端服務(wù)器,不需要經(jīng)過數(shù)據(jù)庫,直接在前臺客戶端生成,這樣可以最大可能的減少服務(wù)器的壓力。具體的方法可以使用freemarker模板技術(shù),建立網(wǎng)頁模板,填充數(shù)據(jù),然后渲染網(wǎng)頁

      2.4:單體redis升級為集群redis

      秒殺是一個(gè)讀多寫少的場景,使用redis做緩存再合適不過。不過考慮到緩存擊穿問題,我們應(yīng)該構(gòu)建redis集群,采用哨兵模式,可以提升redis的性能和可用性。

      ?2.5:使用nginx


      nginx是一個(gè)高性能web服務(wù)器,它的并發(fā)能力可以達(dá)到幾萬,而tomcat只有幾百。通過nginx映射客戶端請求,再分發(fā)到后臺tomcat服務(wù)器集群中可以大大提升并發(fā)能力。

      2.6:精簡sql


      典型的一個(gè)場景是在進(jìn)行扣減庫存的時(shí)候,傳統(tǒng)的做法是先查詢庫存,再去update。這樣的話需要兩個(gè)sql,而實(shí)際上一個(gè)sql我們就可以完成的??梢杂眠@樣的做法:update
      miaosha_goods? set stock =stock-1 where goos_id ={#goods_id} and? version =
      #{version} and sock>0;這樣的話,就可以保證庫存不會超賣并且一次更新庫存,還有注意一點(diǎn)這里使用了版本號的樂觀鎖,相比較悲觀鎖,它的性能較好。

      2.7:redis預(yù)減庫存


      很多請求進(jìn)來,都需要后臺查詢庫存,這是一個(gè)頻繁讀的場景。可以使用redis來預(yù)減庫存,在秒殺開始前可以在redis設(shè)值,比如redis.set(goodsId,100),這里預(yù)放的庫存為100可以設(shè)值為常量),每次下單成功之后,Integer
      stock = (Integer)redis.get(goosId);
      然后判斷sock的值,如果小于常量值就減去1;不過注意當(dāng)取消的時(shí)候,需要增加庫存,增加庫存的時(shí)候也得注意不能大于之間設(shè)定的總庫存數(shù)(查詢庫存和扣減庫存需要原子操作,此時(shí)可以借助lua腳本)下次下單再獲取庫存的時(shí)候,直接從redis里面查就可以了。

      2.8:接口限流

      秒殺最終的本質(zhì)是數(shù)據(jù)庫的更新,但是有很多大量無效的請求,我們最終要做的就是如何把這些無效的請求過濾掉,防止?jié)B透到數(shù)據(jù)庫。限流的話,需要入手的方面很多:

      2.9.1:前端限流


      首先第一步就是通過前端限流,用戶在秒殺按鈕點(diǎn)擊以后發(fā)起請求,那么在接下來的5秒是無法點(diǎn)擊(通過設(shè)置按鈕為disable)。這一小舉措開發(fā)起來成本很小,但是很有效。

      2.9.2:同一個(gè)用戶xx秒內(nèi)重復(fù)請求直接拒絕

      具體多少秒需要根據(jù)實(shí)際業(yè)務(wù)和秒殺的人數(shù)而定,一般限定為10秒。具體的做法就是通過redis的鍵過期策略,首先對每個(gè)請求都從String value =
      redis.get(userId);如果獲取到這個(gè)


      value為空或者為null,表示它是有效的請求,然后放行這個(gè)請求。如果不為空表示它是重復(fù)性請求,直接丟掉這個(gè)請求。如果有效,采用redis.setexpire(userId,value,10).value可以是任意值,一般放業(yè)務(wù)屬性比較好,這個(gè)是設(shè)置以userId為key,10秒的過期時(shí)間(10秒后,key對應(yīng)的值自動為null)

      2.7.3:令牌桶算法限流


      接口限流的策略有很多,我們這里采用令牌桶算法。令牌桶算法的基本思路是每個(gè)請求嘗試獲取一個(gè)令牌,后端只處理持有令牌的請求,生產(chǎn)令牌的速度和效率我們都可以自己限定,guava提供了RateLimter的api供我們使用。以下做一個(gè)簡單的例子,注意需要引入guava
      public class TestRateLimiter { public static void main(String[] args) { //
      1秒產(chǎn)生1個(gè)令牌 final RateLimiter rateLimiter = RateLimiter.create(1); for (int i = 0;
      i < 10; i++) { //該方法會阻塞線程,直到令牌桶中能取到令牌為止才繼續(xù)向下執(zhí)行。 double waitTime=
      rateLimiter.acquire(); System.out.println("任務(wù)執(zhí)行" + i + "等待時(shí)間" + waitTime); }
      System.out.println("執(zhí)行結(jié)束"); } }
      ?
      ?上面代碼的思路就是通過RateLimiter來限定我們的令牌桶每秒產(chǎn)生1個(gè)令牌(生產(chǎn)的效率比較低),循環(huán)10次去執(zhí)行任務(wù)。acquire會阻塞當(dāng)前線程直到獲取到令牌,也就是如果任務(wù)沒有獲取到令牌,會一直等待。那么請求就會卡在我們限定的時(shí)間內(nèi)才可以繼續(xù)往下走,這個(gè)方法返回的是線程具體等待的時(shí)間。執(zhí)行如下;




      可以看到任務(wù)執(zhí)行的過程中,第1個(gè)是無需等待的,因?yàn)橐呀?jīng)在開始的第1秒生產(chǎn)出了令牌。接下來的任務(wù)請求就必須等到令牌桶產(chǎn)生了令牌才可以繼續(xù)往下執(zhí)行。如果沒有獲取到就會阻塞(有一個(gè)停頓的過程)。不過這個(gè)方式不太好,因?yàn)橛脩羧绻诳蛻舳苏埱?,如果較多的話,直接后臺在生產(chǎn)token就會卡頓(用戶體驗(yàn)較差),它是不會拋棄任務(wù)的,我們需要一個(gè)更優(yōu)秀的策略:如果超過某個(gè)時(shí)間沒有獲取到,直接拒絕該任務(wù)。接下來再來個(gè)案例:
      public class TestRateLimiter2 { public static void main(String[] args) { final
      RateLimiter rateLimiter = RateLimiter.create(1); for (int i = 0; i < 10; i++) {
      long timeOut = (long) 0.5; boolean isValid = rateLimiter.tryAcquire(timeOut,
      TimeUnit.SECONDS); System.out.println("任務(wù)" + i + "執(zhí)行是否有效:" + isValid); if (!
      isValid) {continue; } System.out.println("任務(wù)" + i + "在執(zhí)行"); }
      System.out.println("結(jié)束"); } }
      其中用到了tryAcquire方法,這個(gè)方法的主要作用是設(shè)定一個(gè)超時(shí)的時(shí)間,如果在指定的時(shí)間內(nèi)預(yù)估(注意是預(yù)估并不會真實(shí)的等待),
      如果能拿到令牌就返回true,如果拿不到就返回false.然后我們讓無效的直接跳過,這里設(shè)定每秒生產(chǎn)1個(gè)令牌,讓每個(gè)任務(wù)嘗試在

      0.5秒獲取令牌,如果獲取不到,就直接跳過這個(gè)任務(wù)(放在秒殺環(huán)境里就是直接拋棄這個(gè)請求);程序?qū)嶋H運(yùn)行如下:



      只有第1個(gè)獲取到了令牌,順利執(zhí)行了,下面的基本都直接拋棄了,因?yàn)?.5秒內(nèi),令牌桶(1秒1個(gè))來不及生產(chǎn)就肯定獲取不到返回false了。

      2.8:異步下單

      為了提升下單的效率,并且防止下單服務(wù)的失敗。需要將下單這一操作進(jìn)行異步處理。最常采用的辦法是使用隊(duì)列,隊(duì)列最顯著的三個(gè)優(yōu)點(diǎn):異步、削峰、解耦
      。這里可以采用rabbitmq,在后臺經(jīng)過了限流、庫存校驗(yàn)之后,流入到這一步驟的就是有效請求。然后發(fā)送到隊(duì)列里,隊(duì)列接受消息,異步下單。下完單,入庫沒有問題可以用短信通知用戶秒殺成功。假如失敗的話,可以采用補(bǔ)償機(jī)制,重試。

      2.9:服務(wù)降級


      假如在秒殺過程中出現(xiàn)了某個(gè)服務(wù)器宕機(jī),或者服務(wù)不可用,應(yīng)該做好后備工作。之前的博客里有介紹通過Hystrix進(jìn)行服務(wù)熔斷和降級,可以開發(fā)一個(gè)備用服務(wù),假如服務(wù)器真的宕機(jī)了,直接給用戶一個(gè)友好的提示返回,而不是直接卡死,服務(wù)器錯(cuò)誤等生硬的反饋。

      三:總結(jié)

      秒殺流程圖:

      ?? ?
      ?這就是我設(shè)計(jì)出來的秒殺流程圖,當(dāng)然不同的秒殺體量針對的技術(shù)選型都不一樣,這個(gè)流程可以支撐起幾十萬的流量,如果是成千萬破億那就得重新設(shè)計(jì)了。比如數(shù)據(jù)庫的分庫分表、隊(duì)列改成用kafka、redis增加集群數(shù)量等手段。通過本次設(shè)計(jì)主要是要表明的是我們?nèi)绾螒?yīng)對高并發(fā)的處理,并開始嘗試解決它,在工作中多思考、多動手能提升我們的能力水平,加油!如果本篇博客有任何錯(cuò)誤,請麻煩指出來,不勝感激。

      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務(wù)器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點(diǎn)信息
      問題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          五月婷香蕉久色在线看 | 男女拍拍拍拍免费视频 | 在线亚洲自拍 | 做爱网站在线看入口免费 | 草草视频网 | 考逼视频在线观看 | 男操女网站 | 骚逼免费观看 | 黄色视频网站免费看 | 国产高清一区二区三区四区 |