Netty基礎(chǔ)原理常用API分析以及Liunx句柄數(shù)修改

Netty介紹

Netty是由JBOSS提供的一個(gè)java開源框架,是業(yè)界最流行的NIO框架,整合了多種協(xié)議(包括FTP、SMTP、HTTP等各種二進(jìn)制文本協(xié)議)的實(shí)現(xiàn)經(jīng)驗(yàn),精心設(shè)計(jì)的框架,在多個(gè)大型商業(yè)項(xiàng)目中得到充分驗(yàn)證。

那些主流框架產(chǎn)品在用?

  • 搜索引擎框架 ElasticSerach
  • Hadopp子項(xiàng)目Avro項(xiàng)目,使用Netty作為底層通信框架
  • 阿里巴巴開源的RPC框架 Dubbo
    Netty在Dubbo里面使用的地址
    https://github.com/apache/incubator-dubbo/tree/master/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4
    
    補(bǔ)充:netty4是dubbo2.5.6后引入的,2.5.6之前的netty用的是netty3

BIO時(shí)間返回器

public class BioServer {
    public static final int PORT=3456;
    public static void main(String[] args) throws IOException {
        ServerSocket server=null;
        try {
            server=new ServerSocket(PORT);
            Socket socket=null;
            while (true) {
                socket= server.accept();
                new Thread(new TimerServerHandler(socket)).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (server != null) {
                server.close();
            }
        }
    }
}
public class TimerServerHandler implements Runnable {

    private Socket socket;

    public TimerServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;

        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);

