<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    瘋狂

    STANDING ON THE SHOULDERS OF GIANTS
    posts - 481, comments - 486, trackbacks - 0, articles - 1
      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

    Netty百萬級推送服務(轉)

    Posted on 2016-07-13 10:03 瘋狂 閱讀(5163) 評論(0)  編輯  收藏 所屬分類: netty
    轉載自:http://www.open-open.com/lib/view/open1420623195375.html

    1. 背景

    1.1. 話題來源

    最近很多從事移動互聯網和物聯網開發的同學給我發郵件或者微博私信我,咨詢推送服務相關的問題。問題五花八門,在幫助大家答疑解惑的過程中,我也對問題進行了總結,大概可以歸納為如下幾類:

    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內存堆棧,對疑似內存泄露的對象和引用關系進行分析,如下所示:

    Netty百萬級推送服務

    我們發現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連接狀態遷移圖如下所示:

    Netty百萬級推送服務

    圖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并編碼,例如通過如下工具類創建消息發送緩沖區:

    Netty百萬級推送服務

    圖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資源就被浪費了,因為無法并行的執行多個軟中斷。

    Netty百萬級推送服務

    圖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的頻率降到最低。

    4. 作者簡介

    李林鋒,2007年畢業于東北大學,2008年進入華為公司從事高性能通信軟件的設計和開發工作,有6年NIO設計和開發經驗,精通Netty、Mina等NIO框架。Netty中國社區創始人,《Netty權威指南》作者。


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 亚洲AV成人精品一区二区三区| 四虎国产精品永久免费网址| 久久久久久亚洲av无码蜜芽| 一本到卡二卡三卡免费高| 日韩视频在线观看免费| 久久电影网午夜鲁丝片免费| 亚洲中文字幕无码爆乳av中文| 国产亚洲精品岁国产微拍精品| 亚洲国产av美女网站| eeuss影院ss奇兵免费com| 可以免费看黄的网站| 国产国拍精品亚洲AV片| 亚洲综合精品第一页| 午夜亚洲福利在线老司机| 亚洲视频在线播放| 美国毛片亚洲社区在线观看 | 麻豆精品成人免费国产片| 一级午夜a毛片免费视频| 国产香蕉九九久久精品免费| 国产亚洲人成网站观看| 日本高清免费中文在线看| 国产成人免费高清激情视频| 亚洲av无码不卡一区二区三区| 免费一级毛suv好看的国产网站 | 麻豆最新国产剧情AV原创免费| 亚洲午夜在线电影| 久久99精品视免费看| 亚洲Av永久无码精品三区在线| 草久免费在线观看网站| 亚洲熟妇少妇任你躁在线观看无码| 色噜噜噜噜亚洲第一| 又大又粗又爽a级毛片免费看| 亚洲成av人片在线天堂无| 在线A级毛片无码免费真人| 亚洲无码一区二区三区| 国产精品视频免费一区二区三区 | 日韩精品内射视频免费观看 | 亚洲国产成人无码av在线播放 | 精品无码国产污污污免费网站国产| 国产一区视频在线免费观看| 婷婷国产偷v国产偷v亚洲|