Netty 系列之 Netty 百萬級推送服務設計要點

1. 背景

1.1. 話題來源

最近不少從事移動互聯網和物聯網開發的同窗給我發郵件或者微博私信我,諮詢推送服務相關的問題。問題五花八門,在幫助你們答疑解惑的過程當中,我也對問題進行了總結,大概能夠概括爲以下幾類:java

  1. Netty 是否能夠作推送服務器?
  2. 若是使用 Netty 開發推送服務,一個服務器最多能夠支撐多少個客戶端?
  3. 使用 Netty 開發推送服務遇到的各類技術問題。

因爲諮詢者衆多,關注點也比較集中,我但願經過本文的案例分析和對推送服務設計要點的總結,幫助你們在實際工做中少走彎路。面試

1.2. 推送服務

移動互聯網時代,推送 (Push) 服務成爲 App 應用不可或缺的重要組成部分,推送服務能夠提高用戶的活躍度和留存率。咱們的手機天天接收到各類各樣的廣告和提示消息等大多數都是經過推送服務實現的。算法

隨着物聯網的發展,大多數的智能家居都支持移動推送服務,將來全部接入物聯網的智能設備都將是推送服務的客戶端,這就意味着推送服務將來會面臨海量的設備和終端接入。數據庫

1.3. 推送服務的特色設計模式

移動推送服務的主要特色以下:數組

  1. 使用的網絡主要是運營商的無線移動網絡,網絡質量不穩定,例如在地鐵上信號就不好,容易發生網絡閃斷;
  2. 海量的客戶端接入,並且一般使用長鏈接,不管是客戶端仍是服務端,資源消耗都很是大;
  3. 因爲谷歌的推送框架沒法在國內使用,Android 的長鏈接是由每一個應用各自維護的,這就意味着每檯安卓設備上會存在多個長鏈接。即使沒有消息須要推送,長鏈接自己的心跳消息量也是很是巨大的,這就會致使流量和耗電量的增長;
  4. 不穩定:消息丟失、重複推送、延遲送達、過時推送時有發生;
  5. 垃圾消息滿天飛,缺少統一的服務治理能力。

爲了解決上述弊端,一些企業也給出了本身的解決方案,例如京東雲推出的推送服務,能夠實現多應用單服務單鏈接模式,使用 AlarmManager 定時心跳節省電量和流量。安全

2. 智能家居領域的一個真實案例

2.1. 問題描述

智能家居 MQTT 消息服務中間件,保持 10 萬用戶在線長鏈接,2 萬用戶併發作消息請求。程序運行一段時間以後,發現內存泄露,懷疑是 Netty 的 Bug。其它相關信息以下:服務器

  1. MQTT 消息服務中間件服務器內存 16G,8 個核心 CPU;
  2. Netty 中 boss 線程池大小爲 1,worker 線程池大小爲 6,其他線程分配給業務使用。該分配方式後來調整爲 worker 線程池大小爲 11,問題依舊;
  3. Netty 版本爲 4.0.8.Final。

2.2. 問題定位

首先須要 dump 內存堆棧,對疑似內存泄露的對象和引用關係進行分析,以下所示:微信

img

咱們發現 Netty 的 ScheduledFutureTask 增長了 9076%,達到 110W 個左右的實例,經過對業務代碼的分析發現用戶使用 IdleStateHandler 用於在鏈路空閒時進行業務邏輯處理,可是空閒時間設置的比較大,爲 15 分鐘。網絡

Netty 的 IdleStateHandler 會根據用戶的使用場景,啓動三類定時任務,分別是:ReaderIdleTimeoutTask、WriterIdleTimeoutTask 和 AllIdleTimeoutTask,它們都會被加入到 NioEventLoop 的 Task 隊列中被調度和執行。

因爲超時時間過長,10W 個長連接鏈路會建立 10W 個 ScheduledFutureTask 對象,每一個對象還保存有業務的成員變量,很是消耗內存。用戶的持久代設置的比較大,一些定時任務被老化到持久代中,沒有被 JVM 垃圾回收掉,內存一直在增加,用戶誤認爲存在內存泄露。

事實上,咱們進一步分析發現,用戶的超時時間設置的很是不合理,15 分鐘的超時達不到設計目標,從新設計以後將超時時間設置爲 45 秒,內存能夠正常回收,問題解決。