            String body=null;
            while ((body = in.readLine()) != null && body.length() != 0) {
                System.out.println("客戶端發(fā)送:"+body);
                out.println(new Date().toString());
            }

        } catch (Exception e) {

        } finally {
            if (in!=null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out!=null) {
                try {
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (this.socket != null) {
                try {
                    this.socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class BioClient {

    public static final int PORT=3456;

    public static void main(String[] args) {
        Socket socket=null;
        BufferedReader in=null;
        PrintWriter  out=null;
        try {
            socket=new Socket("127.0.0.1",PORT);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);
            out.println("i am client");
            String s = in.readLine();
            System.out.println("服務(wù)器當(dāng)前時(shí)間:"+s);
        } catch (Exception e) {

        } finally {
            if (in!=null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out!=null) {
                try {
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

BIO優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn)

    • 模型簡(jiǎn)單
    • 編碼簡(jiǎn)單
  • 缺點(diǎn):性能瓶頸,請(qǐng)求數(shù)和線程數(shù) N:N關(guān)系高并發(fā)情況下,CPU切換線程上下文損耗大

案例:web服務(wù)器Tomcat7之前,都是使用BIO,7之后就使用NIO

改進(jìn):偽NIO,使用線程池去處理業(yè)務(wù)邏輯

網(wǎng)絡(luò)IO模型

同步異步、堵塞和非堵塞

  • 洗衣機(jī)洗衣服

    • 洗衣機(jī)洗衣服(無論阻塞式IO還是非阻塞式IO,都是同步IO模型)
  • 同步阻塞:你把衣服丟到洗衣機(jī)洗,然后看著洗衣機(jī)洗完,洗好后再去晾衣服(你就干等,啥都不做,阻塞在那邊)

  • 同步非阻塞:你把衣服丟到洗衣機(jī)洗,然后會(huì)客廳做其他事情,定時(shí)去陽(yáng)臺(tái)看洗衣機(jī)是不是洗完了,洗好后再去晾衣服,這之間可以干其他事情

  • 異步阻塞: 你把衣服丟到洗衣機(jī)洗,然后看著洗衣機(jī)洗完,洗好后再去晾衣服(幾乎沒這個(gè)情況,幾乎沒這個(gè)說法,可以忽略)

  • 異步非阻塞:你把衣服丟到洗衣機(jī)洗,然后會(huì)客廳做其他事情,洗衣機(jī)洗好后會(huì)自動(dòng)去晾衣服,晾完成后放個(gè)音樂告訴你洗好衣服并晾好了

IO詳解

  • IO操作分兩步:發(fā)起IO請(qǐng)求等待數(shù)據(jù)準(zhǔn)備,實(shí)際IO操作(洗衣服,晾衣服)同步須要主動(dòng)讀寫數(shù)據(jù),在讀寫數(shù)據(jù)的過程中還是會(huì)阻塞(好比晾衣服阻塞了你) 異步僅僅須要I/O操作完畢的通知。并不主動(dòng)讀寫數(shù)據(jù),由操作系統(tǒng)內(nèi)核完畢數(shù)據(jù)的讀寫(機(jī)器人幫你自動(dòng)晾衣服)
  • 五種IO的模型:阻塞IO、非阻塞IO、多路復(fù)用IO、信號(hào)驅(qū)動(dòng)IO和異步IO,前四種都是同步IO,在內(nèi)核數(shù)據(jù)copy到用戶空間時(shí)都是阻塞的
權(quán)威:RFC標(biāo)準(zhǔn),或者書籍 《UNIX Network Programming》中文名《UNIX網(wǎng)絡(luò)編程-卷一》第六章
    1)阻塞式I/O;
    2)非阻塞式I/O;
    3)I/O復(fù)用(select,poll,epoll...);
I/O多路復(fù)用是阻塞在select,epoll這樣的系統(tǒng)調(diào)用沒有阻塞在真正的I/O系統(tǒng)調(diào)用如recvfrom進(jìn)程受阻于select,等待可能多個(gè)套接口中的任一個(gè)變?yōu)榭勺x
    
IO多路復(fù)用使用兩個(gè)系統(tǒng)調(diào)用(select和recvfrom)
blocking IO只調(diào)用了一個(gè)系統(tǒng)調(diào)用(recvfrom)
select/epoll 核心是可以同時(shí)處理多個(gè)connection,而不是更快,所以連接數(shù)不高的話,性能不一定比多線程+阻塞IO好
            
多路復(fù)用模型中,每一個(gè)socket,設(shè)置為non-blocking,
阻塞是在select這
  • 信號(hào)驅(qū)動(dòng)式I/O(SIGIO)

  • 異步I/O(POSIX的aio_系列函數(shù))Future-Listener機(jī)制

  • IO操作分為兩步

    • 發(fā)起IO請(qǐng)求,等待數(shù)據(jù)準(zhǔn)備(Waiting for the data to be ready)
    • 實(shí)際的IO操作,將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中(Copying the data from the kernel to the process)
  • 前四種IO模型都是同步IO操作,區(qū)別在于第一階段,而他們的第二階段是一樣的:在數(shù)據(jù)從內(nèi)核復(fù)制到應(yīng)用緩沖區(qū)期間(用戶空間),進(jìn)程阻塞于recvfrom調(diào)用或者select()函數(shù)。相反,異步I/O模型在這兩個(gè)階段都要處理。

  • 阻塞IO和非阻塞IO的區(qū)別在于第一步,發(fā)起IO請(qǐng)求是否會(huì)被阻塞,如果阻塞直到完成那么就是傳統(tǒng)的阻塞IO,如果不阻塞,那么就是非阻塞IO。同步IO和異步IO的區(qū)別就在于第二個(gè)步驟是否阻塞,如果實(shí)際的IO讀寫阻塞請(qǐng)求進(jìn)程,那么就是同步IO,因此阻塞IO、非阻塞IO、IO復(fù)用、信號(hào)驅(qū)動(dòng)IO都是同步IO,如果不阻塞,而是操作系統(tǒng)幫你做完IO操作再將結(jié)果返回給你,那么就是異步IO。

幾個(gè)核心點(diǎn):
   阻塞非阻塞說的是線程的狀態(tài)(重要)
   同步和異步說的是消息的通知機(jī)制(重要)
   
   同步需要主動(dòng)讀寫數(shù)據(jù),異步是不需要主動(dòng)讀寫數(shù)據(jù)
   同步IO和異步IO是針對(duì)用戶應(yīng)用程序和內(nèi)核的交互
   異步需要內(nèi)核層次的支持

IO多路復(fù)用技術(shù)

什么是IO多路復(fù)用:I/O多路復(fù)用,I/O是指網(wǎng)絡(luò)I/O, 多路指多個(gè)TCP連接(即socket或者channel),復(fù)用指復(fù)用一個(gè)或幾個(gè)線程。簡(jiǎn)單來說:就是使用一個(gè)或者幾個(gè)線程處理多個(gè)TCP連接,最大優(yōu)勢(shì)是減少系統(tǒng)開銷小,不必創(chuàng)建過多的進(jìn)程/線程,也不必維護(hù)這些進(jìn)程/線程

select:
    基本原理:監(jiān)視文件3類描述符: writefds、readfds、和exceptfds,調(diào)用后select
    函數(shù)會(huì)阻塞住,等有數(shù)據(jù) 可讀、可寫、出異常 或者 超時(shí) 就會(huì)返回,select函數(shù)正常返回后,通過遍歷fdset整個(gè)數(shù)組才能發(fā)現(xiàn)哪些句柄發(fā)生了事件,來找到
    就緒的描述符fd,然后進(jìn)行對(duì)應(yīng)的IO操作,幾乎在所有的平臺(tái)上支持,跨平臺(tái)支持性好
    
缺點(diǎn):
    1)select采用輪詢的方式掃描文件描述符,全部掃描,隨著文件描述符FD數(shù)量增多而性能下降            
    2)每次調(diào)用 select(),需要把 fd 集合從用戶態(tài)拷貝到內(nèi)核態(tài),并進(jìn)行遍歷(消息傳遞都是從內(nèi)核到用戶空間)
    3)最大的缺陷就是單個(gè)進(jìn)程打開的FD有限制,默認(rèn)是1024,這個(gè)指的是jvm的限制,而不是linux的限制(可修改宏定義,但是效率仍然慢)                
    static final  int MAX_FD = 1024
poll:
    基本流程:
 select() 和 poll() 系統(tǒng)調(diào)用的大體一樣,處理多個(gè)描述符也是使用輪詢的方式,根據(jù)描述符的狀態(tài)進(jìn)行處理,一樣需要把 fd 集合從用戶態(tài)拷貝到內(nèi)核態(tài),并進(jìn)行遍歷。最大區(qū)別是: poll沒有最大文件描述符限制(使用鏈表的方式存儲(chǔ)fd)

select和poll基本沒啥區(qū)別,主要是一個(gè)鏈表一個(gè)數(shù)組。

Epoll講解

epoll 基本原理:
     在2.6內(nèi)核中提出的,對(duì)比select和poll,epoll更加靈活,沒有描述符限制,用戶態(tài)拷貝到內(nèi)核態(tài)只需要一次
     使用事件通知,通過epoll_ctl注冊(cè)fd,一旦該fd就緒,內(nèi)核就會(huì)采用callback的回調(diào)機(jī)制來激活對(duì)應(yīng)的fd
   
     優(yōu)點(diǎn):
         1)沒fd這個(gè)限制,所支持的FD上限是操作系統(tǒng)的最大文件句柄數(shù),1G內(nèi)存大概支持10萬(wàn)個(gè)句柄 
         2)效率提高,使用回調(diào)通知而不是輪詢的方式,不會(huì)隨著FD數(shù)目的增加效率下降
         3)通過callback機(jī)制通知,內(nèi)核和用戶空間mmap同一塊內(nèi)存實(shí)現(xiàn)
  
         Linux內(nèi)核核心函數(shù)
         1)epoll_create()  在Linux內(nèi)核里面申請(qǐng)一個(gè)文件系統(tǒng) B+樹,返回epoll對(duì)象,也是一個(gè)fd
         2)epoll_ctl() 操作epoll對(duì)象,在這個(gè)對(duì)象里面修改添加刪除對(duì)應(yīng)的鏈接fd, 綁定一個(gè)callback函數(shù)
         3)epoll_wait()  判斷并完成對(duì)應(yīng)的IO操作
  
     缺點(diǎn):
         編程模型比select/poll 復(fù)雜
         例子:100萬(wàn)個(gè)連接,里面有1萬(wàn)個(gè)連接是活躍,在 select、poll、epoll分別是怎樣的表現(xiàn)                
         select:不修改宏定義,則需要 1000個(gè)進(jìn)程才可以支持 100萬(wàn)連接
         poll:100萬(wàn)個(gè)鏈接,遍歷都響應(yīng)不過來了,還有空間的拷貝消耗大量的資源
         epoll:通過回調(diào)通知,性能相比之下提升很大

