<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          讓Netty“榨干”你的CPU | 文末送書

          共 9990字,需瀏覽 20分鐘

           ·

          2022-03-09 14:53

          在開始了解Netty是什么之前,我們先來回顧一下,如果需要實(shí)現(xiàn)一個客戶端與服務(wù)端通信的程序,使用傳統(tǒng)的IO編程,應(yīng)該如何來實(shí)現(xiàn)?

          IO編程

          我們簡化一下場景:客戶端每隔兩秒發(fā)送一個帶有時間戳的“hello world”給服務(wù)端,服務(wù)端收到之后打印它。

          在傳統(tǒng)的IO模型中,每個連接創(chuàng)建成功之后都需要由一個線程來維護(hù),每個線程都包含一個while死循環(huán),那么1萬個連接對應(yīng)1萬個線程,繼而有1萬個while死循環(huán),這就帶來如下幾個問題。

          • 線程資源受限:線程是操作系統(tǒng)中非常寶貴的資源,同一時刻有大量的線程處于阻塞狀態(tài),是非常嚴(yán)重的資源浪費(fèi),操作系統(tǒng)耗不起。

          • 線程切換效率低下:單機(jī)CPU核數(shù)固定,線程爆炸之后操作系統(tǒng)頻繁進(jìn)行線程切換,應(yīng)用性能急劇下降。

          • 除了以上兩個問題,在IO編程中,我們看到數(shù)據(jù)讀寫是以字節(jié)流為單位的。

          為了解決這3個問題,JDK在1.4版本之后提出了NIO。

          NIO編程

          在NIO編程模型中,新來一個連接不再創(chuàng)建一個新線程,而是可以把這個連接直接綁定到某個固定的線程,然后這個連接所有的讀寫都由這個線程來負(fù)責(zé),那么它是怎么做到的?我們用下圖來對比一下IO與NIO。


          如上圖所示,在IO模型中,一個連接來了,會創(chuàng)建一個線程,對應(yīng)一個while死循環(huán),死循環(huán)的目的就是不斷監(jiān)測這個連接上是否有數(shù)據(jù)可以讀。在大多數(shù)情況下,1萬個連接里面同一時刻只有少量的連接有數(shù)據(jù)可讀,因此,很多while死循環(huán)都白白浪費(fèi)掉了,因?yàn)樽x不出數(shù)據(jù)。

          而在NIO模型中,這么多while死循環(huán)轉(zhuǎn)換為一個死循環(huán),這個死循環(huán)由一個線程控制,那么NIO又是如何做到一個線程一個while死循環(huán)就能監(jiān)測1萬個連接是否有數(shù)據(jù)可讀的呢?

          這就是NIO模型中Selector的作用,一個連接來了之后,不會創(chuàng)建一個while死循環(huán)去監(jiān)聽是否有數(shù)據(jù)可讀,而是直接把這條連接注冊到Selector上。然后,通過檢查這個Selector,就可以批量監(jiān)測出有數(shù)據(jù)可讀的連接,進(jìn)而讀取數(shù)據(jù)。下面我們舉一個生活中非常簡單的例子來說明IO與NIO的區(qū)別。

          在一家幼兒園里,小朋友有上廁所的需求,小朋友都太小以至于你要問他要不要上廁所,他才會告訴你。幼兒園一共有100個小朋友,有兩種方案可以解決小朋友上廁所的問題。

          1.每個小朋友都配一個老師。每個老師都隔段時間詢問小朋友是否要上廁所。如果要上,就領(lǐng)他去廁所,100個小朋友就需要100個老師來詢問,并且每個小朋友上廁所的時候都需要一個老師領(lǐng)著他去,這就是IO模型,一個連接對應(yīng)一個線程。

          2.所有的小朋友都配同一個老師。這個老師隔段時間詢問所有的小朋友是否有人要上廁所,然后每一時刻把所有要上廁所的小朋友批量領(lǐng)到廁所,這就是NIO模型。所有小朋友都注冊到同一個老師,對應(yīng)的就是所有的連接都注冊到同一個線程,然后批量輪詢。

          這就是NIO模型解決線程資源受限問題的方案。在實(shí)際開發(fā)過程中,我們會開多個線程,每個線程都管理著一批連接,相對于IO模型中一個線程管理一個連接,消耗的線程資源大幅減少。

          由于NIO模型中線程數(shù)量大大降低,因此線程切換效率也大幅度提高。

          IO讀寫是面向流的,一次性只能從流中讀取一字節(jié)或者多字節(jié),并且讀完之后流無法再讀取,需要自己緩存數(shù)據(jù)。而NIO的讀寫是面向Buffer的,可以隨意讀取里面任何字節(jié)數(shù)據(jù),不需要自己緩存數(shù)據(jù),只需要移動讀寫指針即可。

          簡單講完了JDK NIO的解決方案之后,接下來我們使用NIO方案替換掉IO方案。先來看看,如果用JDK原生的NIO來實(shí)現(xiàn)服務(wù)端,該怎么做。

          前方高能預(yù)警:以下代碼可能會讓你感覺極度不適,如有不適,請?zhí)^。

          NIOServer.java

          /** * @author 閃電俠 */public class NIOServer {    public static void main(String[] args) throws IOException {        Selector serverSelector = Selector.open();        Selector clientSelector = Selector.open();
          new Thread(() -> { try { // 對應(yīng)IO編程中的服務(wù)端啟動 ServerSocketChannel listenerChannel = ServerSocketChannel.open(); listenerChannel.socket().bind(new InetSocketAddress(8000)); listenerChannel.configureBlocking(false); listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
          while (true) { // 監(jiān)測是否有新連接,這里的1指阻塞的時間為 1ms if (serverSelector.select(1) > 0) { Set<SelectionKey> set = serverSelector.selectedKeys(); Iterator<SelectionKey> keyIterator = set.iterator();
          while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next();
          if (key.isAcceptable()) { try { // (1)每來一個新連接,不需要創(chuàng)建一個線程,而是直接注冊到clientSelector SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); clientChannel.configureBlocking(false); clientChannel.register(clientSelector, SelectionKey.OP_READ); } finally { keyIterator.remove(); } }
          } } } } catch (IOException ignored) { }
          }).start();

          new Thread(() -> { try { while (true) { // (2)批量輪詢哪些連接有數(shù)據(jù)可讀,這里的1指阻塞的時間為 1ms if (clientSelector.select(1) > 0) { Set<SelectionKey> set = clientSelector.selectedKeys(); Iterator<SelectionKey> keyIterator = set.iterator();
          while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next();
          if (key.isReadable()) { try { SocketChannel clientChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // (3)面向Buffer clientChannel.read(byteBuffer); byteBuffer.flip(); System.out.println(Charset.defaultCharset().newDecoder(). decode(byteBuffer) .toString()); } finally { keyIterator.remove(); key.interestOps(SelectionKey.OP_READ); } }
          } } } } catch (IOException ignored) { } }).start();

          }}


          相信大部分沒有接觸過NIO的讀者應(yīng)該會直接跳過代碼來到這一行:原來使用JDK原生NIO的API實(shí)現(xiàn)一個簡單的服務(wù)端通信程序如此復(fù)雜!

          我們還是先對照NIO來解釋一下核心思路。

          • NIO模型中通常會有兩個線程,每個線程都綁定一個輪詢器Selector。在這個例子中,serverSelector負(fù)責(zé)輪詢是否有新連接,clientSelector負(fù)責(zé)輪詢連接是否有數(shù)據(jù)可讀。

          • 服務(wù)端監(jiān)測到新連接之后,不再創(chuàng)建一個新線程,而是直接將新連接綁定到clientSelector上,這樣就不用IO模型中的1萬個while循環(huán)死等,參見(1)。

          • clientSelector被一個while死循環(huán)包裹著,如果在某一時刻有多個連接有數(shù)據(jù)可讀,那么通過clientSelector.select(1)方法可以輪詢出來,進(jìn)而批量處理。

          • 數(shù)據(jù)的讀寫面向Buffer。

          其他細(xì)節(jié)部分,因?yàn)閷?shí)在是太復(fù)雜,所以筆者不再多講,讀者也不用對代碼的細(xì)節(jié)深究到底。總之,強(qiáng)烈不建議直接基于JDK原生NIO來進(jìn)行網(wǎng)絡(luò)開發(fā),下面是筆者總結(jié)的原因。

          • JDK的NIO編程需要了解很多概念,編程復(fù)雜,對NIO入門非常不友好,編程模型不友好,ByteBuffer的API簡直“反人類”。

          • 對NIO編程來說,一個比較合適的線程模型能充分發(fā)揮它的優(yōu)勢,而JDK沒有實(shí)現(xiàn),需要自己實(shí)現(xiàn),就連簡單的自定義協(xié)議拆包都要自己實(shí)現(xiàn)。

          • JDK的NIO底層由Epoll實(shí)現(xiàn),該實(shí)現(xiàn)飽受詬病的空輪詢Bug會導(dǎo)致CPU占用率飆升至100%。

          • 項(xiàng)目龐大之后,自行實(shí)現(xiàn)的NIO很容易出現(xiàn)各類Bug,維護(hù)成本較高,上面這些代碼筆者都不能保證沒有Bug。

          正因?yàn)槿绱?,客戶端代碼這里就省略了,讀者可以直接使用IOClient.java與NIOServer.java通信。

          JDK的NIO猶如帶刺的玫瑰,雖然美好,讓人向往,但是使用不當(dāng)會讓你抓耳撓腮,痛不欲生,正因?yàn)槿绱耍?/span>Netty橫空出世!


          Netty編程

          Netty到底是何方神圣?

          用一句簡單的話來說就是:Netty封裝了JDK的NIO,讓你用得更方便,不用再寫一大堆復(fù)雜的代碼了。

          用官方正式的話來說就是:Netty是一個異步事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用框架,用于快速開發(fā)可維護(hù)的高性能服務(wù)端和客戶端。

          下面是筆者總結(jié)的使用Netty而不使用JDK原生NIO的原因。

          • 使用JDK原生NIO需要了解太多概念,編程復(fù)雜,一不小心就Bug橫飛。

          • Netty底層IO模型隨意切換,而這一切只需要做微小的改動,改改參數(shù),Netty可以直接從NIO模型變身為IO模型。

          • Netty自帶的拆包/粘包、異常檢測等機(jī)制讓你從NIO的繁重細(xì)節(jié)中脫離出來,只需要關(guān)心業(yè)務(wù)邏輯即可。

          • Netty解決了JDK很多包括空輪詢在內(nèi)的Bug。

          • Netty底層對線程、Selector做了很多細(xì)小的優(yōu)化,精心設(shè)計的Reactor線程模型可以做到非常高效的并發(fā)處理。

          • 自帶各種協(xié)議棧,讓你處理任何一種通用協(xié)議都幾乎不用親自動手。

          • Netty社區(qū)活躍,遇到問題隨時郵件列表或者Issue。

          • Netty已經(jīng)歷各大RPC框架、消息中間件、分布式通信中間件線上的廣泛驗(yàn)證,健壯性無比強(qiáng)大。

          這些原因看不懂沒有關(guān)系,在后續(xù)的章節(jié)中我們都可以學(xué)到。接下來我們用Netty來重新實(shí)現(xiàn)一下本章開篇的功能吧!

          首先引入Maven依賴,案例后續(xù)Netty都基于4.1.6.Final版本。

            <dependency>        <groupId>io.netty</groupId>        <artifactId>netty-all</artifactId>        <version>4.1.6.Final</version>    </dependency>

          然后是服務(wù)端實(shí)現(xiàn)部分。

          NettyServer.java

          /** * @author 閃電俠 */public class NettyServer {    public static void main(String[] args) {        ServerBootstrap serverBootstrap = new ServerBootstrap();
          NioEventLoopGroup boss = new NioEventLoopGroup(); NioEventLoopGroup worker = new NioEventLoopGroup(); serverBootstrap .group(boss, worker) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<NioSocketChannel>() { protected void initChannel(NioSocketChannel ch) { ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { System.out.println(msg); } }); } }) .bind(8000); }}

          這么一小段代碼就實(shí)現(xiàn)了我們前面NIO編程中的所有功能,包括服務(wù)端啟動、接收新連接、打印客戶端傳來的數(shù)據(jù),怎么樣?是不是比JDK原生NIO編程簡潔許多?

          初學(xué)Netty的時候,由于大部分人對NIO編程缺乏經(jīng)驗(yàn),因此,將Netty里的概念與IO模型結(jié)合起來可能更好理解。

          • boss對應(yīng)IOServer.java中的負(fù)責(zé)接收新連接的線程,主要負(fù)責(zé)創(chuàng)建新連接。

          • worker對應(yīng)IOServer.java中的負(fù)責(zé)讀取數(shù)據(jù)的線程,主要用于讀取數(shù)據(jù)及業(yè)務(wù)邏輯處理。

          剩下的邏輯筆者在后面的內(nèi)容中會詳細(xì)分析,讀者可以先把這段代碼復(fù)制到自己的IDE里,然后運(yùn)行main函數(shù)。

          下面是客戶端NIO的實(shí)現(xiàn)部分。

          NettyClient.java

          /** * @author 閃電俠 */public class NettyClient {    public static void main(String[] args) throws InterruptedException {        Bootstrap bootstrap = new Bootstrap();        NioEventLoopGroup group = new NioEventLoopGroup();
          bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) { ch.pipeline().addLast(new StringEncoder()); } });
          Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
          while (true) { channel.writeAndFlush(new Date() + ": hello world!"); Thread.sleep(2000); } }}

          在客戶端程序中,group對應(yīng)了IOClient.java中main函數(shù)起的線程,剩下的邏輯在后面的內(nèi)容中會詳細(xì)分析,現(xiàn)在你要做的事情就是把這段代碼復(fù)制到你的IDE里,然后運(yùn)行main函數(shù),最后回到NettyServer.java的控制臺,你會看到效果。

          使用Netty之后是不是覺得整個世界都變美好了?一方面,Netty對NIO封裝得如此完美,寫出來的代碼非常優(yōu)雅;另一方面,使用Netty之后,網(wǎng)絡(luò)通信的性能問題幾乎不用操心,盡情地讓Netty“榨干”你的CPU吧。

           

          以上內(nèi)容節(jié)選自《跟閃電俠學(xué) Netty:Netty 即時聊天實(shí)戰(zhàn)與底層原理》一書!


          目前市面上對初學(xué)者友好的Netty資料較少,網(wǎng)絡(luò)上大多數(shù)技術(shù)博客都是一堆零散的知識點(diǎn)集合,無法串成一條線形成一個體系。

          本書作者俞超老師(閃電俠)在多年的Netty實(shí)戰(zhàn)、調(diào)優(yōu)、“踩坑”過程中積累了豐富的經(jīng)驗(yàn),持續(xù)在網(wǎng)絡(luò)上分享的相關(guān)博客、視頻等有百萬+閱讀量,并得到網(wǎng)友一致好評!

          為了將這部分經(jīng)驗(yàn)系統(tǒng)地分享給大家,幫助大家提升核心競爭力,俞超老師特地將Netty底層原理相關(guān)知識進(jìn)行系統(tǒng)梳理,寫作了此書。

          本書上篇通過一個即時聊天的例子,讓讀者能夠系統(tǒng)地使用一遍Netty,全面掌握Netty的知識點(diǎn);下篇通過對源碼的層層剖析,讓讀者能夠掌握Netty底層原理,知其然并知其所以然,從而編寫出高性能網(wǎng)絡(luò)應(yīng)用程序。

          上篇 入門實(shí)戰(zhàn)


          在入門實(shí)戰(zhàn)篇中,讀者跟隨筆者實(shí)踐完這個即時聊天系統(tǒng)后,能夠?qū)W會如何使用Netty完成最基本的網(wǎng)絡(luò)通信程序,可以掌握以下知識點(diǎn):

          1. 如何啟動服務(wù)端?

          2. 如何啟動客戶端?

          3. 如何設(shè)計長連自定義協(xié)議?

          4. 拆包/粘包原理與實(shí)踐。

          5. 如何實(shí)現(xiàn)自定義編解碼。

          6. 如何使用Pipeline與ChannelHandler?

          7. 心跳與空閑檢測的方法。

          8. 如何性能調(diào)優(yōu)?

          本篇通俗易懂,可一口氣讀完,讓你一周內(nèi)進(jìn)入實(shí)戰(zhàn)!

          適讀人群


          本書適合以下三類人群:

          1. 如果你聽說過或簡單使用過Netty,想全面系統(tǒng)地學(xué)習(xí)Netty,并掌握一些性能調(diào)優(yōu)方法,本書的入門實(shí)戰(zhàn)篇可以幫助你達(dá)成這個目標(biāo)。

          2. 如果你深度使用過Netty,想深入了解Netty的底層設(shè)計,編寫出更靈活高效的網(wǎng)絡(luò)通信程序,本書的源碼分析篇可以幫助你達(dá)成這個目標(biāo)。

          3. 如果你從未讀過開源框架源碼,本書將是你的第一本源碼指導(dǎo)書,閱讀優(yōu)秀的開源軟件源碼可以助你寫出更優(yōu)美的程序。讀源碼并不難,難的是邁出這一小步,之后就能通往更廣闊的空間。

          本書推薦使用方式


          01. 按章節(jié)順序把入門實(shí)戰(zhàn)篇的代碼一章章敲出來,在沒有掌握前一章節(jié)的知識點(diǎn)之前,建議不要跳躍學(xué)習(xí)。

          02. 入門實(shí)戰(zhàn)篇學(xué)完之后,合上書本,把本書即時聊天系統(tǒng)的代碼再整體敲若干遍,敲的過程中可能會發(fā)現(xiàn)自己有遺忘知識點(diǎn),這個時候可能需要不斷翻閱書本,沒有關(guān)系,翻閱就好了。

          03. 確保最后一次實(shí)現(xiàn)本書的即時聊天系統(tǒng)的例子是沒有翻閱書本的,是完全自行實(shí)現(xiàn)的,之后進(jìn)入源碼分析篇的學(xué)習(xí)。

          04. 針對源碼分析篇,建議讀者按章節(jié)順序來學(xué)習(xí),不要跳躍,不要圖快,每一步都要扎實(shí)。

          05. 在源碼學(xué)習(xí)的過程中,先跟隨書本,對照源碼,把對應(yīng)章節(jié)的流程過一遍,每個章節(jié)學(xué)完之后,建議花較多的時間進(jìn)行調(diào)試和閱讀,確保掌握了前一章節(jié)的內(nèi)容之后再進(jìn)行下一章的學(xué)習(xí)。



          贈書規(guī)則

          數(shù)量:5本

          參與方式:文章留言點(diǎn)贊最高5位分別贈送一本

          截止時間:2022年3月11日下午6點(diǎn)


          瀏覽 31
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产这里只有精品 | 操操网 | 特西西人体门四WW高清 | 色婷婷丁香五月天男人天堂 | 伊人五月综合 |