2.3. 問題總結

若是是 100 個長鏈接,即使是長週期的定時任務,也不存在內存泄露問題,在新生代經過 minor GC 就能夠實現內存回收。正是由於十萬級的長鏈接,致使小問題被放大,引出了後續的各類問題。

事實上,若是用戶確實有長週期運行的定時任務,該如何處理?對於海量長鏈接的推送服務,代碼處理稍有不慎,就滿盤皆輸,下面咱們針對 Netty 的架構特色,介紹下如何使用 Netty 實現百萬級客戶端的推送服務。

3. Netty 海量推送服務設計要點

做爲高性能的 NIO 框架,利用 Netty 開發高效的推送服務技術上是可行的,可是因爲推送服務自身的複雜性,想要開發出穩定、高性能的推送服務並不是易事,須要在設計階段針對推送服務的特色進行合理設計。

3.1. 最大句柄數修改

百萬長鏈接接入,首先須要優化的就是 Linux 內核參數,其中 Linux 最大文件句柄數是最重要的調優參數之一,默認單進程打開的最大句柄數是 1024,經過 ulimit -a 能夠查看相關參數,示例以下:

[root@lilinfeng ~]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 256324
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024

...... 後續輸出省略

當單個推送服務接收到的連接超過上限後,就會報「too many open files」,全部新的客戶端接入將失敗。

經過 vi /etc/security/limits.conf 添加以下配置參數:修改以後保存,註銷當前用戶,從新登陸,經過 ulimit -a 查看修改的狀態是否生效。

*  soft  nofile  1000000
*  hard  nofile  1000000

須要指出的是,儘管咱們能夠將單個進程打開的最大句柄數修改的很是大,可是當句柄數達到必定數量級以後,處理效率將出現明顯降低,所以,須要根據服務器的硬件配置和處理能力進行合理設置。若是單個服務器性能不行也能夠經過集羣的方式實現。

3.2. 小心 CLOSE_WAIT

從事移動推送服務開發的同窗可能都有體會,移動無線網絡可靠性很是差,常常存在客戶端重置鏈接,網絡閃斷等。

在百萬長鏈接的推送系統中,服務端須要可以正確處理這些網絡異常,設計要點以下:

  1. 客戶端的重連間隔須要合理設置,防止鏈接過於頻繁致使的鏈接失敗(例如端口尚未被釋放);
  2. 客戶端重複登錄拒絕機制;
  3. 服務端正確處理 I/O 異常和解碼異常等,防止句柄泄露。

最後特別須要注意的一點就是 close_wait 過多問題,因爲網絡不穩定常常會致使客戶端斷連,若是服務端沒有可以及時關閉 socket,就會致使處於 close_wait 狀態的鏈路過多。close_wait 狀態的鏈路並不釋放句柄和內存等資源,若是積壓過多可能會致使系統句柄耗盡,發生「Too many open files」異常,新的客戶端沒法接入,涉及建立或者打開句柄的操做都將失敗。

下面對 close_wait 狀態進行下簡單介紹,被動關閉 TCP 鏈接狀態遷移圖以下所示:

img

圖 3-1 被動關閉 TCP 鏈接狀態遷移圖

close_wait 是被動關閉鏈接是造成的,根據 TCP 狀態機,服務器端收到客戶端發送的 FIN,TCP 協議棧會自動發送 ACK,連接進入 close_wait 狀態。但若是服務器端不執行 socket 的 close() 操做,狀態就不能由 close_wait 遷移到 last_ack,則系統中會存在不少 close_wait 狀態的鏈接。一般來講,一個 close_wait 會維持至少 2 個小時的時間(系統默認超時時間的是 7200 秒,也就是 2 小時)。若是服務端程序因某個緣由致使系統形成一堆 close_wait 消耗資源,那麼一般是等不到釋放那一刻,系統就已崩潰。

致使 close_wait 過多的可能緣由以下:

  1. 程序處理 Bug,致使接收到對方的 fin 以後沒有及時關閉 socket,這多是 Netty 的 Bug,也多是業務層 Bug,須要具體問題具體分析;
  2. 關閉 socket 不及時:例如 I/O 線程被意外阻塞,或者 I/O 線程執行的用戶自定義 Task 比例太高,致使 I/O 操做處理不及時,鏈路不能被及時釋放。

