博客 / 詳情

返回

解密秒殺系統架構,不是所有的系統都能做秒殺!

摘要:教你如何設計一個秒殺系統架構:從電商系統架構到秒殺系統、從高併發“黑科技”與致勝奇招到服務器硬件優化,全方位立體掌握秒殺系統架構!!

本文分享自華為雲社區《實踐出真知:全網最強秒殺系統架構解密,不是所有的秒殺都是秒殺!!》,作者: 冰 河。

電商系統架構

在電商領域,存在着典型的秒殺業務場景,那何謂秒殺場景呢。簡單的來説就是一件商品的購買人數遠遠大於這件商品的庫存,而且這件商品在很短的時間內就會被搶購一空。 比如每年的618、雙11大促,小米新品促銷等業務場景,就是典型的秒殺業務場景。

我們可以將電商系統的架構簡化成下圖所示。
image.png

由圖所示,我們可以簡單的將電商系統的核心層分為:負載均衡層、應用層和持久層。接下來,我們就預估下每一層的併發量。

• 假如負載均衡層使用的是高性能的Nginx,則我們可以預估Nginx最大的併發度為:10W+,這裏是以萬為單位。
• 假設應用層我們使用的是Tomcat,而Tomcat的最大併發度可以預估為800左右,這裏是以百為單位。
• 假設持久層的緩存使用的是Redis,數據庫使用的是MySQL,MySQL的最大併發度可以預估為1000左右,以千為單位。Redis的最大併發度可以預估為5W左右,以萬為單位。

所以,負載均衡層、應用層和持久層各自的併發度是不同的,那麼,為了提升系統的總體併發度和緩存,我們通常可以採取哪些方案呢?

(1)系統擴容

系統擴容包括垂直擴容和水平擴容,增加設備和機器配置,絕大多數的場景有效。

(2)緩存

本地緩存或者集中式緩存,減少網絡IO,基於內存讀取數據。大部分場景有效。

(3)讀寫分離

採用讀寫分離,分而治之,增加機器的並行處理能力。

秒殺系統的特點

對於秒殺系統來説,我們可以從業務和技術兩個角度來闡述其自身存在的一些特點。

秒殺系統的業務特點

這裏,我們可以使用12306網站來舉例,每年春運時,12306網站的訪問量是非常大的,但是網站平時的訪問量卻是比較平緩的,也就是説,每年春運時節,12306網站的訪問量會出現瞬時突增的現象。

再比如,小米秒殺系統,在上午10點開售商品,10點前的訪問量比較平緩,10點時同樣會出現併發量瞬時突增的現象。

所以,秒殺系統的流量和併發量我們可以使用下圖來表示。
image.png

由圖可以看出,秒殺系統的併發量存在瞬時凸峯的特點,也叫做流量突刺現象。

我們可以將秒殺系統的特點總結如下。
image.png

(1)限時、限量、限價

在規定的時間內進行;秒殺活動中商品的數量有限;商品的價格會遠遠低於原來的價格,也就是説,在秒殺活動中,商品會以遠遠低於原來的價格出售。

例如,秒殺活動的時間僅限於某天上午10點到10點半,商品數量只有10萬件,售完為止,而且商品的價格非常低,例如:1元購等業務場景。

限時、限量和限價可以單獨存在,也可以組合存在。

(2)活動預熱

需要提前配置活動;活動還未開始時,用户可以查看活動的相關信息;秒殺活動開始前,對活動進行大力宣傳。

(3)持續時間短

購買的人數數量龐大;商品會迅速售完。

在系統流量呈現上,就會出現一個突刺現象,此時的併發訪問量是非常高的,大部分秒殺場景下,商品會在極短的時間內售完。

秒殺系統的技術特點

我們可以將秒殺系統的技術特點總結如下。
image.png

(1)瞬時併發量非常高

大量用户會在同一時間搶購商品;瞬間併發峯值非常高。

(2)讀多寫少

系統中商品頁的訪問量巨大;商品的可購買數量非常少;庫存的查詢訪問數量遠遠大於商品的購買數量。

在商品頁中往往會加入一些限流措施,例如早期的秒殺系統商品頁會加入驗證碼來平滑前端對系統的訪問流量,近期的秒殺系統商品詳情頁會在用户打開頁面時,提示用户登錄系統。這都是對系統的訪問進行限流的一些措施。

