我是靠谱客的博主 善良高山,最近开发中收集的这篇文章主要介绍Netty实现单机百万连接和应用级别性能调优突破局部文件句柄限制突破全局文件句柄限制Netty应用级别的性能调优,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

 

 

Netty实现单机百万连接和应用级别性能调优

如何模拟Ntty百万连接

通常启动一个服务端会绑定一个端口例如 8000,当客户端去连接端口是有限制的最大是65535加上默认1024及以下的端口,就剩下1024-65535再扣除一些常用的端口,实际可用端口只有6W左右。那么如何实现单机百万连接?

我们在服务端启动例如 8000-8100 这100个端口,100*6W这样就可以连接600W左右的连接,这里是TPC的一个基础的知识,虽然对于客户端来说是同一个端口号,但是对于Server是不同的端口号,由于TCP是一个私源组概念,也就是说它是由源IP源端口号加上目的IP目的端口号为确定的,当你源IP和源端口号是一样的但是你目的端口号不一样那么最终系统底层会把它当做两条TCP连接来处理,所以说我们这里取了个巧开了Server开启了100个端口号,这就是我们单机百万连接的准备工作,接下来看一下代码的实现

 

统计时间Hanlder

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

}
public class Constant {
    static final int BEGIN_PORT = 8000;
    static final int N_PORT = 100;
}

 

服务端

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!");
    }
}


       

 

客户端

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打包到centos6.5系统上   配置是 CPU 4核 8G

同样我们将程序 Client 打包另一台到centos6.5系统上  配置是 同样CPU 4核 8G

然后我们在同目录下创建一个叫start.sh的程序

java -jar server.jar 

-Xms6.5

-Xmx6.5
-XX:NewSize=5.5g
-XX:MaxnewSize=5.5g
-XX:MaxDirectMemorySize=1g

给JVM配置 6.5个G的堆内存和新生代5.5个G

然后我们 ./start.sh启动服务端

服务端会打印出Log

bind success in port:8000

bind success in port:8099 的log

当前连接数为0

connections:0

然后运行客户端 ./start.sh

此时回到服务端查看输出的日志是 connections:870一直就是870,

也就是服务端最大支持到870个连接。

突破局部文件句柄限制

首先我们在server端的服务器上输入命令

ulimit -n

会出现1024的数字,表示linux一个进程能够打开的最文件数,由于一个TCP连接会对应linux系统的一个文件,所以我们就是受限于这个文件最大文件限制,为什么服务端连接数是870那,是因为除了连接数还有JVM打开的文件是我们的class类,1024扣去我们JVM打开的文件数剩下就是我们TCP的连接数。

接下来我们来突破这个限制

sudo vi /etc/security/limits.conf

 

然后在这个文件末尾加上两行

* hard nofile 1000000

* soft nofile   1000000

然后我们重启server服务器

 

最终连接数停留在 9391

这是为什么那,肯定是还有地方限制了连接数

突破全局文件句柄限制

cat /proc/sys/fs/file-max

通过上面这个命令可以看到全局的限制

看到我们这个liunx内核的限制是1W,不同的linux内核或者发行版本是不一样的,现在我们需要突破这个限制

首先我们切换为root用户不然没有权限

sudo  -s
echo 2000> /proc/sys/fs/file-max
exit

我们首先改成20000个我们测试一下

然后我们继续试验 启动我们的Server端 然后在启动我们的客户端

最终停留在19356个连接数

使用echo 来配置的  的话重启服务器就会 echo 2000> /proc/sys/fs/file-max 就会失效 还会变回原来的 10000

我们需要修改 /etc/sysctl.conf

sodu vi /etc/sysctl.conf

我们在文件末尾加上

fs.file-max=1000000

接下来我们重启 liunx服务器

然后我们启动服务器 然后启动客户端

突破10W

最终连接数定格在 94W左右

然后我们看一下什么原因

输入一下命令 htop 查看一下

我们4个CPU都接近100%了所以没办法创建了,受限于我本机的机器性能。

Netty应用级别的性能调优

首先我们标准的应用程序代码

服务端

public class Server {

    public static void main(String[] args) {

        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 ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) {
                ch.pipeline().addLast(new FixedLengthFrameDecoder(Long.BYTES));
                ch.pipeline().addLast(ServerBusinessHandler.INSTANCE);
//                ch.pipeline().addLast(ServerBusinessThreadPoolHandler.INSTANCE);
            }
        });


        bootstrap.bind(PORT).addListener((ChannelFutureListener) future -> System.out.println("bind success in port: " + PORT));
    }

}
@ChannelHandler.Sharable
public class ServerBusinessHandler extends SimpleChannelInboundHandler<ByteBuf> {
    public static final ChannelHandler INSTANCE = new ServerBusinessHandler();


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
        ByteBuf data = Unpooled.directBuffer();
        data.writeBytes(msg);
        Object result = getResult(data);
        ctx.channel().writeAndFlush(result);
    }

    protected Object getResult(ByteBuf data) {
        // 90.0% == 1ms
        // 95.0% == 10ms  1000 50 > 10ms
        // 99.0% == 100ms 1000 10 > 100ms
        // 99.9% == 1000ms1000 1 > 1000ms
        int level = ThreadLocalRandom.current().nextInt(1, 1000);

        int time;
        if (level <= 900) {
            time = 1;
        } else if (level <= 950) {
            time = 10;
        } else if (level <= 990) {
            time = 100;
        } else {
            time = 1000;
        }

        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
        }

        return data;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // ignore
    }
}