下面咱們結合 Netty 的原理,對潛在的故障點進行分析。

設計要點 1:不要在 Netty 的 I/O 線程上處理業務(心跳發送和檢測除外)。Why? 對於 Java 進程,線程不能無限增加,這就意味着 Netty 的 Reactor 線程數必須收斂。Netty 的默認值是 CPU 核數 * 2,一般狀況下,I/O 密集型應用建議線程數儘可能設置大些,但這主要是針對傳統同步 I/O 而言,對於非阻塞 I/O,線程數並不建議設置太大,儘管沒有最優值,可是 I/O 線程數經驗值是 [CPU 核數 + 1,CPU 核數 *2 ] 之間。

假如單個服務器支撐 100 萬個長鏈接,服務器內核數爲 32,則單個 I/O 線程處理的連接數 L = 100/(32 * 2) = 15625。 假如每 5S 有一次消息交互(新消息推送、心跳消息和其它管理消息),則平均 CAPS = 15625 / 5 = 3125 條 / 秒。這個數值相比於 Netty 的處理性能而言壓力並不大,可是在實際業務處理中,常常會有一些額外的複雜邏輯處理,例如性能統計、記錄接口日誌等,這些業務操做性能開銷也比較大,若是在 I/O 線程上直接作業務邏輯處理,可能會阻塞 I/O 線程,影響對其它鏈路的讀寫操做,這就會致使被動關閉的鏈路不能及時關閉,形成 close_wait 堆積。

設計要點 2:在 I/O 線程上執行自定義 Task 要小心。Netty 的 I/O 處理線程 NioEventLoop 支持兩種自定義 Task 的執行:

  1. 普通的 Runnable: 經過調用 NioEventLoop 的 execute(Runnable task) 方法執行;
  2. 定時任務 ScheduledFutureTask: 經過調用 NioEventLoop 的 schedule(Runnable command, long delay, TimeUnit unit) 系列接口執行。

爲何 NioEventLoop 要支持用戶自定義 Runnable 和 ScheduledFutureTask 的執行,並非本文要討論的重點,後續會有專題文章進行介紹。本文重點對它們的影響進行分析。

在 NioEventLoop 中執行 Runnable 和 ScheduledFutureTask,意味着容許用戶在 NioEventLoop 中執行非 I/O 操做類的業務邏輯,這些業務邏輯一般用消息報文的處理和協議管理相關。它們的執行會搶佔 NioEventLoop I/O 讀寫的 CPU 時間,若是用戶自定義 Task 過多,或者單個 Task 執行週期過長,會致使 I/O 讀寫操做被阻塞,這樣也間接致使 close_wait 堆積。

因此,若是用戶在代碼中使用到了 Runnable 和 ScheduledFutureTask,請合理設置 ioRatio 的比例,經過 NioEventLoop 的 setIoRatio(int ioRatio) 方法能夠設置該值,默認值爲 50,即 I/O 操做和用戶自定義任務的執行時間比爲 1:1。

個人建議是當服務端處理海量客戶端長鏈接的時候,不要在 NioEventLoop 中執行自定義 Task,或者非心跳類的定時任務。

設計要點 3:IdleStateHandler 使用要小心。不少用戶會使用 IdleStateHandler 作心跳發送和檢測,這種用法值得提倡。相比於本身啓定時任務發送心跳,這種方式更高效。可是在實際開發中須要注意的是,在心跳的業務邏輯處理中,不管是正常仍是異常場景,處理時延要可控,防止時延不可控致使的 NioEventLoop 被意外阻塞。例如,心跳超時或者發生 I/O 異常時,業務調用 Email 發送接口告警,因爲 Email 服務端處理超時,致使郵件發送客戶端被阻塞,級聯引發 IdleStateHandler 的 AllIdleTimeoutTask 任務被阻塞,最終 NioEventLoop 多路複用器上其它的鏈路讀寫被阻塞。

對於 ReadTimeoutHandler 和 WriteTimeoutHandler,約束一樣存在。

3.3. 合理的心跳週期

百萬級的推送服務,意味着會存在百萬個長鏈接,每一個長鏈接都須要靠和 App 之間的心跳來維持鏈路。合理設置心跳週期是很是重要的工做,推送服務的心跳週期設置須要考慮移動無線網絡的特色。