(3)流程簡單

秒殺系統的業務流程一般比較簡單;總體上來説,秒殺系統的業務流程可以概括為:下單減庫存。

秒殺三階段

通常,從秒殺開始到結束,往往會經歷三個階段:

準備階段:這個階段也叫作系統預熱階段,此時會提前預熱秒殺系統的業務數據,往往這個時候,用户會不斷刷新秒殺頁面,來查看秒殺活動是否已經開始。在一定程度上,通過用户不斷刷新頁面的操作,可以將一些數據存儲到Redis中進行預熱。
秒殺階段:這個階段主要是秒殺活動的過程,會產生瞬時的高併發流量,對系統資源會造成巨大的衝擊,所以,在秒殺階段一定要做好系統防護。
結算階段: 完成秒殺後的數據處理工作,比如數據的一致性問題處理,異常情況處理,商品的回倉處理等。

針對這種短時間內大流量的系統來説,就不太適合使用系統擴容了,因為即使系統擴容了,也就是在很短的時間內會使用到擴容後的系統,大部分時間內,系統無需擴容即可正常訪問。 那麼,我們可以採取哪些方案來提升系統的秒殺性能呢?

秒殺系統方案

針對秒殺系統的特點,我們可以採取如下的措施來提升系統的性能。
image.png

(1)異步解耦

將整體流程進行拆解,核心流程通過隊列方式進行控制。

(2)限流防刷

控制網站整體流量,提高請求的門檻,避免系統資源耗盡。

(3)資源控制

將整體流程中的資源調度進行控制,揚長避短。

由於應用層能夠承載的併發量比緩存的併發量少很多。所以,在高併發系統中,我們可以直接使用OpenResty由負載均衡層訪問緩存,避免了調用應用層的性能損耗。大家可以到https://openresty.org/cn/來了解有關OpenResty更多的知識。同時,由於秒殺系統中,商品數量比較少,我們也可以使用動態渲染技術,CDN技術來加速網站的訪問性能。

如果在秒殺活動開始時,併發量太高時,我們可以將用户的請求放入隊列中進行處理,併為用户彈出排隊頁面。
image.png

注:圖片來自魅族

秒殺系統時序圖

網上很多的秒殺系統和對秒殺系統的解決方案,並不是真正的秒殺系統,他們採用的只是同步處理請求的方案,一旦併發量真的上來了,他們所謂的秒殺系統的性能會急劇下降。我們先來看一下秒殺系統在同步下單時的時序圖。

同步下單流程

image.png

1.用户發起秒殺請求

在同步下單流程中,首先,用户發起秒殺請求。商城服務需要依次執行如下流程來處理秒殺請求的業務。

(1)識別驗證碼是否正確

商城服務判斷用户發起秒殺請求時提交的驗證碼是否正確。

(2)判斷活動是否已經結束

驗證當前秒殺活動是否已經結束。

(3)驗證訪問請求是否處於黑名單

在電商領域中,存在着很多的惡意競爭,也就是説,其他商家可能會通過不正當手段來惡意請求秒殺系統,佔用系統大量的帶寬和其他系統資源。此時,就需要使用風控系統等實現黑名單機制。為了簡單,也可以使用攔截器統計訪問頻次實現黑名單機制。

(4)驗證真實庫存是否足夠

系統需要驗證商品的真實庫存是否足夠,是否能夠支持本次秒殺活動的商品庫存量。

(5)扣減緩存中的庫存

在秒殺業務中,往往會將商品庫存等信息存放在緩存中,此時,還需要驗證秒殺活動使用的商品庫存是否足夠,並且需要扣減秒殺活動的商品庫存數量。

(6)計算秒殺的價格

由於在秒殺活動中,商品的秒殺價格和商品的真實價格存在差異,所以,需要計算商品的秒殺價格。

注意:如果在秒殺場景中,系統涉及的業務更加複雜的話,會涉及更多的業務操作,這裏,我只是列舉出一些常見的業務操作。

2.提交訂單

(1)訂單入口

將用户提交的訂單信息保存到數據庫中。

(2)扣減真實庫存

訂單入庫後,需要在商品的真實庫存中將本次成功下單的商品數量扣除。

