【學習筆記】秒殺系統的實現

高併發系統如何作到高可用

原理

減而治之:cdn;nginx限流,異步隊列(高併發流量變成均攤流量)
分而治之:lvs+nginx負載均衡php

特徵

寫強一致性(不能超賣)
讀弱一致性(讀可能有庫存,可是不能下單)html

核心實現

讀服務實現
寫服務實現
排隊進度查詢實現
鏈路流量優化如何作(lvs層, sever層,減小流量涌入)node

流量漏斗

請求鏈路中,每層服務削峯,限流,自動降級,熔斷nginx

秒殺系統的實現【滴滴講師的一個實例】redis

Ab壓測接口能夠承受的最大qps值

-n訪問1000次, -c併發100個

image.png

image.png

Requests per second 是吞吐量,是以秒爲單位的,因此這個值能夠了;結尾qps。算法

限流

Tengine版本採用http_limit_req_module進行限制

具體鏈接請參考 http://tengine.taobao.org/doc...數據庫

和官方nginx相似,不過支持多個變量,而且支持多個limit_req_zone的設置。好比:數組

limit_req_zone $binary_remote_addr zone=one:3m rate=1r/s;
limit_req_zone $binary_remote_addr $uri zone=two:3m rate=1r/s;               # $uri:不帶客戶端請求參數
limit_req_zone $binary_remote_addr $request_uri zone=thre:3m rate=1r/s;      # $request_uri:帶客戶端請求參數

上面的第二個指令表示當相同的ip地址而且訪問相同的uri,會致使進入limit req的限制(每秒1個請求)。安全

Nginx官方版本限制IP的鏈接和併發分別有兩個模塊:

  • limit_req_zone 用來限制單位時間內的請求數,即速率限制,採用的漏桶算法 "leaky bucket"
  • limit_req_conn 用來限制同一時間鏈接數,即併發限制

其中limit_req_conn模塊能夠根據源IP限制單用戶併發訪問的鏈接數或鏈接到該服務的總併發鏈接數。 性能優化

兩種算法的最大區別:令牌桶算法能夠應對突發流量,漏銅算法不行。

image.png

image.png

能夠參考學習:https://www.cnblogs.com/bigli...

漏桶算法

咱們假設系統是一個漏桶,當請求到達時,就是往漏桶裏「加水」,而當請求被處理掉,就是水從漏桶的底部漏出。水漏出的速度是固定的,當「加水」太快,桶就會溢出,也就是「拒絕請求」。從而使得桶裏的水的體積不可能超出桶的容量。​主要目的是控制數據注入到網絡的速率,平滑網絡上的突發流量。漏桶算法提供了一種機制,經過它,突發流量能夠被整形以便爲網絡提供一個穩定的流量。

示例以下:

http {

limit_conn_log_level error;

limit_conn_status 503;

limit_conn_zone **$binary_remote_addr** zone=one:10m;

limit_conn_zone **$server_name** zone=perserver:10m;

limit_req_zone **$binary_remote_addr** zone=allips:100m   rate=10r/s;   //其中$binary_remote_addr有時須要根據本身已有的**log\_format變量**配置進行替換

server {

                …………………….

                limit_conn  one  100;                                              

                limit_conn perserver 1000;

                limit_req   zone=allips  burst=5  nodelay;

                ………………….

         }

}

參數解釋:

Zone=one或allips 表示設置了名爲「one」或「allips」的存儲區,大小爲10兆字節

rate=10r/s 的意思是容許1秒鐘不超過10個請求

burst=5 表示最大延遲請求數量不大於5。  若是太過多的請求被限制延遲是不須要的 ,這時須要使用nodelay參數,服務器會馬上返回503狀態碼。

limit_conn  one  100表示限制每一個客戶端IP的最大併發鏈接數100

limit_conn perserver 1000表示該服務提供的總鏈接數不得超過1000,超過請求的會被拒絕

示例以下:   

http {

    limit_req_zone $binary_remote_addr zone=one:100m   rate=10r/m;

    server {

              limit_req   zone=one  burst=1  nodelay;

    }

}

配置解釋

rate=10r/m 

容許1秒鐘不超過1個請求,最大延遲請求數量不大於5.

若是請求不須要被延遲,添加nodelay參數,服務器會馬上返回503狀態碼。若是沒有該字段會形成大量的tcp鏈接請求等待。

http{  
    limit_zone one  $binary_remote_addr  10m;  
    server  
    {  
    ......  
    limit_conn  one  1;  
    ......  
    }  
   }

這裏的 one 是聲明一個 limit_zone 的名字,$binary_remote_addr是替代 $remore_addr 的變量,10m 是會話狀態儲存的空間。

limit_conn one 1

限制客戶端併發鏈接數量爲1, allow only one connection per an IP address at a time(每次).

limit_req_zone的功能是經過漏桶原理來限制用戶的鏈接頻率,(這個模塊容許你去限制單個地址指定會話或特殊須要的請求數 ) 。

