Java NIO选择器最多只能选择50个SelectionKeys吗?

4
我会翻译中文。这段内容与编程有关,作者使用 siege 对自己手动构建的文件服务器进行压力测试,对于小文件(小于1KB),它表现得相当好,但是当测试一个1MB文件时,表现并不如预期。
以下是测试小文件的结果:
neevek@~$ siege -c 1000 -r 10 -b http://127.0.0.1:9090/1KB.txt
** SIEGE 2.71
** Preparing 1000 concurrent users for battle.
The server is now under siege..      done.

Transactions:              10000 hits
Availability:             100.00 %
Elapsed time:               9.17 secs
Data transferred:           3.93 MB
Response time:              0.01 secs
Transaction rate:        1090.51 trans/sec
Throughput:             0.43 MB/sec
Concurrency:                7.29
Successful transactions:       10000
Failed transactions:               0
Longest transaction:            1.17
Shortest transaction:           0.00

以下是使用1MB文件进行测试的结果:
neevek@~$ siege -c 1000 -r 10 -b http://127.0.0.1:9090/1MB.txt
** SIEGE 2.71
** Preparing 1000 concurrent users for battle.
The server is now under siege...[error] socket: read error Connection reset by peer sock.c:460: Connection reset by peer
[error] socket: unable to connect sock.c:222: Connection reset by peer
[error] socket: unable to connect sock.c:222: Connection reset by peer
[error] socket: unable to connect sock.c:222: Connection reset by peer
[error] socket: read error Connection reset by peer sock.c:460: Connection reset by peer
[error] socket: unable to connect sock.c:222: Connection reset by peer
[error] socket: read error Connection reset by peer sock.c:460: Connection reset by peer
[error] socket: read error Connection reset by peer sock.c:460: Connection reset by peer
[error] socket: read error Connection reset by peer sock.c:460: Connection reset by peer
[error] socket: read error Connection reset by peer sock.c:460: Connection reset by peer

当以上述错误终止时,我的文件服务器仍然会旋转固定数量的 SelectionKey,即保持返回固定数量,比如50。
通过以上测试,我发现我的文件服务器最多只能接受50个并发连接,因为在使用小文件运行测试时,我注意到服务器选择1或2个SelectionKeys,而在使用大文件运行时,它每次选择50个。
我尝试增加中的但没有帮助。
问题的原因是什么?
编辑
更多信息:
当测试一个1MB文件时,我注意到以错误终止,并且文件服务器仅接受了198个连接,尽管我指定了1000个并发连接x 10轮(1000 * 10 = 10000)来淹没服务器。
编辑2

我已经使用以下代码(单个类)进行测试,以重现同样的问题,在这段代码中,我只接受连接,不读取或写入,siege客户端在连接超时之前以连接重置断开管道错误终止。我还注意到选择器只能选择少于1000个键。你可以尝试下面的代码来观察问题。

public class TestNIO implements Runnable {
    ServerSocketChannel mServerSocketChannel;
    Selector mSelector;

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

    public TestNIO () throws Exception {
       mSelector = Selector.open();
    }

    public void start () throws Exception {
        mServerSocketChannel = ServerSocketChannel.open();
        mServerSocketChannel.configureBlocking(false);
        mServerSocketChannel.socket().bind(new InetSocketAddress(9090));
        mServerSocketChannel.socket().setSoTimeout(150000);
        mServerSocketChannel.register(mSelector, SelectionKey.OP_ACCEPT);

        int port = mServerSocketChannel.socket().getLocalPort();
        String serverName = "http://" + InetAddress.getLocalHost().getHostName() + ":" + port;
        System.out.println("Server start listening on " + serverName);

        new Thread(this).start();

    }

    @Override
    public void run() {
        try {
            Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
            while (true) {
                int num = mSelector.select();

                System.out.println("SELECT = " + num + "/" + mSelector.keys().size());
                if (num > 0) {
                    Iterator<SelectionKey> keys = mSelector.selectedKeys().iterator();

                    while (keys.hasNext()) {
                        final SelectionKey key = keys.next();

                        if (key.isValid() && key.isAcceptable()) {
                            accept(key);
                        }

                    }
                    // clear the selected keys
                    mSelector.selectedKeys().clear();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void accept (SelectionKey key) throws IOException {
        SocketChannel socketChannel = mServerSocketChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.socket().setSoTimeout(1000000);
        socketChannel.socket().setKeepAlive(true);
        // since we are connected, we are ready to READ
        socketChannel.register(mSelector, SelectionKey.OP_READ);
    }
}

我只在有数据需要写入时才选择 OP_WRITE,当我完成向通道写入所有内容后,就会将其清除。Selector 旋转的原因是客户端已经关闭了连接,而我只在读取部分检查“对等方重置连接”。我想我应该在写入部分检测到客户端已经关闭了连接。 - neevek
我的困惑是为什么它可以很好地处理小文件,但对于大文件却不行?看起来它只是无法处理几十个并发连接。 - neevek
据我理解,分块是服务器提供的一种功能,客户端可以设计支持或不支持它。在我的情况下,“siege”客户端只发送每个请求的HTTP头(GET方法),因此分块不太可能是问题所在。无论如何,感谢您的回复。 - neevek
@EJP,你看到的代码不是我最初发布的代码,上面的代码只是为了测试服务器套接字在抛出异常之前可以接受多少连接,因此它既不处理OP_READ也不处理OP_WRITE。对于常量端口,是的,它在代码中并没有太多意义,但在生产环境中,我需要将0作为端口传递,这样就会为我选择一个随机端口,并且我需要从套接字中获取该端口以进行显示。 - neevek
我只能对您发布的代码进行评论。这段代码无限接受套接字,因此会用尽套接字。如果您有其他代码,请在此处发布。 - user207421
显示剩余6条评论
3个回答

1

0

检查打开文件(文件描述符)的ulimit和硬限制

我猜你在使用Linux。您可以查看limits.conf /etc/security/limits.conf


我已经增加了打开文件的限制,我正在使用Mac,ulimit -a 给出以下结果:core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited file size (blocks, -f) unlimited max locked memory (kbytes, -l) unlimited max memory size (kbytes, -m) unlimited open files (-n) 1000000 pipe size (512 bytes, -p) 1 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 709 virtual memory (kbytes, -v) unlimited - neevek

0

这个问题可能与我的代码无关,我在本地运行了相同的测试(MacOSX上运行的nginx服务器),出现了相同的错误。因此,它很可能与硬件或siege客户端有关。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接