如果我們使用上述流程開發了一個秒殺系統,當用户發起秒殺請求時,由於系統每個業務流程都是串行執行的,整體上系統的性能不會太高,當併發量太高時,我們會為用户彈出下面的排隊頁面,來提示用户進行等待。
image.png

注:圖片來自魅族

此時的排隊時間可能是15秒,也可能是30秒,甚至是更長時間。這就存在一個問題:在用户發起秒殺請求到服務器返回結果的這段時間內,客户端和服務器之間的連接不會被釋放,這就會佔大量佔用服務器的資源。

網上很多介紹如何實現秒殺系統的文章都是採用的這種方式,那麼,這種方式能做秒殺系統嗎?答案是可以做,但是這種方式支撐的併發量並不是太高。此時,有些網友可能會問:我們公司就是這樣做的秒殺系統啊!上線後一直在用,沒啥問題啊!我想説的是:使用同步下單方式確實可以做秒殺系統,但是同步下單的性能不會太高。之所以你們公司採用同步下單的方式做秒殺系統沒出現大的問題,那是因為你們的秒殺系統的併發量沒達到一定的量級,也就是説,你們的秒殺系統的併發量其實並不高。

所以,很多所謂的秒殺系統,存在着秒殺的業務,但是稱不上真正的秒殺系統,原因就在於他們使用的是同步的下單流程,限制了系統的併發流量。之所以上線後沒出現太大的問題,是因為系統的併發量不高,不足以壓死整個系統。

如果12306、淘寶、天貓、京東、小米等大型商城的秒殺系統是這麼玩的話,那麼,他們的系統遲早會被玩死,他們的系統工程師不被開除才怪!所以,在秒殺系統中,這種同步處理下單的業務流程的方案是不可取的。

以上就是同步下單的整個流程操作,如果下單流程更加複雜的話,就會涉及到更多的業務操作。

異步下單流程

既然同步下單流程的秒殺系統稱不上真正的秒殺系統,那我們就需要採用異步的下單流程了。異步的下單流程不會限制系統的高併發流量。
image.png

1.用户發起秒殺請求

用户發起秒殺請求後,商城服務會經過如下業務流程。

(1)檢測驗證碼是否正確

用户發起秒殺請求時,會將驗證碼一同發送過來,系統會檢驗驗證碼是否有效,並且是否正確。

(2)是否限流

系統會對用户的請求進行是否限流的判斷,這裏,我們可以通過判斷消息隊列的長度來進行判斷。因為我們將用户的請求放在了消息隊列中,消息隊列中堆積的是用户的請求,我們可以根據當前消息隊列中存在的待處理的請求數量來判斷是否需要對用户的請求進行限流處理。

例如,在秒殺活動中,我們出售1000件商品,此時在消息隊列中存在1000個請求,如果後續仍然有用户發起秒殺請求,則後續的請求我們可以不再處理,直接向用户返回商品已售完的提示。
image.png

所以,使用限流後,我們可以更快的處理用户的請求和釋放連接的資源。

(3)發送MQ

用户的秒殺請求通過前面的驗證後,我們就可以將用户的請求參數等信息發送到MQ中進行異步處理,同時,向用户響應結果信息。在商城服務中,會有專門的異步任務處理模塊來消費消息隊列中的請求,並處理後續的異步流程。

在用户發起秒殺請求時,異步下單流程比同步下單流程處理的業務操作更少,它將後續的操作通過MQ發送給異步處理模塊進行處理,並迅速向用户返回響應結果,釋放請求連接。

2.異步處理

我們可以將下單流程的如下操作進行異步處理。

(1)判斷活動是否已經結束

(2)判斷本次請求是否處於系統黑名單,為了防止電商領域同行的惡意競爭可以為系統增加黑名單機制,將惡意的請求放入系統的黑名單中。可以使用攔截器統計訪問頻次來實現。

(3)扣減緩存中的秒殺商品的庫存數量。

(4)生成秒殺Token,這個Token是綁定當前用户和當前秒殺活動的,只有生成了秒殺Token的請求才有資格進行秒殺活動。

這裏我們引入了異步處理機制,在異步處理中,系統使用多少資源,分配多少線程來處理相應的任務,是可以進行控制的。

3.短輪詢查詢秒殺結果

