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 来源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。

相关文章
相关标签/搜索