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線程模型
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原生工程, 然后在本地重新編譯運行, 這樣我就能在源碼工程中寫注解, 記筆記...
在這個工程里面大概寫了 1400 多行注釋(所有的筆記都打上了todo 高亮標記) , 也翻譯了一些類和方法上的文檔和注釋, 不能說百分百正確, 但是這個過程也是挺走心的, 比如netty是如何解決JDK空輪詢的bug的? 這些在代碼中都是有跡可循的,如下
另外一個學習的感觸就是忘東西, 還有就是隨着時間的推移, 接觸到技術會越來越多, 確實很難在同一個時間將學的所有的技術都提升到最高的熟練程度, 好處也有, 可能原來學用了一個星期, 現在重新看只要1天就夠了
github地址: https://github.com/zhuchangwu/netty-project
我更推薦你也這樣自己編譯一個使用,學習netty是一個漫長的過程, 如果你也有一腔熱血 , 可以聯系我哦...
致敬真神: Netty的創始人trustin lee 和 Netty的開發者們