這裏,可以採取客户端短輪詢查詢是否獲得秒殺資格的方案。例如,客户端可以每隔3秒鐘輪詢請求服務器,查詢是否獲得秒殺資格,這裏,我們在服務器的處理就是判斷當前用户是否存在秒殺Token,如果服務器為當前用户生成了秒殺Token,則當前用户存在秒殺資格。否則繼續輪詢查詢,直到超時或者服務器返回商品已售完或者無秒殺資格等信息為止。

採用短輪詢查詢秒殺結果時,在頁面上我們同樣可以提示用户排隊處理中,但是此時客户端會每隔幾秒輪詢服務器查詢秒殺資格的狀態,相比於同步下單流程來説,無需長時間佔用請求連接。

此時,可能會有網友會問:採用短輪詢查詢的方式,會不會存在直到超時也查詢不到是否具有秒殺資格的狀態呢?答案是:有可能! 這裏我們試想一下秒殺的真實場景,商家參加秒殺活動本質上不是為了賺錢,而是提升商品的銷量和商家的知名度,吸引更多的用户來買自己的商品。所以,我們不必保證用户能夠100%的查詢到是否具有秒殺資格的狀態。

4.秒殺結算

(1)驗證下單Token

客户端提交秒殺結算時,會將秒殺Token一同提交到服務器,商城服務會驗證當前的秒殺Token是否有效。

(2)加入秒殺購物車

商城服務在驗證秒殺Token合法並有效後,會將用户秒殺的商品添加到秒殺購物車。

5.提交訂單

(1)訂單入庫

將用户提交的訂單信息保存到數據庫中。

(2)刪除Token

秒殺商品訂單入庫成功後,刪除秒殺Token。

這裏大家可以思考一個問題:我們為什麼只在異步下單流程的粉色部分採用異步處理,而沒有在其他部分採取異步削峯和填谷的措施呢?

這是因為在異步下單流程的設計中,無論是在產品設計上還是在接口設計上,我們在用户發起秒殺請求階段對用户的請求進行了限流操作,可以説,系統的限流操作是非常前置的。在用户發起秒殺請求時進行了限流,系統的高峯流量已經被平滑解決了,再往後走,其實系統的併發量和系統流量並不是非常高了。

所以,網上很多的文章和帖子中在介紹秒殺系統時,説是在下單時使用異步削峯來進行一些限流操作,那都是在扯淡! 因為下單操作在整個秒殺系統的流程中屬於比較靠後的操作了,限流操作一定要前置處理,在秒殺業務後面的流程中做限流操作是沒啥卵用的。

高併發“黑科技”與致勝奇招

假設,在秒殺系統中我們使用Redis實現緩存,假設Redis的讀寫併發量在5萬左右。我們的商城秒殺業務需要支持的併發量在100萬左右。如果這100萬的併發全部打入Redis中,Redis很可能就會掛掉,那麼,我們如何解決這個問題呢?接下來,我們就一起來探討這個問題。

在高併發的秒殺系統中,如果採用Redis緩存數據,則Redis緩存的併發處理能力是關鍵,因為很多的前綴操作都需要訪問Redis。而異步削峯只是基本的操作,關鍵還是要保證Redis的併發處理能力。

解決這個問題的關鍵思想就是:分而治之,將商品庫存分開放。

暗度陳倉

我們在Redis中存儲秒殺商品的庫存數量時,可以將秒殺商品的庫存進行“分割”存儲來提升Redis的讀寫併發量。

例如,原來的秒殺商品的id為10001,庫存為1000件,在Redis中的存儲為(10001, 1000),我們將原有的庫存分割為5份,則每份的庫存為200件,此時,我們在Redia中存儲的信息為(10001_0, 200),(10001_1, 200),(10001_2, 200),(10001_3, 200),(10001_4, 200)。
image.png

此時,我們將庫存進行分割後,每個分割後的庫存使用商品id加上一個數字標識來存儲,這樣,在對存儲商品庫存的每個Key進行Hash運算時,得出的Hash結果是不同的,這就説明,存儲商品庫存的Key有很大概率不在Redis的同一個槽位中,這就能夠提升Redis處理請求的性能和併發量。

