单机百万连接调优

实现单机的百万连接,瓶颈有以下几点:

* 如何模拟百万连接
* 突破局部文件句柄的限制
* 突破全局文件句柄的限制
在linux系统里面,单个进程打开的句柄数是非常有限的,一条TCP连接就对应一个文件句柄,而对于我们应用程序来说,一个服务端默认建立的连接数是有限制的。

如何模拟百万连接




如上图所示,当服务端开启一个端口,客户端去连接,除去固定的端口,最多只能实现单机6W的连接,实现单机百万连接,最简单的方法,就是启动十几个客户端,然后去连接同一个端口,但是比较麻烦的。


在服务端启动800~8100,而客户端依旧使用1025-65535范围内可用的端口号,让同一个端口号,可以连接Server的不同端口。这样的话,6W的端口可以连接Server的100个端口,累加起来就能实现近600W左右的连接,TCP是以一个四元组概念,以原IP、原端口号、目的IP、目的端口号来确定的,所以TCP连接可以如此设计。

突破局部文件句柄的限制

* ulimit -n
* /etc/security/limits.conf
首先在终端输入ulimit
-n,查看一个进程能够打开的最大文件数,一条TCP连接,对应Linux系统里面是一个文件,所以服务端最大连接数会受限于这个数字,然后,在/etc/security/limits.conf文件中配置如下两行:

* hard nofile 1000000
* soft nofile  1000000
soft和hard为两种限制方式,其中soft表示警告的限制,hard表示真正限制,nofile表示打开的最大文件数。

突破全局文件句柄的限制

* cat /proc/sys/fs/file-max
* etc/sysctl.conf
cat
/proc/sys/fs/file-max查看我所有进程能够打开的最大文件数是多少,TCP连接,每一个连接代表一个文件,局部的不能大过全局的限制,然后进入etc/sysctl.conf,在该配置文件中添加fs.file-max
= 1000000,file-max表示全局文件句柄数的限制,这里我设置为100W。然后,我通过简单DEMO进行模仿连接,最终结果大约在94W左右。

实例代码

Client端
public class Client { private static final String SERVER_HOST =
"192.168.1.42"; public static void main(String[] args) { new
Client().start(BEGIN_PORT, N_PORT); } 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("connect failed, exit!");
System.exit(0); } }); channelFuture.get(); } catch (Exception e) { } if
(++index == nPort) { index = 0; } } } }
Server端
public final class Server { public static void main(String[] args) { new
Server().start(BEGIN_PORT, N_PORT); } 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!"); } }
其中,BEGIN_PORT 和 N_PORT分别为8000和100。

Handler
@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(); } }

上述简单DEMO,用于利用端口进行的模拟百万连接,然后各位看官按照,我后面介绍的突破局部文件句柄和全局文件句柄,则能基本实现百万连接,具体连接数最终受限于你个人所用的电脑硬件配置。

Netty应用级别性能调优


在一般Netty项目中,如果在channelHandler里面做一些业务复杂操作,比如数据库或者网络操作,通常情况下,请求比较快,在百分之十或者百分之一左右,这操作是非常耗时的,这时候,需要把这操作放在单独的线程池去处理,调整线程数的大小应该是我们首先想到的方法,但线程数的大小最终也将会存在一个上限。

接下来,我简单概述下Netty应用级别的调优方式:

*
第一种方式,在handler里面,自己创建线程池,在执行具体代码的时候,只需要针对特定代码到线程池去处理,而其他操作仍可以在netty提供的线程池里完成。
*
另一种方式,在添加handler的时候,直接指定一个线程池,而不需要在handler里面指定一个线程池,对业务代码是无侵入的,但方法里的每行操作都是在单独的线程池里面,假若在该线程池里的某个方法内做内存分配,则就只在该业务线程池里进行分配,无法做到内存的共享。
 

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信