一
由於本人的碼雲太多太亂了,於是決定一個一個的整合到一個springboot項目里面。
附上自己的github項目地址 https://github.com/247292980/spring-boot
附上匯總博文地址 https://www.cnblogs.com/ydymz/p/9391653.html
以整合功能
spring-boot,FusionChart,thymeleaf,vue,ShardingJdbc,mybatis-generator,微信分享授權,drools,spring-security,spring-jpa,webjars,Aspect,drools-drt,rabbitmq,zookeeper,mongodb,mysql存儲過程,前端的延遲加載,netty,postgresql,樹的遍歷
這次再次說下netty,為什么在學netty呢?因為面試一家大公司的時候,被問倒了,,,所以只好耐下心來看原理了。
目前看了兩本netty的書(市面上就兩本 orz)第一感覺就是netty的書怎么說呢,就是demo集合,某程度還是挺好看懂的,但是過時啊。
不過參考之前寫drools的痛苦經驗,國內大部分環境應該也是用過時的netty,不信你去技術群里問下新版netty的特性,大家一定會勸你去踩坑...
而這篇文章也不介紹新特性,只是對netty的原理研究。說實話就是丟圖,,,
二 模型
說到netty一定要知道他的基本模型。
1.先說下之前的bio-同步阻塞io
顯然有點網絡基礎的人都寫過類似的代碼。優缺點很明顯
缺點:性能瓶頸。每個鏈接一個線程,怎么保證線程的即時回收和可靠回收是一大技術問題。而該模型在遇到大量線程的時候(超過某個閾值,例如java默認的線程上限或者linux的默認線程上限之類的)性能指數性下降啊。
優點:簡單。所以我才說有點網絡基礎的人都寫過類似的代碼,,,除非直接起手springboot的
2.后面就是nio-同步非阻塞io
acceptor收到所有的連接請求
acceptor上注冊了selector,
服務器構建對應的Channel,並在其上注冊Selector
selector分配請求到不同的channel,進行相應的讀寫處理。
ps.寫完發現可能有點難理解,具體代碼項目里面有。
其實就是selector分配請求,channel處理請求並能通過selector得到client,等處理完成后直接返回client,不走selector。
優點:一個線程處理全部連接,不阻塞后面的連接。性能大提升!
缺點:模型復雜,代碼復雜(至少我覺得不看一下代碼,很難說清);處理半包問題
ps.半包問題
我們知道TCP/IP在發送消息的時候,可能會拆包。這就導致接收端無法知道什么時候收到的數據是一個完整的數據。
例如:發送端分別發送了ABC,DEF,GHI三條信息,發送時被拆成了AB,CDRFG,H,I這四個包進行發送,接受端如何將其進行還原呢?在BIO模型中,當讀不到數據后會阻塞,而NIO中不會!所以需要自行進行處理!說到底就是自定義網絡協議,額其實這個有點基礎的都寫過,我也不細說了。
3.所以為了解決這問題,netty就使用了Reactor模型-bio的變種
具體來說這是reactor的單線程模型,可以看出和bio基本沒什么差別,就是把channel的數據處理提到handler,並且統一在reactor注冊了,而不用去acceptor,channel分別注冊在兩個地方,統一處理了。
但是這玩意有個問題就是,一個client多次請求,handler中的處理特別慢,那么后續的請求就會被積壓。
4.Reactor多線程模型
reactor將接受發送分離,client發送的請求丟到線程池中,所以后續請求不會被阻塞。
但是當用戶進一步增加的時候,Reactor會出現瓶頸!因為Reactor既要處理IO操作請求,又要響應連接請求!為了分擔Reactor的負擔,所以引入了主從Reactor模型!
上面的這一句話是書上原話,其實我是不太贊成的,因為說到底就是一個線程的事,所以沒可能是性能瓶頸,而在我看來netty使用主從reactor的主要原因是代碼可讀性和易於理解。
5.Reactor主從模型
簡單明了
client發送請求
acceptor就是個死循環監聽
mainreactor分配acceptor監聽到的請求到channel
channel通過subreactor將請求發到handler處理,然后直接返回給client
ps.注意的是mainreactor一定是單線程,多線程可能導致為client分配channel的時候,一個線程分配了一個,浪費資源;也存在一個client的請求去了兩線程,而其中一個處理很久,另一個則早早完成,而此時client的等待時間是取最大等待時間的,那么早早完成的還不如一起在另一個處理很久的線程里面;
主從模型最精彩的地方在於,主reactor和從reactor的分離,可以讓住reactor有更多的花樣,如驗權授權等,更加便於大數據手機分析
三 術語
bootstrap: Netty 通過設置 bootstrap(引導)類的開始,該類提供了一個應用程序網絡層配置的容器
Channel: 是一個客戶端用來進行連接的 Channel,記錄了client信息,用於io操作的交互
ChannelHandler: 數據處理的容器
ChannelPipeline:提供了一個容器給 ChannelHandler 鏈並提供了一個API 用於管理沿着鏈入站和出站事件的流動
EventLoop: EventLoop 用於處理 Channel 的 I/O 操作。一個單一的 EventLoop通常會處理多個 Channel 事件
EventLoopGroup:可以含有多於一個的 EventLoop 和 提供了一種迭代用於檢索清單中的下一個
ChannelFuture: ChannelFuture是netty的一個回調不管是否成功返回
ChannelFutureListener:ChannelFuture通過addListener 注冊。當操作完成時,可以被通知(不管成功與否)
ps.ChannelFutureListener加上了之后就可以說是實現異步。
四 channel
通常來說, 所有的 netty 的 I/O 操作都是從 Channel 開始的. 一個 channel 類似於一個 stream.
Stream 和 Channel 對比
我們可以在同一個 Channel 中執行讀和寫操作, 然而同一個 Stream 僅僅支持讀或寫.
Channel 可以異步地讀寫, 而 Stream 是阻塞的同步讀寫.
Channel 總是從 Buffer 中讀取數據, 或將數據寫入到 Buffer 中.
Channel 類型有:
FileChannel, 文件操作
DatagramChannel, UDP 操作
SocketChannel, TCP 操作
ServerSocketChannel, TCP 操作, 使用在服務器端.
五 Buffer
與 Channel 進行交互時, 我們就需要使用到 Buffer, 即數據從 Buffer讀取到 Channel 中, 並且從 Channel 中寫入到 Buffer 中.
Buffer 其實就是一塊內存區域, 並提供了一些操作方法讓我們能夠方便地進行數據的讀寫.
Buffer 類型有:
ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer(就是基本類型啊)
六 Selector
Selector 允許一個單一的線程來操作多個 Channel. 如果我們的應用程序中使用了多個 Channel, 那么使用 Selector 很方便的實現這樣的目的, 但是因為在一個線程中使用了多個 Channel, 因此也會造成了每個 Channel 傳輸效率的降低.
為了使用 Selector, 我們首先需要將 Channel 注冊到 Selector 中, 隨后調用 Selector 的 select()方法, 這個方法會阻塞, 直到注冊在 Selector 中的 Channel 發送可讀寫事件. 當這個方法返回后, 當前的這個線程就可以處理 Channel 的事件了.
七 Bootstrap
Bootstrap 是 Netty 提供的一個便利的工廠類, 我們可以通過它來完成 Netty 的客戶端或服務器端的 Netty 初始化.
八 ChannelPipeline
一個 Channel 包含了一個 ChannelPipeline, 而 ChannelPipeline 中又維護了一個由 ChannelHandlerContext 組成的雙向鏈表