而 limit_zone 功能是限制一個客戶端的併發鏈接數。(這個模塊能夠限制單個地址的指定會話或者特殊狀況的併發鏈接數) 。

一個是限制併發鏈接一個是限制鏈接頻率,表面上彷佛看不出來有什麼區別,那就看看實際的效果吧~~~
在個人測試機上面加上這兩個參數下面是個人部分配置文件

http{  
    limit_zone one  $binary_remote_addr  10m;  
    #limit_req_zone  $binary_remote_addr  zone=req_one:10m rate=1r/s;  
    server  
    {  
    ......  
    limit_conn   one  1;  
    #limit_req   zone=req_one  burst=120;  
    ......  
    }  
}

配置解釋

limit_zone one  $binary_remote_addr  10m;

這裏的 one 是聲明一個 limit_zone 的名字,$binary_remote_addr是替代 $remore_addr 的變量,10m是會話狀態儲存的空間

limit_conn one 1

限制客戶端併發鏈接數量爲1

limit_zone兩種工做狀況
limit_reqzone=one burst=10;

默認狀況下是這樣配置的,這樣每一個請求就會有一個delay時間,

limit_req_zone$binary_remote_addr zone=one:100m rate=10r/m;

就是每分鐘有10個令牌供用戶使用,按照a的配置狀況,就會有一個delay,每一個請求時間就是60/10,那每一個請求時間就是6s。

limit_reqzone=one burst=10 nodelay;

添加nodelay配置,這樣就是根據你的網絡情況訪問,一分鐘訪問夠10次後,服務器直接返回503。

limit_req_zone$binary_remote_addr zone=one:100m rate=10r/m;

就是每分鐘有10個令牌供用戶使用,按照b的配置狀況,就會根據網絡狀況訪問url,若是一分鐘超過10個令牌,服務器返回503,等待下一個一分鐘領取訪問令牌。

rate=10r/m 

每一個地址每分鐘只能請求10次,也就是說根據漏桶原理burst=1 一共有1塊令牌,而且每分鐘只新增10塊令牌,
1塊令牌發完後多出來的那些請求就會返回503。

加上 nodelay以後超過 burst大小的請求就會直接返回503,若是沒有該字段會形成大量的tcp鏈接請求等待。

http{
    ...
    #定義一個名爲allips的limit_req_zone用來存儲session,大小是10M內存,
    #以$binary_remote_addr 爲key,限制平均每秒的請求爲20個,
    #1M能存儲16000個狀態,rete的值必須爲整數,
    #若是限制兩秒鐘一個請求,能夠設置成30r/m
    limit_req_zone $binary_remote_addr zone=allips:10m rate=20r/s;
    ...
    server{
        ...
        location {
            ...
            #限制每ip每秒不超過20個請求,漏桶數burst爲5
            #brust的意思就是,若是第1秒、2,3,4秒請求爲19個,
            #第5秒的請求爲25個是被容許的。
            #可是若是你第1秒就25個請求,第2秒超過20的請求返回503錯誤。
            #nodelay,若是不設置該選項,嚴格使用平均速率限制請求數,
            #第1秒25個請求時,5個請求放到第2秒執行,
            #設置nodelay,25個請求將在第1秒執行。
            limit_req zone=allips burst=5 nodelay;
            ...
        }
        ...
    }
    ...
}

cdn

image.png

image.png

負載均衡

image.png

ip hash的一個問題,某個學校若是總的 ip 指定到 了某臺機器,那麼這個機器的訪問量就會很大。

消息隊列

因爲在高併發環境下,因爲來不及同步處理,請求每每會發生堵塞,好比說,大量的insert,update之類的請求同時到達DB,直接致使無數的行鎖表鎖,甚至最後請求會堆積過多,從而觸發too many connections錯誤。經過使用消息隊列,咱們能夠異步處理請求,從而緩解系統的壓力。

流量預估

image.png

秒殺系統的特色難點

image.png

image.png

秒殺系統的策略

image.png
秒殺系統,和其餘服務隔離部署,因此也是一個單獨的服 務,秒殺服務。

image.png

實現需求

image.png

秒殺系統的實現-扣庫存方案

扣庫存的幾種方式

● 下單扣庫存

當買家下單後,在商品的總庫存中減去買家購買數量。下單減庫存是最簡單的減庫存方式,也是控制最精 確的一種,下單時直接經過數據庫的事務機制控制商品 庫存,這樣必定不會出現超賣的狀況。可是你要知道, 有些人下完單可能並不會付款。

● 付款扣庫存

即買家下單後,並不當即減庫存,而是等到 有用戶付款後才真正減庫存,不然庫存一直保留給其餘 買家。但由於付款時才減庫存,若是併發比較高,有可 能出現買家下單後付不了款的狀況,由於可能商品已經 被其餘人買走了。

● 預扣庫存

