实现单机的百万连接,瓶颈有以下几点:
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高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!