分割庫存後,我們還需要在Redis中存儲一份商品id和分割庫存後的Key的映射關係,此時映射關係的Key為商品的id,也就是10001,Value為分割庫存後存儲庫存信息的Key,也就是10001_0,10001_1,10001_2,10001_3,10001_4。在Redis中我們可以使用List來存儲這些值。

在真正處理庫存信息時,我們可以先從Redis中查詢出秒殺商品對應的分割庫存後的所有Key,同時使用AtomicLong來記錄當前的請求數量,使用請求數量對從Redia中查詢出的秒殺商品對應的分割庫存後的所有Key的長度進行求模運算,得出的結果為0,1,2,3,4。再在前面拼接上商品id就可以得出真正的庫存緩存的Key。此時,就可以根據這個Key直接到Redis中獲取相應的庫存信息。

移花接木

在高併發業務場景中,我們可以直接使用Lua腳本庫(OpenResty)從負載均衡層直接訪問緩存。

這裏,我們思考一個場景:如果在秒殺業務場景中,秒殺的商品被瞬間搶購一空。此時,用户再發起秒殺請求時,如果系統由負載均衡層請求應用層的各個服務,再由應用層的各個服務訪問緩存和數據庫,其實,本質上已經沒有任何意義了,因為商品已經賣完了,再通過系統的應用層進行層層校驗已經沒有太多意義了!!而應用層的併發訪問量是以百為單位的,這又在一定程度上會降低系統的併發度。

為了解決這個問題,此時,我們可以在系統的負載均衡層取出用户發送請求時攜帶的用户id,商品id和秒殺活動id等信息,直接通過Lua腳本等技術來訪問緩存中的庫存信息。如果秒殺商品的庫存小於或者等於0,則直接返回用户商品已售完的提示信息,而不用再經過應用層的層層校驗了。 針對這個架構,我們可以參見本文中的電商系統的架構圖(正文開始的第一張圖)。

Redis助力秒殺系統

我們可以在Redis中設計一個Hash數據結構,來支持商品庫存的扣減操作,如下所示。

seckill:goodsStock:${goodsId}{
    totalCount:200,
    initStatus:0,
    seckillCount:0
}

在我們設計的Hash數據結構中,有三個非常主要的屬性。

  • totalCount:表示參與秒殺的商品的總數量,在秒殺活動開始前,我們就需要提前將此值加載到Redis緩存中。
  • initStatus:我們把這個值設計成一個布爾值。秒殺開始前,這個值為0,表示秒殺未開始。可以通過定時任務或者後台操作,將此值修改為1,則表示秒殺開始。
  • seckillCount:表示秒殺的商品數量,在秒殺過程中,此值的上限為totalCount,當此值達到totalCount時,表示商品已經秒殺完畢。

我們可以通過下面的代碼片段在秒殺預熱階段,將要參與秒殺的商品數據加載的緩存。

/**
 * @author binghe
 * @description 秒殺前構建商品緩存代碼示例
 */
public class SeckillCacheBuilder{
    private static final String GOODS_CACHE = "seckill:goodsStock:"; 
    private String getCacheKey(String id) { 
        return  GOODS_CACHE.concat(id);
    } 
    public void prepare(String id, int totalCount) { 
        String key = getCacheKey(id); 
        Map<String, Integer> goods = new HashMap<>(); 
        goods.put("totalCount", totalCount); 
        goods.put("initStatus", 0); 
        goods.put("seckillCount", 0); 
        redisTemplate.opsForHash().putAll(key, goods); 
     }
}

秒殺開始的時候,我們需要在代碼中首先判斷緩存中的seckillCount值是否小於totalCount值,如果seckillCount值確實小於totalCount值,我們才能夠對庫存進行鎖定。在我們的程序中,這兩步其實並不是原子性的。如果在分佈式環境中,我們通過多台機器同時操作Redis緩存,就會發生同步問題,進而引起“超賣”的嚴重後果。

在電商領域,有一個專業名詞叫作“超賣”。顧名思義:“超賣”就是説賣出的商品數量比商品的庫存數量多,這在電商領域是一個非常嚴重的問題。那麼,我們如何解決“超賣”問題呢?

Lua腳本完美解決超賣問題

