• 突破局部文件句柄的限制
  • 突破全局文件句柄的限制
  • 在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里面指定一个线程池,对业务代码是无侵入的,但方法里的每行操作都是在单独的线程池里面,假若在该线程池里的某个方法内做内存分配,则就只在该业务线程池里进行分配,无法做到内存的共享。
    • 文章最后发布于: 2018-08-18