當一臺智能手機連上移動網絡時,其實並無真正鏈接上 Internet,運營商分配給手機的 IP 實際上是運營商的內網 IP,手機終端要鏈接上 Internet 還必須經過運營商的網關進行 IP 地址的轉換,這個網關簡稱爲 NAT(NetWork Address Translation),簡單來講就是手機終端鏈接 Internet 其實就是移動內網 IP,端口,外網 IP 之間相互映射。

GGSN(GateWay GPRS Support Note) 模塊就實現了 NAT 功能,因爲大部分的移動無線網絡運營商爲了減小網關 NAT 映射表的負荷,若是一個鏈路有一段時間沒有通訊時就會刪除其對應表,形成鏈路中斷,正是這種刻意縮短空閒鏈接的釋放超時,本來是想節省信道資源的做用,沒想到讓互聯網的應用不得以遠高於正常頻率發送心跳來維護推送的長鏈接。以中移動的 2.5G 網絡爲例,大約 5 分鐘左右的基帶空閒,鏈接就會被釋放。

因爲移動無線網絡的特色,推送服務的心跳週期並不能設置的太長,不然長鏈接會被釋放,形成頻繁的客戶端重連,可是也不能設置過短,不然在當前缺少統一心跳框架的機制下很容易致使信令風暴(例如微信心跳信令風暴問題)。具體的心跳週期並無統一的標準,180S 也許是個不錯的選擇,微信爲 300S。

在 Netty 中,能夠經過在 ChannelPipeline 中增長 IdleStateHandler 的方式實現心跳檢測,在構造函數中指定鏈路空閒時間,而後實現空閒回調接口,實現心跳的發送和檢測,代碼以下:

public void initChannel({@link Channel} channel) {
 channel.pipeline().addLast("idleStateHandler", new {@link   IdleStateHandler}(0, 0, 180));
 channel.pipeline().addLast("myHandler", new MyHandler());
}
攔截鏈路空閒事件並處理心跳:
 public class MyHandler extends {@link ChannelHandlerAdapter} {
     {@code @Override}
      public void userEventTriggered({@link ChannelHandlerContext} ctx, {@link Object} evt) throws {@link Exception} {
          if (evt instanceof {@link IdleStateEvent}} {
              // 心跳處理 
          }
      }
  }

3.4. 合理設置接收和發送緩衝區容量

對於長連接,每一個鏈路都須要維護本身的消息接收和發送緩衝區,JDK 原生的 NIO 類庫使用的是 java.nio.ByteBuffer, 它實際是一個長度固定的 Byte 數組,咱們都知道數組沒法動態擴容,ByteBuffer 也有這個限制,相關代碼以下:

public abstract class ByteBuffer
    extends Buffer
    implements Comparable
{
    final byte[] hb; // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;

容量沒法動態擴展會給用戶帶來一些麻煩,例如因爲沒法預測每條消息報文的長度,可能須要預分配一個比較大的 ByteBuffer,這一般也沒有問題。可是在海量推送服務系統中,這會給服務端帶來沉重的內存負擔。假設單條推送消息最大上限爲 10K,消息平均大小爲 5K,爲了知足 10K 消息的處理,ByteBuffer 的容量被設置爲 10K,這樣每條鏈路實際上多消耗了 5K 內存,若是長連接鏈路數爲 100 萬,每一個鏈路都獨立持有 ByteBuffer 接收緩衝區,則額外損耗的總內存 Total(M) = 1000000 * 5K = 4882M。內存消耗過大,不只僅增長了硬件成本,並且大內存容易致使長時間的 Full GC,對系統穩定性會形成比較大的衝擊。

實際上,最靈活的處理方式就是可以動態調整內存,即接收緩衝區能夠根據以往接收的消息進行計算,動態調整內存,利用 CPU 資源來換內存資源,具體的策略以下:

  1. ByteBuffer 支持容量的擴展和收縮,能夠按需靈活調整,以節約內存;
  2. 接收消息的時候,能夠按照指定的算法對以前接收的消息大小進行分析,並預測將來的消息大小,按照預測值靈活調整緩衝區容量,以作到最小的資源損耗知足程序正常功能。

幸運的是,Netty 提供的 ByteBuf 支持容量動態調整,對於接收緩衝區的內存分配器,Netty 提供了兩種:

  1. FixedRecvByteBufAllocator:固定長度的接收緩衝區分配器,由它分配的 ByteBuf 長度都是固定大小的,並不會根據實際數據報的大小動態收縮。可是,若是容量不足,支持動態擴展。動態擴展是 Netty ByteBuf 的一項基本功能,與 ByteBuf 分配器的實現沒有關係;
  2. AdaptiveRecvByteBufAllocator:容量動態調整的接收緩衝區分配器,它會根據以前 Channel 接收到的數據報大小進行計算,若是連續填充滿接收緩衝區的可寫空間,則動態擴展容量。若是連續 2 次接收到的數據報都小於指定值,則收縮當前的容量,以節約內存。

相對於 FixedRecvByteBufAllocator,使用 AdaptiveRecvByteBufAllocator 更爲合理,能夠在建立客戶端或者服務端的時候指定 RecvByteBufAllocator,代碼以下:

Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)

