1. 程式人生 > 實用技巧 >雙十一來臨,你就不想知道阿里後臺都做了哪些準備嗎?

雙十一來臨,你就不想知道阿里後臺都做了哪些準備嗎?

目錄:

  1. 案例背景引入
  2. 特殊的電商大促場景
  3. 抗住大促的瞬時壓力需要幾臺機器?
  4. 大促高峰期訂單系統的記憶體使用模型估算
  5. 記憶體到底該如何分配?
  6. 新生代垃圾回收優化之一:Survivor空間夠不夠
  7. 新生代物件躲過多少次垃圾回收後進入老年代?
  8. 多大的物件直接進入老年代?
  9. 別忘了指定垃圾回收器
  10. 今日思考題

阿里9年雙11:網際網路技術超級工程

11月11日,原本平淡無奇,因為網際網路,因為天貓,有了特殊的魅力。

早在2009年,天貓還不叫天貓,叫淘寶商城。這一年,第一屆雙11當天的交易額是1000萬元。2012年1月11日,淘寶商城正式更名為天貓。那一年,有了天貓雙11的叫法。彼時,他們或許還沒想到,這個商業性的“人造節”終將成為中國乃至全球商業社會中的一個標誌性時刻。

所以第一步當然是來看一下阿里在雙十一都幹了什麼事情

新智慧

新基礎

主要介紹底層技術應用是如何進行的,萬丈高樓平地起,但是沒有一個紮實的地基怎麼可以,天貓雙十一的背後,看一下是如何進行的

新體驗

在這一方面,不多少,一天10億淘寶首頁,這可不是10個 啊,當時看到這個資料我驚呆了,個人承認,這一方面從來沒接觸這樣的資料量

1、案例背景引入

按照慣例,我們接下來會用案例驅動來帶著大家分析到底該如何在特定場景下,預估系統的記憶體使用模型。

然後合理優化新生代、老年代、Eden和Survivor各個區域的記憶體大小。

接著再儘量優化引數避免新生代的物件進入老年代,儘量讓物件留在新生代裡被回收掉。

我們這裡的背景是電商系統,電商系統其實一般會拆分為很多的子系統獨立部署

比如商品系統、訂單系統、促銷系統、庫存系統、倉儲系統、會員系統,等等

我們這裡就以比較核心的訂單系統作為例子來說明。

(提示:食用本案例之前,請務必充分理解專欄之前兩週的文章!)

我們的案例背景是每日上億請求量的電商系統,那麼大家可以來推算一下每日上億請求量的電商系統,他會每日有多少活躍使用者?

一般按每個使用者平均訪問20次來計算,那麼上億請求量,大致需要有500萬日活使用者。

那麼繼續來推算一下,這500萬的日活使用者都是會進來進行大量的瀏覽,那麼多少人會下訂單?

這裡可以按照10%的付費轉化率來計算,每天大概有50萬人會下訂單,那麼大致就是每天會有50萬訂單。

這50萬訂單算他集中在每天4小時的高峰期內,那麼其實平均下來每秒鐘大概也就幾十個訂單,大家是不是覺得根本沒啥可說的?

因為幾十個訂單的壓力下,根本就不需要對JVM多關注,基本上就是每秒鐘佔用一些新生代記憶體,隔很久新生代才會滿。然後一次Minor GC後垃圾物件清理掉,記憶體就空出來了,幾乎無壓力。


2、特殊的電商大促場景

但是如果你要是考慮到特殊的電商大促場景,就不會這麼想了

因為很多中小型的電商平臺,確實平時系統壓力其實沒那麼大,也沒太大的高併發,每秒幾千併發壓力就算是高峰壓力了。

但是如果遇到一些大促場景,比如雙11什麼的,情況就不同了。

假設在類似雙11的節日裡,零點的時候,很多人等著大促開始就要剁手購物,這個時候,可能在大促開始的短短10分鐘內,瞬間就會有50萬訂單。

那麼此時每秒就會有接近1000的下單請求,我們就針對這種大促場景來對訂單系統的記憶體使用模型分析一下。


3、抗住大促的瞬時壓力需要幾臺機器?

那麼要抗住大促期間的瞬時下單壓力,訂單系統需要部署幾臺機器呢?

基本上可以按3臺來算,就是每臺機器每秒需要抗300個下單請求。這個也是非常合理的,而且需要假設訂單系統部署的就是最普通的標配4核8G機器。

從機器本身的CPU資源和記憶體資源角度,抗住每秒300個下單請求是沒問題的。