客户端

public class Client {

    private static final String SERVER_HOST = "127.0.0.1";

    public static void main(String[] args) throws Exception {
        new Client().start(PORT);
    }

    public void start(int port) throws Exception {
        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) {
                ch.pipeline().addLast(new FixedLengthFrameDecoder(Long.BYTES));
                ch.pipeline().addLast(ClientBusinessHandler.INSTANCE);
            }
        });

        for (int i = 0; i < 1000; i++) {
            bootstrap.connect(SERVER_HOST, port).get();
        }
    }
}
@ChannelHandler.Sharable
public class ClientBusinessHandler extends SimpleChannelInboundHandler<ByteBuf> {
    public static final ChannelHandler INSTANCE = new ClientBusinessHandler();

    private static AtomicLong beginTime = new AtomicLong(0);
    private static AtomicLong totalResponseTime = new AtomicLong(0);
    private static AtomicInteger totalRequest = new AtomicInteger(0);

    public static final Thread THREAD = new Thread(() -> {
        try {
            while (true) {
                long duration = System.currentTimeMillis() - beginTime.get();
                if (duration != 0) {
                    System.out.println("qps: " + 1000 * totalRequest.get() / duration + ", " + "avg response time: " + ((float) totalResponseTime.get()) / totalRequest.get());
                    Thread.sleep(2000);
                }
            }

        } catch (InterruptedException ignored) {
        }
    });

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ctx.executor().scheduleAtFixedRate(() -> {

            ByteBuf byteBuf = ctx.alloc().ioBuffer();
            byteBuf.writeLong(System.currentTimeMillis());
            ctx.channel().writeAndFlush(byteBuf);

        }, 0, 1, TimeUnit.SECONDS);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
        totalResponseTime.addAndGet(System.currentTimeMillis() - msg.readLong());
        totalRequest.incrementAndGet();

        if (beginTime.compareAndSet(0, System.currentTimeMillis())) {
            THREAD.start();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // ignore
    }
}
public class Constant {
    public static final int PORT = 8000;
}

上面的代码主要模拟了,Netty真实业务环境下的处理耗时和QPS

我们启动服务器和客户端查看一下log

真实的环境下我们的耗时时间越来越长

首先我们服务端的Hander getResult方法就是我们模拟真是环境下的 例如 数据库读写,网络请求等等耗时的操作,

最终性能都是在getResult这里被堵住了。

Object result =getResult(data);

ctx.channel().wrteAndFlush(result);

这两行代码是把它扔到我们业务线程池里,业务线程池里面可以不断在后台运行运行完了给我们返回结果。

我们改造一下代码

在Server端代码中 

 

 

我们新建一个Hander

public class ServerBusinessThreadPoolHandler extends ServerBusinessHandler {
    public static final ChannelHandler INSTANCE = new ServerBusinessThreadPoolHandler();
    private static ExecutorService threadPool = Executors.newFixedThreadPool(1000);


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
        ByteBuf data = Unpooled.directBuffer();
        data.writeBytes(msg);
        threadPool.submit(() -> {
            Object result = getResult(data);
            ctx.channel().writeAndFlush(result);
        });

    }
}
 ch.pipeline().addLast(ServerBusinessThreadPoolHandler.INSTANCE);

然后我们在Server端添加注册这个Hander 

注释掉原来的Hander

然后我们启动程序查看性能

最终耗时稳定在 250ms左右

       // 90.0% == 1ms
        // 95.0% == 10ms  1000 50 > 10ms
        // 99.0% == 100ms 1000 10 > 100ms
        // 99.9% == 1000ms1000 1 > 1000ms

接下来继续调整,我们调整Hander的线程个数 我们吧业务线程调整到 20个线程

然后我们启动程序

现在我们QPS 1000左右 耗时在 18毫秒左右

继续观察,由于有时候会被其他线程耗时堵着就会造成整体时间拉长。

现在稳定在30-40毫秒左右

继续调整线程数现在我们吧线程数调整为30

然后我们启动程序观察

这次耗时最终稳定在 15毫秒左右

继续调整把线程数调为50

耗时稍微高一点。

具体的线程数我们需要在真实的环境下不断的调整来测试,这个需要我们一直不断的调整来测试,最终才能找到最合适的。

最后

以上就是善良高山为你收集整理的Netty实现单机百万连接和应用级别性能调优突破局部文件句柄限制突破全局文件句柄限制Netty应用级别的性能调优的全部内容,希望文章能够帮你解决Netty实现单机百万连接和应用级别性能调优突破局部文件句柄限制突破全局文件句柄限制Netty应用级别的性能调优所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(47)

评论列表共有 0 条评论

立即
投稿
返回
顶部