Netty的線程模型, 調優 及 獻上寫過注釋的源碼工程


Netty能干什么?

Http服務器

使用Netty可以編寫一個 Http服務器, 就像tomcat那樣,能接受用戶發送的http請求, , 只不過沒有實現Servelt規范, 但是它也能解析攜帶的參數, 對請求的路徑進行路由導航, 從而實現將不同的請求導向不同的handler進行處理

對socket與RPC的支持

Netty可是實現的第二件事就是Socket編程,也是它最為廣泛的應用領域

進行微服務開發時不可丟棄的一個點, 服務和服務之間如果使用Http通信不是不行,但是http的底層使用的也是socket, 相對我們直接使用netty加持socket效果會更好 (比如阿里的Dubbo)

當然Netty能做的事還有很多比如自定義通信協議等等,,,

對WebSocket的支持

Netty對WebSocket也提供了強大的支持, netty內置的處理器會為我們做好大量的機械性麻煩性的工作, 如WebSocketServerProtocolHandler內置編解碼處理, 心跳檢驗等, 可以讓我們專注於實現自己的業務邏輯

Reactor線程模型

Reactor線程模型, 顧名思義就像核反應堆一樣, 是一種很勁爆的線程模型

最經典的兩種圖就像下面這樣

reactor線程模型3

上面兩種圖所描述的都是Reactor線程模型

Reactor線程模型五大角色

  • handle

handle本質上表示是一種資源 , 在不同的操作系統上他們的名稱又不一樣, 比如在windows上稱它為文件句柄 , 而在linux中稱它為文件描述符, 其實我這樣說, handle的概念就顯得比較抽象 不容易理解 , 具體一點說handle是啥呢 ? 比如客戶端向服務端發送一個連接請求,這個socket請求對操作系統來說, 本質上就是handle

在Reactor線程模型中的Handle 就是限制netty並發量的主要原因, 下面的調優主要也是為了突破這個限制

  • Synchronius Event Demultiplexer

意為同步事件分離器, 也是一看這個名字完全沒有頭緒它是什么, 其實, 在本質上它是一個操作系統層面的系統調用, 操作系統用它來阻塞的等待事件的發生, 具體一點它來說, 比如它可以是Linux系統上的IO多路復用, select(), 或者是 poll(),epoll() , 或者是Nio編程模型中的selector, 總之, 它的特點就是阻塞的等待事件的發生

  • EventHandler

事件處理器, 事件處理器是擁有Handle的, 我們可以直觀將EventHandler理解成是當系統中發生了某個事件后, 針對這個事件進行處理的回調, 為啥說是回調, 結合netty的實現中, 我們在啟動netty前, 會往他的pipeline中添加大量的handler ,這些handler的地位其實和 EventHandler的地位相當

  • **concrete EventHandler **

顧明思議, 具體的事件處理器的實現, 換句話說, 這是我們根據自己的需求, 不同的業務邏輯自己去添加上去的處理器

  • InitiationDispacher

初始分發器, 它其實就是整個編程模型的核心, 沒錯, 他就是Reactor, 具體怎么理解這個Reactor , 比如我們就能把他看成一個規范, 由它聚合, 支配其他的四大角色之間有條不紊的工作, 迸發出巨大的能量

Reactor線程模型五大角色的關系與工作流程

首先: 我們需要將 EventHandler注冊進 Reactor, 通過上圖也能看到, EventHandler 擁有 操作系統底層的 Handle,

並且, Reactor 要求, 當EventHandler 注冊進自己時, 務必將他關聯的handle一並告訴自己, 由Reactor統一進行維護

我們將上面所說的handle , 聯想成Nio編程模型中的將channel注冊進Selector,也是必須綁定上的這個感性趣的事件, 牢記, Reactor是的這個模型的核心, 待會操作一旦系統發現handle有了某些變動,需要由Reactor去通知具體哪個EventHandler來處理這個資源. 這也是Reactor如何找到正確的EventHandler依賴的就是這個handle , 或者說它是感興趣的事件

更近一步: 當所有的EventHandler都注冊進了Reactor中后, Reactor就開始了它放縱的一生, 於是它開始調與 同步事件分離器通信 ,企圖等待一些事件的發生, 什么事件呢? 比如說 socket的連接事件

當同步事件分離器發現了某個handle的狀態發生了改變, 比如它的狀態變成了ready, 就說明這個handle中發生了感興趣的事件, 這時, 同步事件分離器會將這個handle的情況告訴Reactor , Reactor進一步用這個handle當成key, 獲取出相對應的 EventHandler 開始方法的回調...

如何實現單機百萬性能調優

​ 當我們進行socket編程時, 我們得給Server端綁定上一個端口號, 客戶端一般會被自動分配Server所在的機器上的一個端口號, 區間一般是1025-65535之間, 這樣看上去, 即使服務器的性能再強, 即使netty再快, 並發數目都被操作系統的特性限制的死死的

突破局部文件句柄的限制

