1. 程式人生 > >突破netty單機最大連線數

突破netty單機最大連線數

實現單機的百萬連線,瓶頸有以下幾點:
1、如何模擬百萬連線
2、突破區域性檔案控制代碼的限制
3、突破全域性檔案控制代碼的限制
在linux系統裡面,單個程序開啟的控制代碼數是非常有限的,一條TCP連線就對應一個檔案控制代碼,而對於我們應用程式來說,一個服務端預設建立的連線數是有限制的。

如下圖所示,通常一個客戶端去除一些被佔用的埠之後,可用的埠大於只有6w個左右,要想模擬百萬連線要起比較多的客戶端,而且比較麻煩,所以這種方案不合適。

 

在服務端啟動800~8100,而客戶端依舊使用1025-65535範圍內可用的埠號,讓同一個埠號,可以連線Server的不同埠。這樣的話,6W的埠可以連線Server的100個埠,累加起來就能實現近600W左右的連線,TCP是以一個四元組概念,以原IP、原埠號、目的IP、目的埠號來確定的,當原IP 和原埠號相同,但目的埠號不同,最終系統會把他當成兩條TCP 連線來處理,所以TCP連線可以如此設計。

 

測試環境

netty客戶端 ,和netty服務端 都是springboot專案。
執行環境:linux
netty版本:4.1.6.Final

netty服務端程式碼

netty maven
<properties>
<netty-all.version>4.1.6.Final</netty-all.version>
</properties>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty-all.version}</version>
</dependency>

@SpringBootApplication
public class NettyserverApplication {
    private static final int BEGIN_PORT = 8000;
    private static final int N_PORT = 100;
    public static void main(String[] args) {
        SpringApplication.run(NettyserverApplication.class, args);
        new Server().start(BEGIN_PORT, N_PORT);
    }
}
/----------------------------------------------------------------------------------
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public final class Server {
    public void start(int beginPort, int nPort) {
        System.out.println("server starting....");

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup);
        bootstrap.channel(NioServerSocketChannel.class);
        bootstrap.childOption(ChannelOption.SO_REUSEADDR, true);

        bootstrap.childHandler(new ConnectionCountHandler());

        /**
         *  繫結100個埠號
         */
        for (int i = 0; i < nPort; i++) {
            int port = beginPort + i;
            bootstrap.bind(port).addListener((ChannelFutureListener) future -> {
                System.out.println("bind success in port: " + port);
            });
        }
        System.out.println("server started!");
    }
}
/-------------------------------------------------------------------------------------------------
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Sharable
public class ConnectionCountHandler extends ChannelInboundHandlerAdapter {
  //jdk1.5 併發包中的用於計數的類
    private AtomicInteger nConnection = new AtomicInteger();

    public ConnectionCountHandler() {
           /**
         *  每兩秒統計一下連線數
         */
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
            System.out.println("connections: " + nConnection.get());
        }, 0, 2, TimeUnit.SECONDS);

    }
   /**
     *  每次過來一個新連線就對連線數加一
     * @param ctx
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        nConnection.incrementAndGet();
    }
   /**
     *  埠的時候減一
     * @param ctx
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        nConnection.decrementAndGet();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        Channel channel = ctx.channel();
        if(channel.isActive()){
            ctx.close();
        }
        //……
    }
}

netty客戶端程式碼

netty maven
<properties>
<netty-all.version>4.1.6.Final</netty-all.version>
</properties>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty-all.version}</version>
</dependency>

@SpringBootApplication
public class NettyclientApplication {
    private static final int BEGIN_PORT = 8000;
    private static final int N_PORT = 100;
    public static void main(String[] args) {
        SpringApplication.run(NettyclientApplication.class, args);
        new Client().start(BEGIN_PORT, N_PORT);
    }
}
//----------------------------------------------------------------------------------------
package com.nettyclient.test;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class Client {

    private static final String SERVER_HOST = "127.0.0.1";

    public void start(final int beginPort, int nPort) {
        System.out.println("client starting....");
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        final Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup);
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.option(ChannelOption.SO_REUSEADDR, true);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) {
            }
        });
        int index = 0;
        int port;
        while (!Thread.interrupted()) {
            port = beginPort + index;
            try {
                ChannelFuture channelFuture = bootstrap.connect(SERVER_HOST, port);
                channelFuture.addListener((ChannelFutureListener) future -> {
                    if (!future.isSuccess()) {
                        System.out.println("連線失敗, 退出!");
                        System.exit(0);
                    }
                });
                channelFuture.get();
            } catch (Exception e) {
            }
            if (++index == nPort) {
                index = 0;
            }
        }
    }
}

測試

啟動服務端

 

啟動客戶端

 

測試發現當連線數達到13136 的時候,此時達到了最大的連線數,這時候伺服器將不再對新的連線進行處理,客戶端贏長時間得不到服務端的響應而結束與服務端的連線。(不同的機器配置結果可能不同)
下面通過優化要突破這個連線數。

八月 25, 2018 9:29:41 上午 io.netty.channel.DefaultChannelPipeline onUnhandledInboundException
警告: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.

 

優化

1、區域性檔案控制代碼限制

 

一個jvm程序最大能夠開啟的檔案數.png

修改65535的這個限制
vi /etc/security/limits.conf
在檔案末尾新增兩行

*hard nofile 1000000
soft nofile 1000000
soft和hard為兩種限制方式,其中soft表示警告的限制,hard表示真正限制,nofile表示開啟的最大檔案數。整體表示任何使用者一個程序能夠開啟1000000個檔案。注意語句簽名有
號 表示任何使用者


shutdown -r now 重啟linux
再次檢視


已經修改生效了。
測試

 

2、突破全域性檔案控制代碼的限制

cat /proc/sys/fs/file-max
file-max 表示在linux 中最終所有x執行緒能夠開啟的最大檔案數

 

 

修改這個最大值:
sudo vi /etc/sysctl.conf
在檔案的末尾新增 fs.file-max=1000000
然後讓檔案生效 sudo sysctl -p
這個時候再檢視一下全域性最大檔案控制代碼的數已經變成1000000了

 

 

測試

注: 測試的伺服器型號

 

cpu 相關配置

 

 

感興趣可以加Java架構師群獲取Java工程化、高效能及分散式、高效能、深入淺出。高架構。效能調優、Spring,MyBatis,Netty原始碼分析和大資料等多個知識點高階進階乾貨的直播免費學習許可權 都是大牛帶飛 讓你少走很多的彎路的 群..號是:855801563 對了 小白勿進 最好是有開發經驗

注:加群要求

1、具有工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。

3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。

5.阿里Java高階大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!