Java的I/O演進(jìn)歷史

  • jdk1.4之前是采用同步阻塞模型,也就是BIO 大型服務(wù)一般采用C或者C++, 因?yàn)榭梢灾苯硬僮飨到y(tǒng)提供的異步IO,AIO
  • jdk1.4推出NIO,支持非阻塞IO,jdk1.7升級(jí),推出NIO2.0,提供AIO的功能,支持文件和網(wǎng)絡(luò)套接字的異步IO

Netty線程模型和Reactor模式

  • 設(shè)計(jì)模式——Reactor模式(反應(yīng)器設(shè)計(jì)模式),是一種基于事件驅(qū)動(dòng)的設(shè)計(jì)模式,在事件驅(qū)動(dòng)的應(yīng)用中,將一個(gè)或多個(gè)客戶的服務(wù)請(qǐng)求分離(demultiplex)和調(diào)度(dispatch)給應(yīng)用程序。在事件驅(qū)動(dòng)的應(yīng)用中,同步地、有序地處理同時(shí)接收的多個(gè)服務(wù)請(qǐng)求一般出現(xiàn)在高并發(fā)系統(tǒng)中,比如Netty,Redis等
  • 優(yōu)點(diǎn)
    • 1)響應(yīng)快,不會(huì)因?yàn)閱蝹€(gè)同步而阻塞,雖然Reactor本身依然是同步的
    • 2)編程相對(duì)簡(jiǎn)單,最大程度的避免復(fù)雜的多線程及同步問題,并且避免了多線程/進(jìn)程的切換開銷;
    • 3)可擴(kuò)展性,可以方便的通過增加Reactor實(shí)例個(gè)數(shù)來充分利用CPU資源;
  • 缺點(diǎn)
    • 1)相比傳統(tǒng)的簡(jiǎn)單模型,Reactor增加了一定的復(fù)雜性,因而有一定的門檻,并且不易于調(diào)試。
    • 2)Reactor模式需要系統(tǒng)底層的的支持,比如Java中的Selector支持,操作系統(tǒng)的select系統(tǒng)調(diào)用支持
  • 通俗理解:KTV例子前臺(tái)接待,服務(wù)人員帶領(lǐng)去開機(jī)器
  • Reactor模式基于事件驅(qū)動(dòng),適合處理海量的I/O事件,屬于同步非阻塞IO(NIO)
  • Reactor單線程模型(比較少用)
    • 1)作為NIO服務(wù)端,接收客戶端的TCP連接;作為NIO客戶端,向服務(wù)端發(fā)起TCP連接;
    • 2)服務(wù)端讀請(qǐng)求數(shù)據(jù)并響應(yīng);客戶端寫請(qǐng)求并讀取響應(yīng)

使用場(chǎng)景: 對(duì)應(yīng)小業(yè)務(wù)則適合,編碼簡(jiǎn)單;對(duì)于高負(fù)載、大并發(fā)的應(yīng)用場(chǎng)景不適合,一個(gè)NIO線程處理太多請(qǐng)求,則負(fù)載過高,并且可能響應(yīng)變慢,導(dǎo)致大量請(qǐng)求超時(shí),而且萬(wàn)一線程掛了,則不可用了

  • Reactor多線程模型
    • 內(nèi)容:Acceptor不在是一個(gè)線程,而是一組NIO線程;IO線程也是一組NIO線程,這樣就是兩個(gè)線程池去處理接入連接和處理IO
    • 使用場(chǎng)景:滿足目前的大部分場(chǎng)景,也是Netty推薦使用的線程模型

實(shí)際上的Reactor模式,是基于Java NIO的,在他的基礎(chǔ)上,抽象出來兩個(gè)組件——Reactor和Handler兩個(gè)組件:

(1)Reactor:負(fù)責(zé)響應(yīng)IO事件,當(dāng)檢測(cè)到一個(gè)新的事件,將其發(fā)送給相應(yīng)的Handler去處理;新的事件包含連接建立就緒、讀就緒、寫就緒等。

(2)Handler:將自身(handler)與事件綁定,負(fù)責(zé)事件的處理,完成channel的讀入,完成處理業(yè)務(wù)邏輯后,負(fù)責(zé)將結(jié)果寫出channel。

總結(jié):上面的單線程Reactor其實(shí)就可以看著一個(gè)特殊的handler。而多線程Reactor則分為兩部分,一部分是Reactor(可以為多線程,線程組或者單線程),而handler也就是上面說的IO線程,必須是線程組或者多線程。

附屬資料:
 為什么Netty使用NIO而不是AIO,是同步非阻塞還是異步非阻塞?
           
 答案:
 在Linux系統(tǒng)上,AIO的底層實(shí)現(xiàn)仍使用EPOLL,與NIO相同,因此在性能上沒有明顯的優(yōu)勢(shì)
 Netty整體架構(gòu)是reactor模型,采用epoll機(jī)制,所以往深的說,還是IO多路復(fù)用模式,所以也可說netty是同步非阻塞模型(看的層次不一樣)

 很多人說這是netty是基于Java NIO 類庫(kù)實(shí)現(xiàn)的異步通訊框架
 特點(diǎn):異步非阻塞、基于事件驅(qū)動(dòng),性能高,高可靠性和高可定制性。
   
 參考資料:
  https://github.com/netty/netty/issues/2515

基于netty搭建echo服務(wù)

常用服務(wù)組件

  • EventLoop和EventLoopGroup
  • Bootstrapt啟動(dòng)引導(dǎo)類
  • Channel 生命周期,狀態(tài)變化
  • ChannelHandler和ChannelPipline

代碼

public class EchoServer {
   private int port;

    public EchoServer(int port) {
        this.port = port;
    }