​ 像 windows中的句柄或者是linux的文件描述符 這種能打開的資源的數量是有限的, 每一個socket連接都是一個句柄或者是描述符, 換句話說, 這一個特性限制了socket連接數, 也就限制了並發數

查看linux系統中一個進程能打開的單個文件數,(它限制了單個jvm能打開的文件描述符數,每一個tcp連接對linux來說都是一個文件)

ulimit -n

修改這個限制:

# 在 /etc/security/limits.conf 追加以下內容 , 表示任何用戶的鏈接最多都能打開100萬個文件
* hard nofile 100000
* soft nofile 100000

重啟機器生效:

突破全局文件句柄的限制

查看當前系統中的全局文件句柄數

cat /proc/sys/fs/file-max

修改這個配置

# 在 /etc/sysctl.conf 中追加如下的內容
fs.file-max = 1000000

虛擬機參數的經驗值

# 堆內存
-Xms6.5g -Xmx6.5g 
# 新生代的內存
-XX:NewSize=5.5g  -XXMaxNewSize=5.5g
# 對外內存
-XX: MaxDirectMemorySize=1g

應用級別的性能調優

問題:

​ Netty基於Reactor這種線程模型的, 進行非阻塞式編程, 一般我們在編寫服務端的代碼時, 都會在 往 服務端的Channel pipeline上添加大量的 入站出站處理器, 客戶端的消息一般我們都是在 handler中的 ChannelRead() 或者是 ChannelRead0() 中取出來, 再和后台的業務邏輯結合

客戶端的消息,會從Pipeline這個雙向鏈表中的header中開始往后傳播, 一直到tail, 這其實是個責任鏈

這時, 如果我們將耗時的操作放在這些處理器中, 毫無疑問, nettey會被拖垮, 系統的並發量也提升不上去

解決方式一:

新開辟一個線程池 , 將耗時的業務邏輯放到新開辟的業務去執行

public class MyThreadPoolHandler extends SimpleChannelInboundHandler<String> {

    private static ExecutorService threadPool = Executors.newFixedThreadPool(20);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        threadPool.submit(() -> {
            Object result = searchFromMysql(msg);
            ctx.writeAndFlush(ctx);
        });
    }

    public Object searchFromMysql(String msg) {
        Object obj = null;
        // todo  obj = 
        return obj;
    }
}

解決方式二:

netty 提供的一種原生的解決方式, netty可以將我們的handler 放到一個專門的線程池中去處理


public class MyInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        // 業務線程池
        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(20);
        // 使用這個重載的方法,給handler指定線程池去執行
        pipeline.addLast(eventLoopGroup,new MyHandler());
    }
}

Netty帶給我的收獲

能立即成為Netty的領域的大佬嗎?

No!

讀過Netty的代碼,真的並不意味着即刻成為一個netty領域的大佬,相反讀的我發慌, 這么龐大的知識體系, 得用多久去慢慢消化, 我們看到的大佬要么是天才, 要么是在自己多年的使用經驗里面歷練出來的, 想在netty這個領域有所建樹,真的是需要不斷是去實踐,真的得用多年的工作經驗去總結才行!也許這樣才可能發自內心的感觸到netty為什么要這么實現, 如果想着立即多厲害, 也許是做夢了...

真的熟練掌握了Netty嗎?

No!

讀過netty, 也並不是意味着可以了解netty整個領域的方方面面, 以及如何恰當的使用netty去解決我們的業務需求, 站在一個學生的角度來看, 這一點我心里感覺的特別深刻, 同樣的東西,比如同一個ArrayList, 大家都在用, 但是對它的掌握程度的差距甚至能達到使人瞠目結舌的地步, 不得不承認, 技術是需要時間去積累, 需要時間去消化的東西,就像塵封的酒,越釀越香

有什么收獲?

​ **當然,讀源碼也不是沒有用,起碼對netty框架整體的架構不是那么是陌生了,當自己遇到bug時,也敢於點開源碼去找原因了,這應該是我最大的收獲 **

獻上注解過的Netty源碼工程

​ 當時我在學netty時, 是從github上拉取的netty原生工程, 然后在本地重新編譯運行, 這樣我就能在源碼工程中寫注解, 記筆記...

todo

​ 在這個工程里面大概寫了 1400 多行注釋(所有的筆記都打上了todo 高亮標記) , 也翻譯了一些類和方法上的文檔和注釋, 不能說百分百正確, 但是這個過程也是挺走心的, 比如netty是如何解決JDK空輪詢的bug的? 這些在代碼中都是有跡可循的,如下

空輪詢bug

另外一個學習的感觸就是忘東西, 還有就是隨着時間的推移, 接觸到技術會越來越多, 確實很難在同一個時間將學的所有的技術都提升到最高的熟練程度, 好處也有, 可能原來學用了一個星期, 現在重新看只要1天就夠了

github地址: https://github.com/zhuchangwu/netty-project

我更推薦你也這樣自己編譯一個使用,學習netty是一個漫長的過程, 如果你也有一腔熱血 , 可以聯系我哦...

致敬真神: Netty的創始人trustin lee 和 Netty的開發者們


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM