案例實戰:每日上億請求量的電商系統,JVM年輕代垃圾回收參數如何優化?

来源:https://www.cnblogs.com/qiaogeli/archive/2019/08/07/11318031.html
-Advertisement-
Play Games

出自:http://1t.click/7TJ 目錄: 案例背景引入 特殊的電商大促場景 抗住大促的瞬時壓力需要幾台機器? 大促高峰期訂單系統的記憶體使用模型估算 記憶體到底該如何分配? 新生代垃圾回收優化之一:Survivor空間夠不夠 新生代對象躲過多少次垃圾回收後進入老年代? 多大的對象直接進入老年 ...


出自:http://1t.click/7TJ

目錄:

案例背景引入

特殊的電商大促場景

抗住大促的瞬時壓力需要幾台機器?

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

記憶體到底該如何分配?

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

新生代對象躲過多少次垃圾回收後進入老年代?

多大的對象直接進入老年代?

別忘了指定垃圾回收器

今日思考題

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個訂單處理完了,所有相關對象都失去了引用,可以回收的狀態。

大家看下圖:

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記憶體入下圖所示。

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

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

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

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

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

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

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

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

此時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,如下圖。

這個時候,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放不下導致對象進入老年代?

會不會因動態年齡判斷規則進入老年代?

請大家把自己的思考發至討論區進行互動討論

覺得文章不錯的歡迎關註我的WX公眾號:程式員喬戈里
我是百度後臺開發工程師,哈工大電腦本碩,專註分享技術乾貨/編程資源/求職面試/成長感悟等,關註送5000G編程資源和自己整理的一份幫助不少人拿下java的offer的面經附答案,免費下載CSDN資源。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • "上一節" 我們分析了廣告索引的維護有2種, 和`增量索引維護`。因為廣告檢索是廣告系統中最為重要的環節,大家一定要認真理解我們索引設計的思路,接下來我們來編碼實現索引維護功能。 我們來定義一個介面,來接收所有index的增刪改查操作,介面定義一個範型,來接收2個參數, 代表我們索引的健值, 代表返 ...
  • #今日目標 **50行代碼爬取微信公眾號所有文章** 今天要爬取的是微信公眾號,爬取公眾號的方式常見的有兩種。一是通過搜狗搜索去獲取,缺點是只能獲取最新的十條推送文章, 今天介紹另一種通過抓包PC端微信的方式去獲取公眾號文章的方法,相對其他方法更加便捷。 分析:我們發現每次下拉刷新文章的時候都會請求... ...
  • 代碼塊 : 以冒號作為開始,用縮進來劃分作用域 作用域 : 作用的範圍 if 5 == 6: print(111) print(222) 代碼塊註意點: """要麼全都是一個tab 縮進 , 要麼全都是4個空格 ,不能混合使用""" """ if 5 == 5: print(1) print(2) ...
  • ```Python import requests import pyquery def crawl_page(url: str) -> None: headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleW... ...
  • 桌面程式在運行過程中,時常需要在主界面之上彈出小窗,把某種消息告知用戶,以便用戶及時知曉並對症處理。這類小視窗通常稱作對話框,依據消息交互的過程,可將對話框分為三類:消息對話框、確認對話框、輸入對話框,分別介紹如下: 1、消息對話框這類對話框僅僅向用戶展示一段文本,告訴用戶發生了什麼事情。它起到了提 ...
  • 使用示例: 提取一條記錄: 提取多條記錄: 設置sql語句參數: 插入一條記錄: 增刪改的操作類似,註意增刪改都需要conn.commit()才會提交到資料庫。 另一種設置參數的方式: 批量操作: ...
  • glob模塊 提供了一個函數,用於匹配符合要求的文件: re模塊 字元串正則匹配 datetime模塊 日期時間 格式化輸出也可以這樣用: 數據壓縮 支持數據打包、壓縮的模塊:zlib,gzip,bz2,zipfile,以及 tarfile。 壓縮數據: 壓縮文件: 解壓文件: ...
  • 1、java gc 2、java class的載入過程 3、java hashmap、 為什麼用紅黑樹、紅黑樹鄰接點為啥是8 。 4、拜占庭問題 5、一致性哈希 6、如何控制負載均衡。 7、http碼 302 403 。 8、https 加密過程。 9、操作系統虛存實現原理,交換,覆蓋區別。 10、 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...