    /**
     * 啟動(dòng)流程
     */
    public void run() throws InterruptedException {
        //配置服務(wù)端線程組
        EventLoopGroup bossGroup=new NioEventLoopGroup();
        EventLoopGroup workGroup=new NioEventLoopGroup();

        try {
            //啟動(dòng)類
            ServerBootstrap serverBootstrap=new ServerBootstrap();
            serverBootstrap.group(bossGroup,workGroup).channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .option(ChannelOption.TCP_NODELAY,true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //串聯(lián)很多要處理的handler
                            ch.pipeline().addLast(new EchoHandler());
                        }
                    });
            //綁定端口,同步等待成功
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            //等待服務(wù)端監(jiān)聽端口關(guān)閉
            channelFuture.channel().closeFuture().sync();
        }finally {
            //優(yōu)雅退出,釋放線程池
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        int port=8080;
        if (args.length > 0) {
            port=Integer.parseInt(args[0]);
        }
        new EchoServer(port).run();
    }
}
public class EchoHandler extends ChannelInboundHandlerAdapter {

    //讀取數(shù)據(jù)
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        Channel channel = ctx.channel();
//        channel.writeAndFlush()


//        ChannelPipeline pipeline = ctx.pipeline();
//        pipeline.writeAndFlush()

        ByteBuf data= (ByteBuf) msg;
        System.out.println("服務(wù)端收到數(shù)據(jù):"+data.toString(CharsetUtil.UTF_8));
        ctx.writeAndFlush(data);
    }

    //讀取完成
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("EchoServerHandler channelReadComplete");
    }

    //異常捕獲
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();//關(guān)閉管道
    }
}
public class EchoClient {
    private String host;
    private int port;

    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }
    //https://blog.csdn.net/fd2025/article/details/79740226
    public void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline().addLast(new EchoClientHandler());
                        }
                    });

            //連接到服務(wù)端,connect是異步連接,再調(diào)用同步async,等待連接成功從
            ChannelFuture channelFuture = bootstrap.connect().sync();
            //阻塞,直到客戶端通道關(guān)閉
            channelFuture.channel().closeFuture().sync();
        } finally {
            //優(yōu)雅退出,釋放nio線程
            group.shutdownGracefully();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        new EchoClient("127.0.0.1", 8080).start();

    }
}
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {


    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf msg) throws Exception {
        System.out.println("Client Received: "+msg.toString(CharsetUtil.UTF_8));
    }

    //channel激活的時(shí)候
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelActive");
        ctx.writeAndFlush(Unpooled.copiedBuffer("哈哈測(cè)試",CharsetUtil.UTF_8));
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("EchoClientHandler Complate");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>net.xdclass</groupId>
    <artifactId>echo-project</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
    <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.32.Final</version>
        </dependency>
    </dependencies>
</project>

Netty的核心鏈路源碼

剖析EventLoop和EventLoopGroup線程模型

  • 高性能RPC框架的3個(gè)要素:IO模型(linux的IO模型五種)、數(shù)據(jù)協(xié)議(http,rpc等)、線程模型

線程模型

1. 傳統(tǒng)IO模型:
每個(gè)請(qǐng)求都分配一個(gè)線程用來處理該請(qǐng)求,關(guān)于該請(qǐng)求
的read,handle,和send都放在一個(gè)線程中進(jìn)行處理
2. 基于線程池的偽異步IO模型
針對(duì)傳統(tǒng)IO模型中會(huì)造成線程資源極大浪費(fèi)的缺點(diǎn),通
過線程池來復(fù)用線程處理客戶端連接和數(shù)據(jù)處理.

* 會(huì)有一個(gè)阻塞線程負(fù)責(zé)socket連接,即acceptor;
*會(huì)有一個(gè)線程池維護(hù)n個(gè)活躍線程和一個(gè)消息隊(duì)列,來
處理socketTask,所以資源是可控的,所以無論客戶端
多少并發(fā)連接,都會(huì)導(dǎo)致系統(tǒng)資源耗盡和宕機(jī);

缺點(diǎn):

- 無法解決通信阻塞的問題,因?yàn)閟ocket.read()方法是
流式數(shù)據(jù)讀取,因此只能讀取完所有數(shù)據(jù)后才能正確處理,如果一個(gè)socket發(fā)送數(shù)據(jù)需要60秒那么該線程處理數(shù)
據(jù)至少要60秒,那么這段時(shí)間內(nèi)的io事件,該線程是
無法及時(shí)處理的,如果這樣的io事件出現(xiàn)多次,很可
能造成消息隊(duì)列阻塞;

- 只有一個(gè)acceptor負(fù)責(zé)socket連接,如果線程池阻塞隊(duì)列阻塞之后,那么所有新的客戶端連接也將會(huì)被拒絕;如果大量連接拒絕,就可能會(huì)認(rèn)定為系統(tǒng)故障;

3. Reactor模型(實(shí)時(shí)響應(yīng))
前面已經(jīng)講過這個(gè)模型;
IO復(fù)用結(jié)合線程池復(fù)用就是Reactor模型設(shè)計(jì)的基本思想

總結(jié):線程模型其實(shí)就是IO模型的相關(guān)運(yùn)用,可能還會(huì)搭配線程池服用,例如Reactor模型
  • EventLoop好比一個(gè)線程,1個(gè)EventLoop可以服務(wù)多個(gè)Channel,1個(gè)Channel只有一個(gè)EventLoop可以創(chuàng)建多個(gè) EventLoop 來優(yōu)化資源利用,也就是EventLoopGroup

  • EventLoopGroup 負(fù)責(zé)分配 EventLoop 到新創(chuàng)建的 Channel,里面包含多個(gè)EventLoop

    • EventLoopGroup -> 多個(gè) EventLoop
    • EventLoop -> 維護(hù)一個(gè)Selector(其實(shí)就是遍歷器)
    • 學(xué)習(xí)資料:http://ifeve.com/selectors/
  • EventLoopGroup默認(rèn)線程池?cái)?shù)量是系統(tǒng)核數(shù)*2

Bootstrap模塊講解

