netty百萬鏈接

實現單機的百萬鏈接,瓶頸有如下幾點:
一、如何模擬百萬鏈接
二、突破局部文件句柄的限制
三、突破全局文件句柄的限制
在linux系統裏面,單個進程打開的句柄數是很是有限的,一條TCP鏈接就對應一個文件句柄,而對於咱們應用程序來講,一個服務端默認創建的鏈接數是有限制的。java

以下圖所示,一般一個客戶端去除一些被佔用的端口以後,可用的端口大於只有6w個左右,要想模擬百萬鏈接要起比較多的客戶端,並且比較麻煩,因此這種方案不合適。linux

 

image.pngspring

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

 

image.pngspringboot

測試環境

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>jvm

@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;
            }
        }
    }
}

測試

啓動服務端socket

 

image.pngmaven

 

啓動客戶端

 

最大鏈接數一萬多.png


測試發現當鏈接數達到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.

 

image.png

優化

一、局部文件句柄限制

一個jvm進程最大可以打開的文件數.png

修改65535的這個限制
vi /etc/security/limits.conf
在文件末尾添加兩行

*hard nofile 1000000
soft nofile 1000000
soft和hard爲兩種限制方式,其中soft表示警告的限制,hard表示真正限制,nofile表示打開的最大文件數。總體表示任何用戶一個進程可以打開1000000個文件。注意語句簽名有
號 表示任何用戶

image.png


shutdown -r now 重啓linux
再次查看

image.png


已經修改生效了。
測試

最大鏈接數10萬多.png

 

二、突破全局文件句柄的限制

cat /proc/sys/fs/file-max
file-max 表示在linux 中最終全部x線程可以打開的最大文件數

 

image.png

 

修改這個最大值:
sudo vi /etc/sysctl.conf
在文件的末尾添加 fs.file-max=1000000
而後讓文件生效 sudo sysctl -p
這個時候再查看一下全局最大文件句柄的數已經變成1000000了

 

image.png

測試

最大鏈接數36萬多.png

注: 測試的服務器型號

 

image.png

 

cpu 相關配置

 

image.png

做者:s_j_x 連接:https://www.jianshu.com/p/490e2981545c 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

相關文章
相關標籤/搜索