但是問題就在於需要對JVM有限的記憶體資源進行合理的分配和優化,包括對垃圾回收進行合理的優化,讓JVM的GC次數儘可能最少,而且儘量避免Full GC,這樣可以儘可能減少JVM的GC對高峰期的系統新更難的影響。


4、大促高峰期訂單系統的記憶體使用模型估算

背景已經全部說完了,接下來咱們就得來預估訂單系統的記憶體使用模型了.

基本上可以按照每秒鐘處理300個下單請求來估算,其實無論是訂單處理效能還是併發情況,都跟生產很接近

因為處理下單請求是比較耗時的,涉及很多介面的呼叫,基本上每秒處理100~300個下單請求是差不多的。

那麼每個訂單咱們就按1kb的大小來估算,單單是300個訂單就會有300kb的記憶體開銷

然後算上訂單物件連帶的訂單條目物件、庫存、促銷、優惠券等等一系列的其他業務物件,一般需要對單個物件開銷放大10倍~20倍。

此外,除了下單之外,這個訂單系統還會有很多訂單相關的其他操作,比如訂單查詢之類的,所以連帶算起來,可以往大了估算,再擴大10倍的量。

那麼每秒鐘會有大概300kb * 20 * 10 = 60mb的記憶體開銷。

但是一秒過後,可以認為這60mb的物件就是垃圾了,因為300個訂單處理完了,所有相關物件都失去了引用,可以回收的狀態。

大家看下圖:

每日上億請求量的電商系統,JVM年輕代垃圾回收引數如何優化?


5、記憶體到底該如何分配?

假設我們有4核8G的機器,那麼給JVM的記憶體一般會到4G,剩下幾個G會留點空餘給作業系統之類的來使用

不要想著把機器記憶體一下子都耗盡,其中堆記憶體我們可以給3G,新生代我們可以給到1.5G,老年代也是1.5G。

然後每個執行緒的Java虛擬機器棧有1M,那麼JVM裡如果有幾百個執行緒大概會有幾百M

然後再給永久代256M記憶體,基本上這4G記憶體就差不多了。

同時還要記得設定一些必要的引數,比如說開啟“-XX:HandlePromotionFailure”選項(不熟悉這個引數的,可以回頭複習一下專欄之前的文章)

JVM引數如下所示:

“-Xms3072M -Xmx3072M -Xmn1536M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:HandlePromotionFailure”

但是“-XX:HandlePromotionFailure”引數在JDK 1.6以後就被廢棄了,所以現在一般都不會在生產環境裡設定這個引數了。

在JDK 1.6以後,只要判斷“老年代可用空間”> “新生代物件總和”,或者“老年代可用空間”> “歷次Minor GC升入老年代物件的平均大小”

上述兩個條件滿足一個,就可以直接進行Minor GC,不需要提前觸發Full GC了。

所以實際上,如果大家用的是JDK 1.7或者JDK 1.8,那麼JVM引數就保持如下即可,後面也都不再加入這個引數了:

“-Xms3072M -Xmx3072M -Xmn1536M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M”

此時JVM記憶體入下圖所示。

每日上億請求量的電商系統,JVM年輕代垃圾回收引數如何優化?

接著就很明確了,訂單系統的系統程式在大促期間不停的執行,每秒處理300個訂單,都會佔據新生代60MB的記憶體空間

但是1秒過後這60MB物件都會變成垃圾,那麼新生代1.5G的記憶體空間大概需要25秒就會佔滿,如下圖。

每日上億請求量的電商系統,JVM年輕代垃圾回收引數如何優化?

25秒過後就會要進行Minor GC了,此時因為有“-XX:HandlePromotionFailure”選項,所以你可以認為需要進行的檢查,主要就是比較 “老年代可用空間大小”和“歷次Minor GC後進入老年代物件的平均大小”,剛開始肯定這個檢查是可以通過的。

所以Minor GC直接執行,一下子可以回收掉99%的新生代物件,因為除了最近一秒的訂單請求還在處理,大部分訂單早就處理完了,所以此時可能存活物件就100MB左右。

但是這裡問題來了,如果“-XX:SurvivorRatio”引數預設值為8,那麼此時新生代裡Eden區大概佔據了1.2GB記憶體,每個Survivor區是150MB的記憶體,如下圖。

每日上億請求量的電商系統,JVM年輕代垃圾回收引數如何優化?

所以Eden區1.2GB滿了就要進行Minor GC了,因此大概只需要20秒,就會把Eden區塞滿,就要進行Minor GC了。

然後GC後存活物件在100MB左右,會放入S1區域內。如下圖。