設(shè)置channel通道類型NioServerSocketChannel、OioServerSocketChannel

  • option: 作用于每個(gè)新建立的channel,設(shè)置TCP連接中的一些參數(shù),如下

    • ChannelOption.SO_BACKLOG: 存放已完成三次握手的請(qǐng)求的等待隊(duì)列的最大長(zhǎng)度;
    • Linux服務(wù)器TCP連接底層知識(shí):
      • syn queue:半連接隊(duì)列,洪水攻擊,tcp_max_syn_backlog
      • accept queue:全連接隊(duì)列, net.core.somaxconn
    • 系統(tǒng)默認(rèn)的somaxconn參數(shù)要足夠大 ,如果backlog比somaxconn大,則會(huì)優(yōu)先用后者 https://github.com/netty/netty/blob/4.1/common/src/main/java/io/netty/util/NetUtil.java#L250
    • ChannelOption.TCP_NODELAY: 為了解決Nagle的算法問題,默認(rèn)是false, 要求高實(shí)時(shí)性,有數(shù)據(jù)時(shí)馬上發(fā)送,就將該選項(xiàng)設(shè)置為true關(guān)閉Nagle算法;如果要減少發(fā)送次數(shù),就設(shè)置為false,會(huì)累積一定大小后再發(fā)送
    • 知識(shí)拓展: https://baike.baidu.com/item/Nagle%E7%AE%97%E6%B3%95/5645172 https://www.2cto.com/article/201309/241096.html
  • childOption: 作用于被accept之后的連接

  • childHandler: 用于對(duì)每個(gè)通道里面的數(shù)據(jù)處理

粗略的理解為option是給bossGroup配置的,childOption是給workerGroup配置的;這兩個(gè)線程組對(duì)應(yīng)reactor模型的Acceptor和handler

  • 客戶端啟動(dòng)引導(dǎo)類Bootstrap
    • remoteAddress: 服務(wù)端地址
    • handler:和服務(wù)端通信的處理器

Channel模塊

  • 什么是Channel: 客戶端和服務(wù)端建立的一個(gè)連接通道
  • 什么是ChannelHandler: 負(fù)責(zé)Channel的邏輯處理
  • 什么是ChannelPipeline:負(fù)責(zé)管理ChannelHandler的有序容器
  • 他們是什么關(guān)系

一個(gè)Channel包含一個(gè)ChannelPipeline,所有ChannelHandler都會(huì)順序加入到ChannelPipeline中 創(chuàng)建Channel時(shí)會(huì)自動(dòng)創(chuàng)建一個(gè)ChannelPipeline,每個(gè)Channel都有一個(gè)管理它的pipeline,這關(guān)聯(lián)是永久性的

  • Channel當(dāng)狀態(tài)出現(xiàn)變化,就會(huì)觸發(fā)對(duì)應(yīng)的事件
    • 狀態(tài):
      • channelRegistered: channel注冊(cè)到一個(gè)EventLoop
      • channelActive: 變?yōu)榛钴S狀態(tài)(連接到了遠(yuǎn)程主機(jī)),可以接受和發(fā)送數(shù)據(jù)
      • channelInactive: channel處于非活躍狀態(tài),沒有連接到遠(yuǎn)程主機(jī)
      • channelUnregistered: channel已經(jīng)創(chuàng)建,但是未注冊(cè)到一個(gè)EventLoop里面,也就是沒有和Selector綁定

特別注意:執(zhí)行順序channelRegistered-》channelActive=》channelInactive=》channelUnregistered

ChannelHandler和ChannelPipeline模塊講解

  • 方法: handlerAdded : 當(dāng) ChannelHandler 添加到 ChannelPipeline 調(diào)用; handlerRemoved : 當(dāng) ChannelHandler 從 ChannelPipeline 移除時(shí)調(diào)用; exceptionCaught : 執(zhí)行拋出異常時(shí)調(diào)用;
  • ChannelHandler下主要是兩個(gè)子接口
    • ChannelInboundHandler:(入站) 處理輸入數(shù)據(jù)和Channel狀態(tài)類型改變, 適配器ChannelInboundHandlerAdapter(適配器設(shè)計(jì)模式) 常用的:SimpleChannelInboundHandler
    • ChannelOutboundHandler:(出站) 處理輸出數(shù)據(jù),適配器ChannelOutboundHandlerAdapter
  • ChannelPipeline: 好比廠里的流水線一樣,可以在上面添加多個(gè)ChannelHandler,也可看成是一串 ChannelHandler實(shí)例,攔截穿過 Channel 的輸入輸出 event,ChannelPipeline實(shí)現(xiàn)了攔截器的一種高級(jí)形式,使得用戶可以對(duì)事件的處理以及ChannelHanler之間交互獲得完全的控制權(quán)

ChannelHandlerContext模塊

  • ChannelHandlerContext是連接ChannelHandler和ChannelPipeline的橋梁,ChannelHandlerContext部分方法和Channel及ChannelPipeline重合,好比調(diào)用write方法
    • Channel、ChannelPipeline、ChannelHandlerContext 都可以調(diào)用此方法,前兩者都會(huì)在整個(gè)管道流里傳播,而ChannelHandlerContext就只會(huì)在后續(xù)的Handler里面?zhèn)鞑?/li>
  • AbstractChannelHandlerContext類雙向鏈表結(jié)構(gòu),next/prev分別是后繼節(jié)點(diǎn),和前驅(qū)節(jié)點(diǎn)
  • DefaultChannelHandlerContext 是實(shí)現(xiàn)類,但是大部分都是父類那邊完成,這個(gè)只是簡(jiǎn)單的實(shí)現(xiàn)一些方法 主要就是判斷Handler的類型
  • ChannelInboundHandler之間的傳遞,主要通過調(diào)用ctx里面的FireXXX()方法來實(shí)現(xiàn)下個(gè)handler的調(diào)用

入站出站Handler執(zhí)行順序

  • InboundHandler順序執(zhí)行,OutboundHandler逆序執(zhí)行
  • InboundHandler之間傳遞數(shù)據(jù),通過ctx.fireChannelRead(msg)
  • InboundHandler通過ctx.write(msg),則會(huì)傳遞到outboundHandler
  • 使用ctx.write(msg)傳遞消息,Inbound需要放在結(jié)尾,在Outbound之后,不然outboundhandler會(huì)不執(zhí)行;但是使用channel.write(msg)、pipline.write(msg)情況會(huì)不一致,outboundhandler都會(huì)執(zhí)行
  • outBound和Inbound誰(shuí)先執(zhí)行,針對(duì)客戶端和服務(wù)端而言,客戶端是發(fā)起請(qǐng)求再接受數(shù)據(jù),先outbound再inbound,服務(wù)端則相反