若是默認沒有設置,則使用 AdaptiveRecvByteBufAllocator。

另外值得注意的是,不管是接收緩衝區仍是發送緩衝區,緩衝區的大小建議設置爲消息的平均大小,不要設置成最大消息的上限,這會致使額外的內存浪費。經過以下方式能夠設置接收緩衝區的初始大小:

/**
	 * Creates a new predictor with the specified parameters.
	 * 
	 * @param minimum
	 *            the inclusive lower bound of the expected buffer size
	 * @param initial
	 *            the initial buffer size when no feed back was received
	 * @param maximum
	 *            the inclusive upper bound of the expected buffer size
	 */
	public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum)

對於消息發送,一般須要用戶本身構造 ByteBuf 並編碼,例如經過以下工具類建立消息發送緩衝區:

img

圖 3-2 構造指定容量的緩衝區

3.5. 內存池

推送服務器承載了海量的長連接,每一個長連接實際就是一個會話。若是每一個會話都持有心跳數據、接收緩衝區、指令集等數據結構,並且這些實例隨着消息的處理朝生夕滅,這就會給服務器帶來沉重的 GC 壓力,同時消耗大量的內存。

最有效的解決策略就是使用內存池,每一個 NioEventLoop 線程處理 N 個鏈路,在線程內部,鏈路的處理時串行的。假如 A 鏈路首先被處理,它會建立接收緩衝區等對象,待解碼完成以後,構造的 POJO 對象被封裝成 Task 後投遞到後臺的線程池中執行,而後接收緩衝區會被釋放,每條消息的接收和處理都會重複接收緩衝區的建立和釋放。若是使用內存池,則當 A 鏈路接收到新的數據報以後,從 NioEventLoop 的內存池中申請空閒的 ByteBuf,解碼完成以後,調用 release 將 ByteBuf 釋放到內存池中,供後續 B 鏈路繼續使用。

使用內存池優化以後,單個 NioEventLoop 的 ByteBuf 申請和 GC 次數從原來的 N = 1000000/64 = 15625 次減小爲最少 0 次(假設每次申請都有可用的內存)。

下面咱們以推特使用 Netty4 的 PooledByteBufAllocator 進行 GC 優化做爲案例,對內存池的效果進行評估,結果以下:

垃圾生成速度是原來的 1/5,而垃圾清理速度快了 5 倍。使用新的內存池機制,幾乎能夠把網絡帶寬壓滿。

Netty4 以前的版本問題以下:每當收到新信息或者用戶發送信息到遠程端,Netty 3 均會建立一個新的堆緩衝區。這意味着,對應每個新的緩衝區,都會有一個 new byte[capacity]。這些緩衝區會致使 GC 壓力,並消耗內存帶寬。爲了安全起見,新的字節數組分配時會用零填充,這會消耗內存帶寬。然而,用零填充的數組極可能會再次用實際的數據填充,這又會消耗一樣的內存帶寬。若是 Java 虛擬機(JVM)提供了建立新字節數組而又無需用零填充的方式,那麼咱們原本就能夠將內存帶寬消耗減小 50%,可是目前沒有那樣一種方式。

在 Netty 4 中實現了一個新的 ByteBuf 內存池,它是一個純 Java 版本的 jemalloc (Facebook 也在用)。如今,Netty 不會再由於用零填充緩衝區而浪費內存帶寬了。不過,因爲它不依賴於 GC,開發人員須要當心內存泄漏。若是忘記在處理程序中釋放緩衝區,那麼內存使用率會無限地增加。