我們如何解決多台機器同時操作Redis出現的同步問題呢?一個比較好的方案就是使用Lua腳本。我們可以使用Lua腳本將Redis中扣減庫存的操作封裝成一個原子操作,這樣就能夠保證操作的原子性,從而解決高併發環境下的同步問題。

例如,我們可以編寫如下的Lua腳本代碼,來執行Redis中的庫存扣減操作。

local resultFlag = "0" 
local n = tonumber(ARGV[1]) 
local key = KEYS[1] 
local goodsInfo = redis.call("HMGET",key,"totalCount","seckillCount") 
local total = tonumber(goodsInfo[1]) 
local alloc = tonumber(goodsInfo[2]) 
if not total then 
    return resultFlag 
end 
if total >= alloc + n  then 
    local ret = redis.call("HINCRBY",key,"seckillCount",n) 
    return tostring(ret) 
end 
return resultFlag

我們可以使用如下的Java代碼來調用上述Lua腳本。

public int secKill(String id, int number) { 
    String key = getCacheKey(id); 
    Object seckillCount =  redisTemplate.execute(script, Arrays.asList(key), String.valueOf(number)); 
    return Integer.valueOf(seckillCount.toString()); 
}

這樣,我們在執行秒殺活動時,就能夠保證操作的原子性,從而有效的避免數據的同步問題,進而有效的解決了“超賣”問題。

為了應對秒殺系統高併發大流量的業務場景,除了秒殺系統本身的業務架構外,我們還要進一步優化服務器硬件的性能,接下來,我們就一起來看一下如何優化服務器的性能。

優化服務器性能

操作系統

這裏,我使用的操作系統為CentOS 8,我們可以輸入如下命令來查看操作系統的版本。

CentOS Linux release 8.0.1905 (Core)
對於高併發的場景,我們主要還是優化操作系統的網絡性能,而操作系統中,有很多關於網絡協議的參數,我們對於服務器網絡性能的優化,主要是對這些系統參數進行調優,以達到提升我們應用訪問性能的目的。

系統參數

在CentOS 操作系統中,我們可以通過如下命令來查看所有的系統參數。

/sbin/sysctl -a
部分輸出結果如下所示。
image.png

這裏的參數太多了,大概有一千多個,在高併發場景下,我們不可能對操作系統的所有參數進行調優。我們更多的是關注與網絡相關的參數。如果想獲得與網絡相關的參數,那麼,我們首先需要獲取操作系統參數的類型,如下命令可以獲取操作系統參數的類型。

/sbin/sysctl -a|awk -F "." '{print $1}'|sort -k1|uniq

運行命令輸出的結果信息如下所示。

abi
crypto
debug
dev
fs
kernel
net
sunrpc
user
vm

image.png
其中的net類型就是我們要關注的與網絡相關的操作系統參數。我們可以獲取net類型下的子類型,如下所示。

/sbin/sysctl -a|grep "^net."|awk -F "[.| ]" '{print $2}'|sort -k1|uniq

輸出的結果信息如下所示。

bridge
core
ipv4
ipv6
netfilter
nf_conntrack_max
unix

image.png
在Linux操作系統中,這些與網絡相關的參數都可以在/etc/sysctl.conf 文件裏修改,如果/etc/sysctl.conf 文件中不存在這些參數,我們可以自行在/etc/sysctl.conf 文件中添加這些參數。

在net類型的子類型中,我們需要重點關注的子類型有:core和ipv4。

優化套接字緩衝區

如果服務器的網絡套接字緩衝區太小,就會導致應用程序讀寫多次才能將數據處理完,這會大大影響我們程序的性能。如果網絡套接字緩衝區設置的足夠大,從一定程度上能夠提升我們程序的性能。

我們可以在服務器的命令行輸入如下命令,來獲取有關服務器套接字緩衝區的信息。

/sbin/sysctl -a|grep "^net."|grep "[r|w|_]mem[_| ]"

輸出的結果信息如下所示。

net.core.rmem_default = 212992
net.core.rmem_max = 212992
net.core.wmem_default = 212992
net.core.wmem_max = 212992
net.ipv4.tcp_mem = 43545        58062   87090
net.ipv4.tcp_rmem = 4096        87380   6291456
net.ipv4.tcp_wmem = 4096        16384   4194304
net.ipv4.udp_mem = 87093        116125  174186
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096