總結(jié):需要保證最后一個(gè)outhandler的的上下文可以有next的指向,否則最后一個(gè)outhandler就不會(huì)執(zhí)行了,也就是說最后一個(gè)inhanlder之后的outhandler都不會(huì)執(zhí)行。所以一般最后都要有一個(gè)inhandler。

模塊ChannelFuture

  • Netty中的所有I/O操作都是異步的,這意味著任何I/O調(diào)用都會(huì)立即返回,而ChannelFuture會(huì)提供有關(guān)的信息I/O操作的結(jié)果或狀態(tài)。
  • ChannelFuture狀態(tài)
    • 未完成:當(dāng)I/O操作開始時(shí),將創(chuàng)建一個(gè)新的對(duì)象,新的最初是未完成的 - 它既沒有成功,也沒有成功,也沒有被取消,因?yàn)镮/O操作尚未完成。
    • 已完成:當(dāng)I/O操作完成,不管是成功、失敗還是取消,F(xiàn)uture都是標(biāo)記為已完成的, 失敗的時(shí)候也有具體的信息,例如原因失敗,但請(qǐng)注意,即使失敗和取消屬于完成狀態(tài)
    • 注意:不要在IO線程內(nèi)調(diào)用future對(duì)象的sync或者await方法。不能在channelHandler中調(diào)用sync或者await方法,會(huì)阻塞
  • ChannelPromise:繼承于ChannelFuture,進(jìn)一步拓展用于設(shè)置IO操作的結(jié)果

Netty網(wǎng)絡(luò)數(shù)據(jù)傳輸編解碼

  • 最開始接觸的編碼碼:java序列化/反序列化(就是編解碼)、url編碼、base64編解碼
  • 為啥jdk有編解碼,還要netty自己開發(fā)編解碼?
    • java自帶序列化的缺點(diǎn)
1)無法跨語(yǔ)言
2) 序列化后的碼流太大,也就是數(shù)據(jù)包太大
3) 序列化和反序列化性能比較差
  • 業(yè)界里面也有其他編碼框架: google的 protobuf(PB)、Facebook的Trift、Jboss的Marshalling、Kyro等
  • Netty里面的編解碼:
    • 解碼器:負(fù)責(zé)處理“入站 InboundHandler”數(shù)據(jù)
    • 編碼器:負(fù)責(zé)“出站 OutboundHandler” 數(shù)據(jù)
    • Netty里面提供默認(rèn)的編解碼器,也支持自定義編解碼器
      • Encoder:編碼器
      • Decoder:解碼器
      • Codec:編解碼器

解碼器Decoder

  • Decoder對(duì)應(yīng)的就是ChannelInboundHandler,主要就是字節(jié)數(shù)組轉(zhuǎn)換為消息對(duì)象
  • 主要是兩個(gè)方法 decode decodeLast
  • 抽象解碼器
    • ByteToMessageDecoder用于將字節(jié)轉(zhuǎn)為消息,需要檢查緩沖區(qū)是否有足夠的字節(jié)
    • ReplayingDecoder繼承ByteToMessageDecoder,不需要檢查緩沖區(qū)是否有足夠的字節(jié),但是ReplayingDecoder速度略滿于ByteToMessageDecoder,不是所有的ByteBuf都支持
    • 選擇:項(xiàng)目復(fù)雜性高則使用ReplayingDecoder,否則使用 ByteToMessageDecoder
    • MessageToMessageDecoder用于從一種消息解碼為另外一種消息(例如POJO到POJO)
  • 解碼器具體的實(shí)現(xiàn),用的比較多的是(更多是為了解決TCP底層的粘包和拆包問題)
    • DelimiterBasedFrameDecoder: 指定消息分隔符的解碼器
    • LineBasedFrameDecoder: 以換行符為結(jié)束標(biāo)志的解碼器
    • FixedLengthFrameDecoder:固定長(zhǎng)度解碼器
    • LengthFieldBasedFrameDecoder:message = header+body, 基于長(zhǎng)度解碼的通用解碼器
    • StringDecoder:文本解碼器,將接收到的對(duì)象轉(zhuǎn)化為字符串,一般會(huì)與上面的進(jìn)行配合,然后在后面添加業(yè)務(wù)handle

編碼器Encoder

  • Encoder對(duì)應(yīng)的就是ChannelOutboundHandler,消息對(duì)象轉(zhuǎn)換為字節(jié)數(shù)組
  • Netty本身未提供和解碼一樣的編碼器,是因?yàn)閳?chǎng)景不同,兩者非對(duì)等的(也就是不見得是一對(duì)一的關(guān)系)
  • MessageToByteEncoder消息轉(zhuǎn)為字節(jié)數(shù)組,調(diào)用write方法,會(huì)先判斷當(dāng)前編碼器是否支持需要發(fā)送的消息類型,如果不支持,則透?jìng)鳎?/li>
  • MessageToMessageEncoder用于從一種消息編碼為另外一種消息(例如POJO到POJO)

編解碼器類Codec

 組合解碼器和編碼器,以此提供對(duì)于字節(jié)和消息都相同的操作
       
        優(yōu)點(diǎn):成對(duì)出現(xiàn),編解碼都是在一個(gè)類里面完成    
        缺點(diǎn):耦合在一起,拓展性不佳

        Codec:組合編解碼
            1)ByteToMessageCodec
    
            2)MessageToMessageCodec
    
        decoder:解碼
             1)ByteToMessageDecoder
    
             2)MessageToMessageDecoder
        
        encoder:編碼
             1)ByteToMessageEncoder
    
            2)MessageToMessageEncoder

TCP粘包拆包

什么是粘包拆包

