首頁  >  文章  >  Java  >  Netty是什麼? Netty相關知識的深入解析

Netty是什麼? Netty相關知識的深入解析

不言
不言轉載
2018-11-24 16:22:323153瀏覽

這篇文章帶給大家的內容是關於Netty是什麼? Netty相關知識的深入解析,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

Netty到底是什麼

從HTTP說起

有了Netty,你可以實作自己的HTTP伺服器,FTP伺服器,UDP伺服器, RPC伺服器,WebSocket伺服器,Redis的Proxy伺服器,MySQL的Proxy伺服器等等。

我們回顧傳統的HTTP伺服器的原理

1、建立一個ServerSocket,監聽並綁定一個連接埠

2、一系列客戶端來請求這個連接埠

3、伺服器使用Accept,取得一個來自客戶端的Socket連線物件

4、啟動一個新執行緒處理連線

4.1、讀取Socket,得到位元組流

4.2、解碼協議,得到Http請求物件

4.3、處理Http請求,得到一個結果,封裝成一個HttpResponse物件

4.4、編碼協議,將結果序列化字節流寫Socket,將位元組流發給客戶端

5、繼續循環步驟3

HTTP伺服器之所以稱為HTTP伺服器,是因為編碼解碼協定是HTTP協議,如果協定是Redis協議,那它就變成了Redis伺服器,如果協定是WebSocket,那它就變成了WebSocket伺服器,等等。使用Netty你就可以自訂編解碼協議,實作自己的特定協議的伺服器。

NIO

上面有傳統處理http的伺服器,但在高並發的環境下,執行緒數量會比較多,System load也會比較高,所以就有了NIO。

他不是Java獨有的概念,NIO代表的一個詞彙叫著IO多路復用。它是由作業系統提供的系統調用,早期這個作業系統調用的名字是select,但是效能低下,後來漸漸演化成了Linux下的epoll和Mac裡的kqueue。我們通常會說是epoll,因為沒有人拿蘋果電腦當伺服器來使用對外提供服務。而Netty就是基於Java NIO技術封裝的一套架構。為什麼要封裝,因為原生的Java NIO使用起來沒那麼方便,而且還有臭名昭著的bug,Netty把它封裝之後,提供了一個易於操作的使用模式和接口,用戶使用起來也就便捷多了。

說NIO之前先說一下BIO(Blocking IO),如何理解這個Blocking呢?

客戶端監聽(Listen)時,Accept是阻塞的,只有新連線來了,Accept才會回,主執行緒才能繼

讀寫socket時,Read是阻塞的,只有請求訊息來了,Read才能返回,子執行緒才能繼續處理

讀寫socket時,Write是阻塞的,只有客戶端把訊息收了,Write才能返回,子執行緒才能繼續讀取下一個請求

傳統的BIO模式下,從頭到尾的所有執行緒都是阻塞的,這些執行緒就乾等著,佔用系統的資源,什麼事也不幹。

那麼NIO是怎麼做到非阻塞的呢。它用的是事件機制。它可以用一個執行緒把Accept,讀寫操作,請求處理的邏輯全乾了。如果什麼事都沒得做,它也不會死循環,它會將線程休眠起來,直到下一個事件來了再繼續幹活,這樣的一個線程稱之為NIO線程。用偽代碼表示:

while true {

    events = takeEvents(fds)  // 获取事件,如果没有事件,线程就休眠

    for event in events {        if event.isAcceptable {

            doAccept() // 新链接来了
        } elif event.isReadable {

            request = doRead() // 读消息

            if request.isComplete() {

                doProcess()

            }

        } elif event.isWriteable {

            doWrite()  // 写消息
        }

    }

}

Reactor執行緒模型

Reactor單執行緒模型

一個NIO執行緒一個accept執行緒:

Netty是什麼? Netty相關知識的深入解析

#Reactor多執行緒模型

Netty是什麼? Netty相關知識的深入解析

Reactor主從模型