Netty 默認不使用內存池,須要在建立客戶端或者服務端的時候進行指定,代碼以下:

Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)

使用內存池以後,內存的申請和釋放必須成對出現,即 retain() 和 release() 要成對出現,不然會致使內存泄露。

值得注意的是,若是使用內存池,完成 ByteBuf 的解碼工做以後必須顯式的調用 ReferenceCountUtil.release(msg) 對接收緩衝區 ByteBuf 進行內存釋放,不然它會被認爲仍然在使用中,這樣會致使內存泄露。

3.6. 小心「日誌隱形殺手」

一般狀況下,你們都知道不能在 Netty 的 I/O 線程上作執行時間不可控的操做,例如訪問數據庫、發送 Email 等。可是有個經常使用可是很是危險的操做卻容易被忽略,那即是記錄日誌。

一般,在生產環境中,須要實時打印接口日誌,其它日誌處於 ERROR 級別,當推送服務發生 I/O 異常以後,會記錄異常日誌。若是當前磁盤的 WIO 比較高,可能會發生寫日誌文件操做被同步阻塞,阻塞時間沒法預測。這就會致使 Netty 的 NioEventLoop 線程被阻塞,Socket 鏈路沒法被及時關閉、其它的鏈路也沒法進行讀寫操做等。

以最經常使用的 log4j 爲例,儘管它支持異步寫日誌(AsyncAppender),可是當日志隊列滿以後,它會同步阻塞業務線程,直到日誌隊列有空閒位置可用,相關代碼以下:

synchronized (this.buffer) {
      while (true) {
        int previousSize = this.buffer.size();
        if (previousSize < this.bufferSize) {
          this.buffer.add(event);
          if (previousSize != 0) break;
          this.buffer.notifyAll(); break;
        }
        boolean discard = true;
        if ((this.blocking) && (!Thread.interrupted()) && (Thread.currentThread() != this.dispatcher)) // 判斷是業務線程 
        {
          try
          {
            this.buffer.wait();// 阻塞業務線程 
            discard = false;
          }
          catch (InterruptedException e)
          {
            Thread.currentThread().interrupt();
          }

        }

相似這類 BUG 具備極強的隱蔽性,每每 WIO 高的時間持續很是短,或者是偶現的,在測試環境中很難模擬此類故障,問題定位難度很是大。這就要求讀者在平時寫代碼的時候必定要小心,注意那些隱性地雷。

3.7. TCP 參數優化

經常使用的 TCP 參數,例如 TCP 層面的接收和發送緩衝區大小設置,在 Netty 中分別對應 ChannelOption 的 SO_SNDBUF 和 SO_RCVBUF,須要根據推送消息的大小,合理設置,對於海量長鏈接,一般 32K 是個不錯的選擇。

另一個比較經常使用的優化手段就是軟中斷,如圖所示:若是全部的軟中斷都運行在 CPU0 相應網卡的硬件中斷上,那麼始終都是 cpu0 在處理軟中斷,而此時其它 CPU 資源就被浪費了,由於沒法並行的執行多個軟中斷。

img

圖 3-3 中斷信息

大於等於 2.6.35 版本的 Linux kernel 內核,開啓 RPS,網絡通訊性能提高 20% 之上。RPS 的基本原理:根據數據包的源地址,目的地址以及目的和源端口,計算出一個 hash 值,而後根據這個 hash 值來選擇軟中斷運行的 cpu。從上層來看,也就是說將每一個鏈接和 cpu 綁定,並經過這個 hash 值,來均衡軟中斷運行在多個 cpu 上,從而提高通訊性能。

3.8. JVM 參數

最重要的參數調整有兩個:

  • -Xmx:JVM 最大內存須要根據內存模型進行計算並得出相對合理的值;
  • GC 相關的參數: 例如新生代和老生代、永久代的比例,GC 的策略,新生代各區的比例等,須要根據具體的場景進行設置和測試,並不斷的優化,儘可能將 Full GC 的頻率降到最低。

來源:https://www.infoq.cn/article/netty-million-level-push-service-design-points/ 歡迎關注公衆號 【碼農開花】一塊兒學習成長 我會一直分享Java乾貨,也會分享免費的學習資料課程和麪試寶典 回覆:【計算機】【設計模式】【面試】有驚喜哦

相關文章
相關標籤/搜索