Netty 模擬百萬連接

  我們知道單機的端口最多65536,除去系統使用的端口, 留給程序使用的也就6萬個端口, 在需要對單機做長連接壓力測試的時候,如果要測60W的長連接併發,就得找10臺機器,而一般情況下我們並沒有這麼多的空閒機器去做這種規模的測試,那如何用兩臺機器模擬百萬連接呢?對於TCP的連接,系統用一個4四元組來唯一標識:{server ip, server port,client ip,client port}。這裏有兩個變量是固定的, server ip與clinet ip。能做文章的也就是兩臺服務器的端口號了。如果server port 只開啓一個端口的話, 那一臺client最多也就 6W個連接能連上,多了因爲端口的限制無法創建新的連接。如果server端多開幾個端口,根據TCP的唯一標識,我們便能夠模擬超過6W的連接測試了。處面是具體的代碼,項目依賴netty,版本爲4.1.25.Final。

  • server端代碼如下:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;


public final class Server {

    static final int BEGIN_PORT = 10000;
    static final int N_PORT = 100;

    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 ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                                //每個連接都有個ConnectionCountHandler對連接記數進行增加
                ch.pipeline().addLast(new ConnectionCountHandler());
            }
        });

        //這裏開啓 10000到100099這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 java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class ConnectionCountHandler extends ChannelInboundHandlerAdapter {

    //這裏用來對連接數進行記數,每兩秒輸出到控制檯
    private static final AtomicInteger nConnection = new AtomicInteger();

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

}
  • client端代碼
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class Client {

    //服務端的IP
    private static final String SERVER_HOST = "10.200.10.146";

    static final int BEGIN_PORT = 10000;
    static final int N_PORT = 100;

    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;
        //從10000的端口開始,按端口遞增的方式進行連接
        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;
            }
        }
    }
}

在linux機器上測試的時候,如果報了Could not initialize class sun.nio.ch.FileDispatcherImpl,這是因爲系統爲每個TCP連接都要創建一個socket句柄,每個socket句柄同時也是一個文件句柄。解決辦法如下:

  • 修改linux系統進程打開文件數的限制,文件在/etc/security/limits.conf。需要增加的行如下:
# * 表示對於任何用戶, hard表示硬件的限制 , soft表示軟件限制 nofile表示打開文件數
* hard nofile 1000000
* soft nofile 1000000
  • 修改linux系統能夠打開文件的最大限制,文件在: /proc/sys/fs/file-max。需要增加的行如下:
1000000
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章