主從Reactor多執行緒:多個acceptor的NIO執行緒池用於接受客戶端的連線

Netty是什麼? Netty相關知識的深入解析

Netty可以基於如上三種模型進行靈活的配置。

總結

Netty是建立在NIO基礎之上,Netty在NIO之上又提供了更高層次的抽象。

在Netty裡面,Accept連線可以使用單獨的執行緒池去處理,讀寫操作又是另外的執行緒池來處理。

Accept连接和读写操作也可以使用同一个线程池来进行处理。而请求处理逻辑既可以使用单独的线程池进行处理,也可以跟放在读写线程一块处理。线程池中的每一个线程都是NIO线程。用户可以根据实际情况进行组装,构造出满足系统需求的高性能并发模型。

为什么选择Netty

如果不用netty,使用原生JDK的话,有如下问题:

1、API复杂

2、对多线程很熟悉:因为NIO涉及到Reactor模式

3、高可用的话:需要出路断连重连、半包读写、失败缓存等问题

4、JDK NIO的bug

而Netty来说,他的api简单、性能高而且社区活跃(dubbo、rocketmq等都使用了它)

什么是TCP 粘包/拆包

现象

先看如下代码,这个代码是使用netty在client端重复写100次数据给server端,ByteBuf是netty的一个字节容器,里面存放是的需要发送的数据

public class FirstClientHandler extends ChannelInboundHandlerAdapter { 
 @Override 
 public void channelActive(ChannelHandlerContext ctx) { 
 for (int i = 0; i < 1000; i++) { 
 ByteBuf buffer = getByteBuf(ctx); 
 ctx.channel().writeAndFlush(buffer); 
 } 
 } 
 private ByteBuf getByteBuf(ChannelHandlerContext ctx) { 
 byte[] bytes = "需要更多资料加群:586446657".getBytes(Charset.forName("utf-8")); 
 ByteBuf buffer = ctx.alloc().buffer(); 
 buffer.writeBytes(bytes); 
 return buffer; 
 }
}

从client端读取到的数据为:

Netty是什麼? Netty相關知識的深入解析

从服务端的控制台输出可以看出,存在三种类型的输出

一种是正常的字符串输出。

一种是多个字符串“粘”在了一起,我们定义这种 ByteBuf 为粘包。

一种是一个字符串被“拆”开,形成一个破碎的包,我们定义这种 ByteBuf 为半包。

透过现象分析原因

应用层面使用了Netty,但是对于操作系统来说,只认TCP协议,尽管我们的应用层是按照 ByteBuf 为 单位来发送数据,server按照Bytebuf读取,但是到了底层操作系统仍然是按照字节流发送数据,因此,数据到了服务端,也是按照字节流的方式读入,然后到了 Netty 应用层面,重新拼装成 ByteBuf,而这里的 ByteBuf 与客户端按顺序发送的 ByteBuf 可能是不对等的。因此,我们需要在客户端根据自定义协议来组装我们应用层的数据包,然后在服务端根据我们的应用层的协议来组装数据包,这个过程通常在服务端称为拆包,而在客户端称为粘包。

拆包和粘包是相对的,一端粘了包,另外一端就需要将粘过的包拆开,发送端将三个数据包粘成两个 TCP 数据包发送到接收端,接收端就需要根据应用协议将两个数据包重新组装成三个数据包。

如何解决

在没有 Netty 的情况下,用户如果自己需要拆包,基本原理就是不断从 TCP 缓冲区中读取数据,每次读取完都需要判断是否是一个完整的数据包 如果当前读取的数据不足以拼接成一个完整的业务数据包,那就保留该数据,继续从 TCP 缓冲区中读取,直到得到一个完整的数据包。 如果当前读到的数据加上已经读取的数据足够拼接成一个数据包,那就将已经读取的数据拼接上本次读取的数据,构成一个完整的业务数据包传递到业务逻辑,多余的数据仍然保留,以便和下次读到的数据尝试拼接。

而在Netty中,已经造好了许多类型的拆包器,我们直接用就好:

Netty是什麼? Netty相關知識的深入解析

选好拆包器后,在代码中client段和server端将拆包器加入到chanelPipeline之中就好了:

如上实例中:

客户端:

ch.pipeline().addLast(new FixedLengthFrameDecoder(31));

服务端:

ch.pipeline().addLast(new FixedLengthFrameDecoder(31));

Netty是什麼? Netty相關知識的深入解析

Netty 的零拷贝

传统意义的拷贝

是在发送数据的时候,传统的实现方式是:

1. `File.read(bytes)`

2. `Socket.send(bytes)`

这种方式需要四次数据拷贝和四次上下文切换:

1. 数据从磁盘读取到内核的read buffer

2. 数据从内核缓冲区拷贝到用户缓冲区

3. 数据从用户缓冲区拷贝到内核的socket buffer

4. 数据从内核的socket buffer拷贝到网卡接口(硬件)的缓冲区

零拷贝的概念

明显上面的第二步和第三步是没有必要的,通过java的FileChannel.transferTo方法,可以避免上面两次多余的拷贝(当然这需要底层操作系统支持)

1. 呼叫transferTo,資料從檔案由DMA引擎拷貝到核心read buffer

2. 接著DMA從核心read buffer將資料拷貝到網卡介面buffer

上面的兩次操作都不需要CPU參與,所以就達到零拷貝了。

Netty中的零拷貝

主要體現在三個方面:

#1、bytebuffer

Netty發送和接收訊息主要使用bytebuffer,bytebuffer使用對外記憶體(DirectMemory)直接進行Socket讀寫。

原因:如果使用傳統的堆疊記憶體進行Socket讀寫,JVM會將堆疊記憶體buffer拷貝一份到直接記憶體中然後再寫入socket,多了一次緩衝區的記憶體拷貝。 DirectMemory中可以直接透過DMA傳送到網卡介面

2、Composite Buffers

傳統的ByteBuffer,如果需要將兩個ByteBuffer中的資料組合在一起,我們需要先建立一個size= size1 size2大小的新的數組,然後將兩個數組中的資料拷貝到新的數組中。但是使用Netty提供的組合ByteBuf,就可以避免這樣的操作,因為CompositeByteBuf並沒有真正將多個Buffer組合起來,而是保存了它們的引用,從而避免了數據的拷貝,實現了零拷貝。

3、對於FileChannel.transferTo的使用

Netty中使用了FileChannel的transferTo方法,該方法依賴作業系統實作零拷貝。

Netty 內部執行流程

服務端:

 Netty是什麼? Netty相關知識的深入解析

Netty是什麼? Netty相關知識的深入解析

1、建立ServerBootStrap實例

2、設定並綁定Reactor執行緒池:EventLoopGroup,EventLoop就是處理所有註冊到本執行緒的Selector上面的Channel

3、設定並綁定服務端的channel

4、5、建立處理網路事件的ChannelPipeline和handler,網路時間以流的形式在其中流轉,handler完成多數的功能自訂:例如編解碼SSl安全認證

6、綁定並啟動監聽埠

7、當輪訓到準備就緒的channel後,由Reactor執行緒:NioEventLoop執行pipline中的方法,最終調度並執行channelHandler 

8、說到這裡順便給大家推薦一個Java的交流學習社區:586446657,裡面不僅可以交流討論,還有面試經驗分享以及免費的資料下載,包括Spring,MyBatis,Netty源碼分析,高並發、高效能、分散式、微服務架構的原理,JVM效能優化這些成為架構師必備的知識體系。相信對於已經工作和遇到技術瓶頸的碼友,在這裡會有你需要的內容。

客戶端

Netty是什麼? Netty相關知識的深入解析

Netty是什麼? Netty相關知識的深入解析

#總結

以上就是我對Netty相關知識整理,如果有不同的見解,歡迎討論!

以上是Netty是什麼? Netty相關知識的深入解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:cnblogs.com。如有侵權,請聯絡admin@php.cn刪除