image.png
其中,帶有max、default、min關鍵字的為分別代表:最大值、默認值和最小值;帶有mem、rmem、wmem關鍵字的分別為:總內存、接收緩衝區內存、發送緩衝區內存。

這裏需要注意的是:帶有rmem 和 wmem關鍵字的單位都是“字節”,而帶有mem關鍵字的單位是“頁”。“頁”是操作系統管理內存的最小單位,在 Linux 系統裏,默認一頁是 4KB 大小。

如何優化頻繁收發大文件

如果在高併發場景下,需要頻繁的收發大文件,我們該如何優化服務器的性能呢?

這裏,我們可以修改的系統參數如下所示。

net.core.rmem_default
net.core.rmem_max
net.core.wmem_default
net.core.wmem_max
net.ipv4.tcp_mem
net.ipv4.tcp_rmem
net.ipv4.tcp_wmem

這裏,我們做個假設,假設系統最大可以給TCP分配 2GB 內存,最小值為 256MB,壓力值為 1.5GB。按照一頁為 4KB 來計算, tcp_mem 的最小值、壓力值、最大值分別是 65536、393216、524288,單位是“頁” 。

假如平均每個文件數據包為 512KB,每個套接字讀寫緩衝區最小可以各容納 2 個數據包,默認可以各容納 4 個數據包,最大可以各容納 10 個數據包,那我們可以算出 tcp_rmem 和 tcp_wmem 的最小值、默認值、最大值分別是 1048576、2097152、5242880,單位是“字節”。而 rmem_default 和 wmem_default 是 2097152,rmem_max 和 wmem_max 是 5242880。

注:後面詳細介紹這些數值是如何計算的~~

這裏,還需要注意的是:緩衝區超過了 65535,還需要將 net.ipv4.tcp_window_scaling 參數設置為 1。

經過上面的分析後,我們最終得出的系統調優參數如下所示。

net.core.rmem_default = 2097152
net.core.rmem_max = 5242880
net.core.wmem_default = 2097152
net.core.wmem_max = 5242880
net.ipv4.tcp_mem = 65536  393216  524288
net.ipv4.tcp_rmem = 1048576  2097152  5242880
net.ipv4.tcp_wmem = 1048576  2097152  5242880

優化TCP連接

對計算機網絡有一定了解的小夥伴都知道,TCP的連接需要經過“三次握手”和“四次揮手”的,還要經過慢啓動、滑動窗口、粘包算法等支持可靠性傳輸的一系列技術支持。雖然,這些能夠保證TCP協議的可靠性,但有時這會影響我們程序的性能。

那麼,在高併發場景下,我們該如何優化TCP連接呢?

(1)關閉粘包算法

如果用户對於請求的耗時很敏感,我們就需要在TCP套接字上添加tcp_nodelay參數來關閉粘包算法,以便數據包能夠立刻發送出去。此時,我們也可以設置net.ipv4.tcp_syncookies的參數值為1。

(2)避免頻繁的創建和回收連接資源

網絡連接的創建和回收是非常消耗性能的,我們可以通過關閉空閒的連接、重複利用已經分配的連接資源來優化服務器的性能。重複利用已經分配的連接資源大家其實並不陌生,像:線程池、數據庫連接池就是複用了線程和數據庫連接。

我們可以通過如下參數來關閉服務器的空閒連接和複用已分配的連接資源。

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time=1800

(3)避免重複發送數據包

TCP支持超時重傳機制。如果發送方將數據包已經發送給接收方,但發送方並未收到反饋,此時,如果達到設置的時間間隔,就會觸發TCP的超時重傳機制。為了避免發送成功的數據包再次發送,我們需要將服務器的net.ipv4.tcp_sack參數設置為1。

(4)增大服務器文件描述符數量

在Linux操作系統中,一個網絡連接也會佔用一個文件描述符,連接越多,佔用的文件描述符也就越多。如果文件描述符設置的比較小,也會影響我們服務器的性能。此時,我們就需要增大服務器文件描述符的數量。

例如:fs.file-max = 10240000,表示服務器最多可以打開10240000個文件。

點擊關注,第一時間瞭解華為雲新鮮技術~

user avatar ivyzhang 頭像 sunhengzhe 頭像 yadong_zhang 頭像
3 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.