1)TCP拆包: 一個(gè)完整的包可能會(huì)被TCP拆分為多個(gè)包進(jìn)行發(fā)送
2)TCP粘包: 把多個(gè)小的包封裝成一個(gè)大的數(shù)據(jù)包發(fā)送, client發(fā)送的若干數(shù)據(jù)包 Server接收時(shí)粘成一包
    
發(fā)送方和接收方都可能出現(xiàn)這個(gè)原因
        
發(fā)送方的原因:TCP默認(rèn)會(huì)使用Nagle算法
        
接收方的原因: TCP接收到數(shù)據(jù)放置緩存中,應(yīng)用程序從緩存中讀取 
       
UDP: 是沒有粘包和拆包的問題,有邊界協(xié)議

TCP半包讀寫常見解決方案

發(fā)送方:可以關(guān)閉Nagle算法
接受方: TCP是無界的數(shù)據(jù)流,并沒有處理粘包現(xiàn)象的機(jī)制, 且協(xié)議本身無法避免粘包,半包讀寫的發(fā)生需要在應(yīng)用層進(jìn)行處理
     應(yīng)用層解決半包讀寫的辦法
     1)設(shè)置定長(zhǎng)消息 (10字符)
        xdclass000xdclass000xdclass000xdclass000
                        
     2)設(shè)置消息的邊界 ($$ 切割)
        sdfafwefqwefwe$$dsafadfadsfwqehidwuehfiw$$879329832r89qweew$$
    
     3)使用帶消息頭的協(xié)議,消息頭存儲(chǔ)消息開始標(biāo)識(shí)及消息的長(zhǎng)度信息
        Header+Body

Netty自帶解決TCP半包讀寫方案

DelimiterBasedFrameDecoder: 指定消息分隔符的解碼器

  • LineBasedFrameDecoder:以換行符為結(jié)束標(biāo)志的解碼器
  • FixedLengthFrameDecoder:固定長(zhǎng)度解碼器
  • LengthFieldBasedFrameDecoder:message = header+body, 基于長(zhǎng)度解碼的通用解碼器
public void run() throws Exception{

        //配置服務(wù)端的線程組
        EventLoopGroup bossGroup = new NioEventLoopGroup();

        EventLoopGroup workGroup = new NioEventLoopGroup();

        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workGroup)

                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG,128)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new ServerHandler());
                    }
                });

            System.out.println("Echo 服務(wù)器啟動(dòng)");
            //綁定端口,同步等待成功
            ChannelFuture channelFuture =  serverBootstrap.bind(port).sync();
            //等待服務(wù)端監(jiān)聽端口關(guān)閉
            channelFuture.channel().closeFuture().sync();

        }finally {
            //優(yōu)雅退出,釋放線程池
            workGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }

    }

LineBasedFrameDecoder解決TCP半包讀寫

  • LineBaseFrameDecoder 以換行符為結(jié)束標(biāo)志的解碼器 ,構(gòu)造函數(shù)里面的數(shù)字表示最長(zhǎng)遍歷的幀數(shù)
  • StringDecoder解碼器將對(duì)象轉(zhuǎn)成字符串

自定義分隔符解決TCP讀寫問題

  • maxLength:表示一行最大的長(zhǎng)度,如果超過這個(gè)長(zhǎng)度依然沒有檢測(cè)自定義分隔符,將會(huì)拋出TooLongFrameException
  • failFast:如果為true,則超出maxLength后立即拋出TooLongFrameException,不進(jìn)行繼續(xù)解碼.如果為false,則等到完整的消息被解碼后,再拋出TooLongFrameException異常
  • stripDelimiter:解碼后的消息是否去除掉分隔符
  • delimiters:分隔符,ByteBuf類型

自定義長(zhǎng)度半包讀寫器LengthFieldBasedFrameDecoder

maxFrameLength 數(shù)據(jù)包的最大長(zhǎng)度
    
lengthFieldOffset 長(zhǎng)度字段的偏移位,長(zhǎng)度字段開始的地方,意思是跳過指定長(zhǎng)度個(gè)字節(jié)之后的才是消息體字段

lengthFieldLength 長(zhǎng)度字段占的字節(jié)數(shù), 幀數(shù)據(jù)長(zhǎng)度的字段本身的長(zhǎng)度

lengthAdjustment 
    一般 Header + Body,添加到長(zhǎng)度字段的補(bǔ)償值,如果為負(fù)數(shù),開發(fā)人員認(rèn)為這個(gè) Header的長(zhǎng)度字段是整個(gè)消息包的長(zhǎng)度,則Netty應(yīng)該減去對(duì)應(yīng)的數(shù)字

initialBytesToStrip 從解碼幀中第一次去除的字節(jié)數(shù), 獲取完一個(gè)完整的數(shù)據(jù)包之后,忽略前面的指定位數(shù)的長(zhǎng)度字節(jié),應(yīng)用解碼器拿到的就是不帶長(zhǎng)度域的數(shù)據(jù)包

failFast 是否快速失敗

緩沖ByteBuf

ByteBuf是為解決ByteBuffer的問題和滿足網(wǎng)絡(luò)應(yīng)用程序開發(fā)人員的日常需求而設(shè)計(jì)的

JDK ByteBuffer的缺點(diǎn):

  • 無法動(dòng)態(tài)擴(kuò)容:長(zhǎng)度固定,不能動(dòng)態(tài)擴(kuò)展和收縮,當(dāng)數(shù)據(jù)大于ByteBuffer容量時(shí),會(huì)發(fā)生索引越界異常

  • API使用復(fù)雜:讀寫的時(shí)候需要手工調(diào)用flip()和rewind()等方法,使用時(shí)需要非常謹(jǐn)慎的使用這些API,否則很容易出現(xiàn)錯(cuò)誤

  • ByteBuf:是數(shù)據(jù)容器(字節(jié)容器)

  • JDK ByteBuffer:共用讀寫索引,每次讀寫操作都需要Flip(復(fù)位,因?yàn)樽x索引和寫索引是同一個(gè))擴(kuò)容麻煩,而且擴(kuò)容后容易造成浪費(fèi)

  • Netty ByteBuf: 讀寫使用不同的索引,所以操作便捷自動(dòng)擴(kuò)容,使用便捷

