实现单机的百万链接,瓶颈有如下几点:
一、如何模拟百万链接
二、突破局部文件句柄的限制
三、突破全局文件句柄的限制
在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 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 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 来源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。