電商網站秒殺活動
秒殺活動通常是這樣定義:活動方在有限的時間段內(通常是M分鐘到H小時不等的時間)給出指定數量O個P商品的大減價搶購名額。
這類秒殺活動一般都會出現如下情況↓↓
第一、在某一時間內QPS超過系統負載(注釋:QPS,是指每秒查詢率);
第二、架構不合理導致系統的其它與秒殺活動不相關的模組變得異常緩慢;
第三、少數使用者重複搶到名額;
第四、最終搶到的名額數量超過庫存數量;
第五、伺服器宕機後恢複遲緩導致大量使用者流入競爭者的網站;
第六、機器人流量佔用了網站訪問導致真實使用者訪問遲緩。
解決方案都是人想出來的,只是時間問題罷了。解決方案背景:LNMP技術棧(六個問題)
第一問題:
1. 設定nginx的配置:
舍即是得:既然指定時間內,秒殺活動的QPS達到峰值Peak1,那麼在秒殺活動並發測試的時候我們應該首先得到這個值得平均範圍,然後取其中的極小值(min),這樣就可以通過nginx的ngx_http_limit_req_module和ngx_http_limit_conn_module兩個模組來限制,nginx的配置如下:
(PS:說到了ngx_http_limit_conn_module 模組,來限制並發串連數。那麼請求數的限制該怎麼做呢。這就需要通過ngx_http_limit_req_module 模組來實現,該模組可以通過定義的 索引值來限制請求處理的頻率。特別的,可以限制來自單個IP地址的請求處理頻率。 限制的方法如同漏鬥,每秒固定處理請求數,延遲過多請求。以此來防止應用程式層的DDOS攻擊 。)
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 http://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來給出使用者未能秒殺中的訊息提示,當然我想說的是這裡必須保證使用者的體驗是正常的。 第二個問題:
分功能模組設計系統:
一個成熟的電商系統,一般會分成很多相對獨立的模組,比如產品中心,測試人員中樞,訂單中心,物流中心,配置中心,搜尋中心等大模組,這些大模組之間的庫表資料通常是低耦合的,因此還可以把這些大模組分割成很多子功能模組,這樣就可以讓整個電商系統的模組彼此的影響最大化縮小,其中的分布式服務端架構包含了很多架構實踐,在這裡就不細講了。 第三個問題:
緩衝key原子驗證:
同一個使用者重複搶到名額這個問題比較簡單,最有可能是使用者(機器人程式)在非常短的時間內(假設是0.01秒)提交了2次以上的並發請求,以ProductId+ActivityId+UserId命名的key寫入使用者成功秒殺的記錄值,利用memcached的add原子性來寫入資訊,如果add出錯則證明已經add過一次,那就返回。 第四個問題:
樂觀鎖:
memcached有一個很不錯的CAS檢查機制,就是二話不說我先搶到一個一個名額,到真的要儲存的資料的時候我再看看CAS值是否跟一開始的時候一樣,如果不一樣就不操作返回沒有秒殺到的訊息提示,否則就減掉一個有效秒殺名額,直到儲存秒殺庫存的key為0即止。 第五個問題:
1.冷熱多備份:
不管是應用伺服器,快取服務器,資料庫伺服器,訊息佇列伺服器等,都應該有自己的多備份,尤其是資料庫伺服器與快取服務器更是直接影響了系統資料層面的東西,有條件的還需要做好異地多活,多資料中心等架構設施。
2.自動化營運:
多使用批量管理與組態工具,例如ansible,docker等技術,這裡麵包含的學問比較多,本人對這一塊的技術實踐也掌握不夠,需要不斷磨練啊。
【原文:https://zhuanlan.zhihu.com/p/25466488?group_id=819684972054077440】