每日上億請求量的電商系統,JVM年輕代垃圾回收引數如何優化?

然後再次執行20秒,把Eden區佔滿,再次垃圾回收Eden和S1中的物件,存活物件可能還是在100MB左右會進入S2區,如下圖。

每日上億請求量的電商系統,JVM年輕代垃圾回收引數如何優化?

此時JVM引數如下:

“-Xms3072M -Xmx3072M -Xmn1536M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8”


6、新生代垃圾回收優化之一:Survivor空間夠不夠

首先在進行JVM優化的時候,第一個要考慮的問題,就是你通過估算,你的新生代的Survivor區到底夠不夠?

按照上述邏輯,首先每次新生代垃圾回收在100MB左右,有可能會突破150MB,那麼豈不是經常會出現Minor GC過後的物件無法放入Survivor中?然後豈不是頻繁會讓物件進入老年代?

還有,即使Minor GC後的物件少於150MB,但是即使是100MB的物件進入Survivor區,因為這是一批同齡物件,直接超過了Survivor區空間的50%,此時也可能會導致物件進入老年代。

(關於jvm的垃圾回收規則,如果不太清楚,請參加專欄之前的文章)

所以其實按照我們這個模型來說,Survivor區域是明顯不足的。

這裡其實建議的是調整新生代和老年代的大小,因為這種普通業務系統,明顯大部分物件都是短生存週期的,根本不應該頻繁進入老年代,也沒必要給老年代維持過大的記憶體空間,首先得先讓物件儘量留在新生代裡。

所以此時可以考慮把新生代調整為2G,老年代為1G,那麼此時Eden為1.6G,每個Survivor為200MB,如下圖。

每日上億請求量的電商系統,JVM年輕代垃圾回收引數如何優化?

這個時候,Survivor區域變大,就大大降低了新生代GC過後存活物件在Survivor裡放不下的問題,或者是同齡物件超過Survivor 50%的問題。

這樣就大大降低了新生代物件進入老年代的概率。

此時JVM的引數如下:

“-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8”

其實對任何系統,首先類似上文的記憶體使用模型預估以及合理的分配記憶體,儘量讓每次Minor GC後的物件都留在Survivor裡,不要進入老年代,這是你首先要進行優化的一個地方。


7、新生代物件躲過多少次垃圾回收後進入老年代?

大家都知道,除了Minor GC後物件無法放入Survivor會導致一批物件進入老年代之外,還有就是有些物件連續躲過15次垃圾回收後會自動升入老年代。

其實按照上述記憶體執行模型,基本上20多秒觸發一次Minor GC,那麼如果按照“-XX:MaxTenuringThreshold”引數的預設值15次來說,你要是連續躲過15次GC,就是一個物件在新生代停留超過了幾分鐘了,此時他進入老年代也是應該的。

有些部落格會說,應該提高這個引數,比如增加到20次,或者30次,其實那種說法根本是不對的

因為你對這個引數考慮必須結合系統的執行模型來說,如果躲過15次GC都幾分鐘了,一個物件幾分鐘都不能被回收,說明肯定是系統裡類似用@Service、@Controller之類的註解標註的那種需要長期存活的核心業務邏輯元件。

那麼他就應該進入老年代,何況這種物件一般很少,一個系統累計起來最多也就幾十MB而已。

所以你說你提高“-XX:MaxTenuringThreshold”引數的值,有啥用呢?讓這些物件在新生代裡多停留幾分鐘?

因此考慮問題,一定不要人云亦云,要結合執行原理,自己推演和思考,不同的業務系統還都是不一樣的。

其實這個引數甚至你都可以降低他的值,比如降低到5次,也就是說一個物件如果躲過5次Minor GC,在新生代裡停留超過1分鐘了,儘快就讓他進入老年代,別在新生代裡佔著記憶體了。

總之,對於這個引數務必是結合你的系統具體執行的模型來考慮。

要記住,JVM沒有萬能的最佳引數,但是有一套通用的分析和優化的方法。

此時JVM引數如下:

“-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5”


8、多大的物件直接進入老年代?

另外有一個邏輯是說,大物件可以直接進入老年代 ,因為大物件說明是要長期存活和使用的

比如在JVM裡可能會快取一些資料,這個一般可以結合自己系統中到底有沒有建立大物件來決定。

但是一般來說,給他設定個1MB足以,因為一般很少有超過1MB的大物件。如果有,可能是你提前分配了一個大陣列、大List之類的東西用來放快取的資料。

此時JVM引數如下:

“-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M”


9、別忘了指定垃圾回收器

同時大家別忘了要指定垃圾回收器,新生代使用ParNew,老年代使用CMS,如下JVM引數 :

“-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC”

ParNew垃圾回收器的核心引數,其實就是配套的新生代記憶體大小、Eden和Survivor的比例

只要你設定合理,避免Minor GC後物件放不下Survivor進入老年代,或者是動態年齡判定之後進入老年代,給新生代裡的Survivor充足的空間,那麼Minor GC一般就沒什麼問題。

然後根據你的系統執行模型,合理設定“-XX:MaxTenuringThreshold”,讓那些長期存活的物件,抓緊儘快進入老年代,別在新生代裡一直待著。

這樣基本上一個初步的優化好的JVM引數就結合你的業務出來了。明天我們繼續結合案例來分析 老年代的垃圾回收和引數優化方式。


10、今日思考題

大家看完這個案例,可以直接去看看自己生產系統的JVM引數了,看看你的新生代、老年代、Eden和Survivor的大小

然後去估算一下你的系統執行模型:

  • 每秒佔用多少記憶體?
  • 多長時間觸發一次Minor GC?
  • 一般Minor GC後有多少存活物件?
  • Survivor能放的下嗎?
  • 會不會頻繁因為Survivor放不下導致物件進入老年代?
  • 會不會因動態年齡判斷規則進入老年代?

請大家把自己的思考發至討論區進行互動討論,在明天的文章中,我也會進行相應的點評。

阿里億級高併發系統設計(內部絕密)

最後,有了前面這麼多部門之間的聯合以及新穎技術的鋪墊,雙十一在科技感以及使用者體驗上非常華麗,但是,這只是高樓大廈的林立,就像我前面說的,高樓怎麼能少了地基的支撐呢?而雙十一最大的問題就是瞬間併發量的問題,畢竟阿里號稱的是有幾億使用者的平臺呀,作為網際網路的大哥,也不會藏私不是,這不,阿里內部的高併發系統設計方案也一起開源出來

需要下面這份文件的,新增下方小助手獲取

主要分為以下幾點

基礎篇

我們知道,高併發代表著大流量,高併發系統設計的魅力就在於我們能夠憑藉自己的聰明才智設計巧妙的方案,從而抵抗巨大流量的衝擊,帶給使用者更好的使用體驗。這些方案好似能操縱流量,讓流量更加平穩得被系統中的服務和元件處理。

資料庫篇

快取篇

通過前面資料庫篇的學習,你已經瞭解了在高併發大流量下,資料庫層的演進過程以及庫表設計上的考慮點。你的垂直電商系統在完成了對資料庫的主從分離和分庫分表之後,已經可以支撐十幾萬DAU了,那我們來看下一個重點--快取

訊息佇列篇

在課程一開始,我就帶你瞭解了高併發系統設計的三個目標:效能、可用性和可擴充套件性,而在提升系統性能方面,我們一直關注的是系統的查詢效能。也用了很多的篇幅去講解資料庫的分散式改造,各類快取的原理和使用技巧。究其原因在於,我們遇到的大部分場景都是讀多寫少,尤其是在一個系統的初級階段。

但是隨著系統的發展,我想基礎的系統設計已經不大行了,需要更好的發展,那訊息佇列必須安排上啊

分散式服務篇

通過前面幾個篇章的內容,你已經從資料庫、快取和訊息佇列的角度對自己的垂直電商系統在效能、可用性和擴充套件性上做了優化。

現在,你的系統執行穩定,好評不斷,每天高峰期的流量,已經達到了10000/s請求,DAU也漲到了幾十萬。CEO非常高興,打算繼續完善產品功能,以便進行新一輪的運營推廣,爭取在下個雙十一可以將DAU衝擊過百萬。在這時,你開始考慮,怎麼通過技術上的優化改造,來支撐更高的併發流量,比如支撐過百萬的DAU。那這個時候 ,分散式就到來了

維護篇

在一個專案的生命週期裡,執行維護佔據著很大的比重,在重要性上,它幾乎與專案研發並駕齊驅。而在系統運維過程中,能夠及時地發現問題並解決問題,是每一個團隊的本職工作。所以,你的垂直電商系統在搭建之初,運維團隊肯定完成了對於機器CPU、記憶體、磁碟、網路等基礎監控,期望能在出現問題時,及時地發現並且處理。你本以為萬事大吉,卻沒想到系統在執行過程中,頻頻得到使用者的投訴,這個時候,運維的重要性就體現出來了

實戰篇

END