实现单机的百万连接,瓶颈有以下几点:
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服务端代码
1 2 3 4 5 6 7 8 9
| 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>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| @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());
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 { private AtomicInteger nConnection = new AtomicInteger();
public ConnectionCountHandler() {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { System.out.println("connections: " + nConnection.get()); }, 0, 2, TimeUnit.SECONDS);
}
@Override public void channelActive(ChannelHandlerContext ctx) { nConnection.incrementAndGet(); }
@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客户端代码
1 2 3 4 5 6 7 8 9
| 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>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| @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 的时候,此时达到了最大的连接数,这时候服务器将不再对新的连接进行处理,客户端赢长时间得不到服务端的响应而结束与服务端的连接。(不同的机器配置结果可能不同)
下面通过优化要突破这个连接数。

优化
1、局部文件句柄限制

一个jvm进程最大能够打开的文件数
修改65535的这个限制
vi /etc/security/limits.conf
在文件末尾添加两行
1 2
| *hard nofile 1000000 *soft nofile 1000000
|
soft和hard为两种限制方式,其中soft表示警告的限制,hard表示真正限制,nofile表示打开的最大文件数。整体表示任何用户一个进程能够打开1000000个文件。注意语句签名有* 号 表示任何用户

shutdown -r now
重启linux
再次查看

已经修改生效了。
测试

最大连接数10万多
2、突破全局文件句柄的限制
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了

测试

最大连接数36万多.png
注: 测试的服务器型号

cpu 相关配置

本文整理自
突破netty单机最大连接数
遵循CC 4.0 BY-SA版权协议