增強(qiáng)

  • API操作便捷性
  • 動(dòng)態(tài)擴(kuò)容
  • 多種ByteBuf實(shí)現(xiàn)
  • 高效的零拷貝機(jī)制

ByteBuf操作

1.png
2.png

ByteBuf動(dòng)態(tài)擴(kuò)容

capacity默認(rèn)值:256字節(jié),最大值:Integet.MAX_VALUE(2GB)
write*方法調(diào)用時(shí),通過AbstractByteBuf.ensureWritable0進(jìn)行檢查
容量計(jì)算方法:AbstractByteBufAllocator.calculateNewCapacity(新capacity的最小要求,capacity最大值)

根據(jù)新capacity的最小值要求,對(duì)應(yīng)有兩套計(jì)算方法:
沒超過4M:從64字節(jié)開始,每次增加一倍,直至計(jì)算出來的newCpacity滿足新容量最小要求

示例:當(dāng)前大小256,寫250,繼續(xù)寫10字節(jié)數(shù)據(jù),需要的容量最小要求是261,則新容量是6422*2=512

超過4M:新容量=新容量最小要求/4M*4M+4M

示例:當(dāng)前大小3M,已寫3M,繼續(xù)寫2M數(shù)據(jù),需要的容量最小要求是5M,則新容量是9M(不能超過最大值)

4M的來源:一個(gè)固定的閾值A(chǔ)bstractByteBufAllocator.CALCULATE_THRESHOLD

ByteBuf實(shí)現(xiàn)

3.png

所謂池化,其實(shí)就是內(nèi)存復(fù)用

Unsafe的實(shí)現(xiàn)

4.png

PooledByteBuf對(duì)象、內(nèi)存復(fù)用

5.png

零拷貝機(jī)制

Netty的零拷貝機(jī)制,是一種應(yīng)用層的實(shí)現(xiàn)。和底層JVM、操作系統(tǒng)內(nèi)存機(jī)制并無過多的關(guān)聯(lián)。
使用ByteBuf時(shí)netty高性能很重喲的一個(gè)原因。


6.png

說明:例如2.就是buffer持有array的引用,實(shí)際上數(shù)據(jù)沒動(dòng),3也是,數(shù)據(jù)沒動(dòng),只是其中l(wèi)l的引用被buffer持有;還有1,如果是常規(guī)jdk的數(shù)組合并,其實(shí)是拷貝數(shù)據(jù),同時(shí)新開內(nèi)存生成新的數(shù)組

ByteBuf創(chuàng)建方法和常用的模式

 ByteBuf:傳遞字節(jié)數(shù)據(jù)的容器
    
   ByteBuf的創(chuàng)建方法
    1)ByteBufAllocator
      池化(Netty4.x版本后默認(rèn)使用 PooledByteBufAllocator提高性能并且最大程度減少內(nèi)存碎片
    
      非池化UnpooledByteBufAllocator: 每次返回新的實(shí)例
    
    2)Unpooled: 提供靜態(tài)方法創(chuàng)建未池化的ByteBuf,可以創(chuàng)建堆內(nèi)存和直接內(nèi)存緩沖區(qū)
?           
     ByteBuf使用模式
        堆緩存區(qū)HEAP BUFFER:
            優(yōu)點(diǎn):存儲(chǔ)在JVM的堆空間中,可以快速的分配和釋放
            缺點(diǎn):每次使用前會(huì)拷貝到直接緩存區(qū)(也叫堆外內(nèi)存)
    
        直接緩存區(qū)DIRECR BUFFER:
            優(yōu)點(diǎn):存儲(chǔ)在堆外內(nèi)存上,堆外分配的直接內(nèi)存,不會(huì)占用堆空間
            缺點(diǎn):內(nèi)存的分配和釋放,比在堆緩沖區(qū)更復(fù)雜
    
        復(fù)合緩沖區(qū)COMPOSITE BUFFER:
            可以創(chuàng)建多個(gè)不同的ByteBuf,然后放在一起,但是只是一個(gè)視圖
            選擇:大量IO數(shù)據(jù)讀寫,用“直接緩存區(qū)”; 業(yè)務(wù)消息編解碼用“堆緩存區(qū)”

Netty內(nèi)部設(shè)計(jì)模式

Builder構(gòu)造器模式:ServerBootstap 

責(zé)任鏈設(shè)計(jì)模式:pipeline的事件傳播
            
工廠模式: 創(chuàng)建Channel
            
適配器模式:HandlerAdapter

單機(jī)百萬(wàn)連接

必備知識(shí)

  • 網(wǎng)絡(luò)IO模型
  • Linux文件描述符
    • 單進(jìn)程文件句柄數(shù)(默認(rèn)1024,不同系統(tǒng)不一樣,每個(gè)進(jìn)程都有最大的文件描述符限制)
    • 全局文件句柄數(shù)
  • 如何確定一個(gè)唯一的TCP連接.
    • TCP四元組:源IP地址、源端口、目的ip、目的端口

Netty單機(jī)百萬(wàn)連接Linux內(nèi)核參數(shù)優(yōu)化

局部文件句柄限制(單個(gè)進(jìn)程最大文件打開數(shù))
    ulimit -n 一個(gè)進(jìn)程最大打開的文件數(shù) fd 不同系統(tǒng)有不同的默認(rèn)值

    root身份編輯   vim /etc/security/limits.conf
                增加下面
                root soft nofile 1000000
                root hard nofile 1000000
                * soft nofile 1000000
                * hard nofile 1000000
    * 表示當(dāng)前用戶,修改后要重啟
    
全局文件句柄限制(所有進(jìn)程最大打開的文件數(shù),不同系統(tǒng)是不一樣,可以直接echo臨時(shí)修改)
    查看命令
        cat /proc/sys/fs/file-max
    永久修改全局文件句柄, 修改后生效 sysctl -p
        vim  /etc/sysctl.conf
        增加 fs.file-max = 1000000
    
    啟動(dòng)
        java -jar millionServer-1.0-SNAPSHOT.jar  -Xms5g -Xmx5g -XX:NewSize=3g -XX:MaxNewSize=3g
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容