1. 程式人生 > >電商網站秒殺活動

電商網站秒殺活動

秒殺活動通常是這樣定義:活動方在有限的時間段內(通常是M分鐘到H小時不等的時間)給出指定數量O個P商品的大減價搶購名額。

這類秒殺活動一般都會出現如下情況↓↓

第一、在某一時間內QPS超過系統負載;

第二、架構不合理導致系統的其它與秒殺活動不相關的模組變得異常緩慢;

第三、少數使用者重複搶到名額;

第四、最終搶到的名額數量超過庫存數量;

第五、伺服器宕機後恢復遲緩導致大量使用者流入競爭對手的網站;

第六、機器人流量佔用了網站訪問導致真實使用者訪問遲緩。

解決方案都是人想出來的,只是時間問題罷了。

解決方案背景:LNMP技術棧

第一、六個問題:

1. 

舍即是得:既然指定時間內,秒殺活動的QPS達到峰值Peak1,那麼在秒殺活動併發測試的時候我們應該首先得到這個值得平均範圍,然後取其中的極小值(min),這樣就可以通過nginx的ngx_http_limit_req_module和ngx_http_limit_conn_module兩個模組來限制,nginx的配置如下:

http {
     #geot和map兩段用於處理限速白名單,map段對映名單到$limit,處於geo內的IP將被對映為空值,否則為其IP地址。
     #limit_conn_zone和limit_req_zone指令對於鍵為空值的將會被忽略,從而實現對於列出來的IP不做限制
     geo $whiteiplist  {
        default 1;
        127.0.0.1 0;
        121.199.16.249 0;
     }
     map $whiteiplist  $limit {
        1 $binary_remote_addr;
        0 "";
     }
     #limit_conn_zone定義每個IP的併發連線數量
     #設定一個快取區儲存不同key的狀態,大小10m。使用$limit來作為key,以此限制每個源IP的連結數
     limit_conn_zone $limit  zone=perip:10m;
     #limit_req_zone定義每個IP的每秒請求數量
     #設定一個快取區reqps儲存不同key的狀態,大小10m。這裡的狀態是指當前的過量請求數。
     #$limit為空值則不限速,否則對應的IP進行限制每秒5個連線請求。
     limit_req_zone  $limit  zone=reqps:10m rate=5r/s;
     server {
        listen     80;
        server_name   www.yoururl.com;
        #只對PHP的秒殺頁面的請求進行限速
        location ~ [^/]miaosha\.php(/|$)
        {
            #對應limit_conn_zone塊
            #限制每IP的PHP頁面請求併發數量為5個
            limit_conn perip 5; 
           
            #對應limit_req_zone塊
            #限制每IP的每秒的PHP頁面請求次數為上面定義的rate的值:每秒5個請求,不延遲      
            limit_req zone=reqps nodelay;
        }
     }
}

上面的這段nginx配置其實是對單個IP進行限制,效果是有的,但不夠明顯。

2.過濾無效請求:

前端生成簽名字串,例如通過crypto.js,對當前unix時間戳time,產生隨機字串nonce,還有一個key必須是使用者填寫好驗證碼後主機返回給瀏覽器客戶端一個token名稱的cookie欄位值(有一個過期時間)結合混淆演算法生成的,最後然後經過自定義的簽名演算法在前端生成簽名字串signature,最後在傳送搶購表單時帶上以上4個欄位資訊,當請求到達nginx之後,我們使用nginx的lua模組編寫lua指令碼驗證signature的正確性,並且限定以上token的過期時間為30秒,且客戶端返回過來的time引數必須跟伺服器的unix時間戳相差不超過5秒鐘,否則直接在nginx的lua層面上直接遮蔽掉該請求,這裡面就不得不說

Openresty技術了,感興趣的小夥伴可以去深入研究一下。

3.概率性丟棄超負載的請求:

既然我們已經在前期併發測試的時候獲得了一個峰值引數PeakMin,我們應該儘量保證所有的有效秒殺請求不大於這個值,首先我們得獲得當前nginx的總連線數CurrentConnectionCount,當QPS達到PeakMin的時候,我們測算出來的連線數是PeakMinConnectionCount,那麼我們使用nginx的lua模組獲取這個值,在系統負載達到0.8*PeakMinConnectionCount的時候,我們就對超出的部分90%的丟棄率,返回一個未能秒殺中的提示,並把使用者對此次活動的秒殺結果寫入memcached快取進行記錄,當系統負載達到PeakMinConnectionCount時,我們直接100%丟棄請求,前端根據狀態碼是5XX來給出使用者未能秒殺中的訊息提示,當然我想說的是這裡必須保證使用者的體驗是正常的。

第二個問題:

1. 

分功能模組設計系統:

2. 

一個成熟的電商系統,一般會分成很多相對獨立的模組,比如產品中心,會員中心,訂單中心,物流中心,配置中心,搜尋中心等大模組,這些大模組之間的庫表資料通常是低耦合的,因此還可以把這些大模組分割成很多子功能模組,這樣就可以讓整個電商系統的模組彼此的影響最大化縮小,其中的分散式服務端架構包含了很多架構實踐,在這裡就不細講了。

3. 

第三個問題:

    1.快取key原子驗證:

同一個使用者重複搶到名額這個問題比較簡單,最有可能是使用者(機器人程式)在非常短的時間內(假設是0.01秒)提交了2次以上的併發請求,以ProductId+ActivityId+UserId命名的key寫入使用者成功秒殺的記錄值,利用memcached的add原子性來寫入資訊,如果add出錯則證明已經add過一次,那就返回。

第四個問題:

    1.樂觀鎖:

      memcached有一個很不錯的CAS檢查機制,就是二話不說我先搶到一個一個名額,到真的要儲存的資料的時候我再看看CAS值是否跟一開始的時候一樣,如果不一樣就不操作返回沒有秒殺到的訊息提示,否則就減掉一個有效秒殺名額,直到儲存秒殺庫存的key為0即止。

第五個問題:

     1.冷熱多備份:

    不管是應用伺服器,快取伺服器,資料庫伺服器,訊息佇列伺服器等,都應該有自己的多備份,尤其是資料庫伺服器與快取伺服器更是直接影響了系統資料層面的東西,有條件的還需要做好異地多活,多資料中心等架構設施。

    2.自動化運維:

多使用批量管理與配置工具,例如ansibledocker等技術,這裡麵包含的學問比較多,本人對這一塊的技術實踐也掌握不夠,需要不斷磨練啊。