這種方式相對複雜一些,買家下單後,庫存爲 其保留必定的時間(如 10 分鐘),超過這個時間,庫存 將會自動釋放,釋放後其餘買家就能夠繼續購買。在買 家付款前,系統會校驗該訂單的庫存是否還有保留:如 果沒有保留,則再次嘗試預扣;若是庫存不足(也就是 預扣失敗)則不容許繼續付款;若是預扣成功,則完成 付款並實際地減去庫存。

以上減庫存的幾種方式存在的問題

因爲購物過程當中存在兩步或者多步的操做,所以在不一樣 的操做步驟中減庫存,就會存在一些可能被惡意買家利 用的漏洞,例如發生惡意下單的狀況。

假如咱們採用「下單減庫存」的方式,即用戶下單後就減去 庫存,正常狀況下,買家下單後付款 的機率會很高,因此不會有太大問題。可是有一種場景例 外,就是當賣家參加某個活動時,此 時活動的有效時間是商品的⻩金售賣時間,若是有競爭對手 經過惡意下單的方式將該賣家的商 品所有下單,讓這款商品的庫存減爲零,那麼這款商品就不 能正常售賣了。要知道,這些惡意 下單的人是不會真正付款的,這正是「下單減庫存」方式的 不足之處。

付款減庫存 既然「下單減庫存」可能致使惡意下單,從而影響賣家的商 品銷售,那麼有沒有辦法解決呢? 你可能會想,採用「付款減庫存」的方式是否是就能夠了? 的確能夠。可是,「付款減庫存」又 會致使另一個問題:庫存超賣。

既然「下單減庫存」和「付款減庫存」都有缺點,咱們可否 把二者相結合,將兩次操做進行先後關聯起來,下單時 先預扣,在規定時間內不付款再釋放庫存,即採用「預扣 庫存」這種方式呢?

這種方案確實能夠在必定程度上緩解上面的問題。可是否就 完全解決了呢?其實沒有!針對惡 意下單這種狀況,雖然把有效的付款時間設置爲 10 分鐘, 可是惡意買家徹底能夠在 10 分鐘 後再次下單,或者採用一次下單不少件的方式把庫存減完。 針對這種狀況,解決辦法仍是要結 合安全和反做弊的措施來制止。

如何解決下單扣庫存問題

給常常下單不付款的買家進行識別打標(能夠在被打標的買 家下單時不減庫存)、給某些類目 設置最大購買件數(例如,參加活動的商品一人最多隻能買 3 件),以及對重複下單不付款的
操做進行次數限制等。

服務器性能優化(極限壓榨cpu)

重點是:避免沒必要要的上下文切換。
進程,線程之間的切換,系統調用,cpu都須要耗費資源調度,這些是無效的動做。真正執行程序,纔是有效動做,

線程,協程的使用,能夠避免這二者。
因此能夠看出,Php 並非作扣庫存,比較比如較合適的一⻔語言。

爲了達到極致性能,php 能夠作的是,減小阻塞時 IO(磁盤 文件讀寫,遠程調用)。

image.png

redis,單機單服務能夠實現 10w qps。

image.png

扣庫存實現

Go 是單進程,php 是多進程。 代碼實現:

image.png
redis+lua 腳本,防止超賣。 商品信息和搶購進度的實現

image.png
商品標題圖片詳情,這種不常常改變的,相對靜態信息的, 能夠作靜態化 +cdn

價格,促銷活動,庫存,這些相對動態信息,能夠作熱點緩 存。

數組和 hash 表的數據結構區別,
最大區別:方式不一樣,數組經過 key 查找 value,hash 經過函數查找。

https://www.cnblogs.com/chenj...

減庫存成功的用戶,存到數組 A 裏面,依次存入。 數組是索引數組,key 是索引值,值是 uid。
hash 表,key 是 uid,value 是數組 A 的索引值。

每次消費數據,就是從頭至尾去消費,記錄起來最近消費的 索引值,爲 X,

獲得排隊進度,
數組和 hash 是在內存中存儲的,數組和 hash 表的查詢複雜度都是 O1,很是快。

image.png
image.png

扣庫存,放少部分流量,到統一扣庫存的地方去,進行操 做。
寫訂單,異步消費。

請求鏈路實現漏斗流量

image.png

舉例:12306的驗證碼,就是爲了削峯, 增長用戶操做難 度,下降請求次數。也能夠防機器人破解驗證。

對訪問庫存數,這裏注意限流,但不能限流過久,注意時間 間隔。容許用戶低頻次的讀取數據,

讀寫分離,一主多從,能夠有效大幅度提升讀的訪問能力

image.png
海量併發的處理本質: 分治 ; 有效壓榨 cpu。 單服務的性能提高,重點是壓榨 cpu 性能。減小 IO(網絡訪

問,磁盤讀寫),減小 cpu 上下文切換, Cpu 上下文切換是無效的運算。

單進程和單線程,保證不會切換上下文,可是遇到 IO 可能 會阻塞,異步

多核 cpu,單機部署多實例(多個單進程單線程),有效利 用多核 cpu。

相關文章
相關標籤/搜索