2014-03-03 77 views
7

在n个线程之间分配传入连接是否有利,每个线程都有自己的独立NIO Selector,其中n是服务器中的核心数量?假设我正在编写一个服务器,它应该处理一些客户端连接。我可以有这样的事情:多线程中的多个选择器

selector.select(); 
Iterator<SelectionKey> i = selector.selectedKeys().iterator(); 

while (i.hasNext()) { 
    SelectionKey key = i.next(); 
    i.remove(); 

    if (!key.isValid()) 
     continue; 

    if (key.isAcceptable()) { 
     // get one of the n selectors (I'd have one per core) 
     Selector chosenSelector = getRandomSelector(); 

     // delegate the new connection to the chosen selector 
     SocketChannel newChannel = key.channel.accept(); 
     newChannel.configureBlocking(false); 
     newChannel.register(chosenSelector, SelectionKey.OP_READ); 
    } 
} 

你们觉得这有道理吗?我的意思是,运行n个线程,每个都有不同的选择器?或者我应该坚持只有一个选择器线程来处理所有连接的OP_READ?或者也许别的东西?

+0

保留不同客户端之间的消息顺序已经足够困难了,而不会故意使其更难。你打算如何完成它?时间戳? – EJP

+0

我想订购邮件是一个正交的问题......一旦我收到它们,某人(一个线程)检查时间戳,并确保它们按正确的顺序处理,没有间隙。但我想我在这里问一些更基本的东西。 –

回答

2

不,它不是有益的,因为需要处理的代码与IO操作所需的时间之间的关系可以忽略不计。尤其是如果您考虑需要额外的时间来同步碎片数据。然而,在单独的线程中处理接收到的数据是有益的。

所以基本上:有一个单线程选择器循环,将数据从一个缓冲区复制到一个任务缓冲区中,以便在单独的线程中进行进一步处理,然后在Executor中启动带有该任务缓冲区的Runnable来处理复制数据。

+0

如果每个线程都会拿起可读的密钥,反序列化消息,然后才将反序列化的对象排入单个消息队列中供服务器处理,该怎么办?假设这样的队列实际上是必需的,那么多线程选择不会有用吗? –

+0

你需要从网卡的角度来看这个:你只有一个,所以线程无法加速从一个数据源读取数据。另外,主线程需要等到所有的读/写操作完成,因为在下一次选择之前需要完成键,所以这里也没有增益。所有在一起,你会有一个复杂的解决方案,速度比单线程少。如果你想使用线程,在'channel.read'之后启动,但不要在之前启动。 – TwoThe

+2

由于这让我困惑了一些,所以我写了一个测试程序来测量选择循环实际上在做什么,结果证实了我的建议。当处于最大负载时,循环处理约90k IOs /秒,但它仍然只能处理大约1.5个按键/选择循环。因此,线程循环不会有太大的好处。 – TwoThe

0

我只有一个选择器,并通过无锁环形缓冲区将消息分配到固定数量的线程中。然后,您可以以完全无锁,超快速的方式获得流量。该流程是这样的:

关键选择=> DEMUX =>工作线程=> MUX =>关键选择

你只需要确保你的工作线程足够数量(和你有足够的自由核心)来快速处理你的消息,否则你可能会得到一个完整的DEMUX,选择器将不得不阻止或丢弃消息。

我建议您阅读this article以了解异步,单线程,非阻塞网络框架的工作原理。当然你也可以检查Netty或Mina。有关使用demux和mux(纳秒)必须支付的延迟概念,请参阅this benchmark

+1

'异步'和'非阻塞'不是同义词。你不需要同时使用它们。他们指的是两种截然不同的I/O模型,阻止是第三种。 Java已经有了这样一个内置的库。 – EJP

+0

@EJP我没有说或认为他们是同义词。我认为异步和非阻塞是两件不同的事情。如何在没有非阻塞的情况下进行异步操作?也许你可以清晰?我知道Java有'java.nio',但它是选择器的基本骨架。然后你在它上面有Netty,Mina,CoralReactor和其他框架。顺便说一下,我编辑了我的答案,以包含您使用demux/task-buffer的建议。我完全同意。 – rdalmeida

+1

您的文章没有任何关于异步I/O的内容。 '非阻塞'表示操作立即完成或失败。'异步'表示操作在后台继续,而实际的异步I/O方法返回到您的代码。他们甚至没有什么关系。由于我认为1.7除了非阻塞和多路复用之外,Java NIO还有很多异步通道。 – EJP

-1

我不太确定当前答案来自哪里。我在想这个问题是经过编辑的。

在n个线程之间分配传入连接是否有利,每个线程都有自己独立的NIO Selector,其中n是服务器内核的数量?

你在这里描述的是基本的工人模型。每个核心通常有2名工人。每个工人都有一个Selector。这个模型在Windows上是绝对必要的,因为每个Selector只能处理1024个套接字,并且JDK对性能造成灾难性的影响。

螺纹 - >选择器 - > OP_READ/OP_WRITE - >使冰沙

这是反应器图案的基本变化。

重要的是单个SocketChannel只附加到一个单独的Selector。所以不要去拨打SocketChannel.register()到一堆Selectors,并且听他们的OP_READ因为你不会获得任何好处。

http://man7.org/linux/man-pages/man7/epoll.7.html

Q2 2种epoll的情况下,可以等待同一文件描述符?如果 那么,事件是否报告给两个epoll文件描述符?

A2是的,事件会报告给两者。但是,可能需要仔细编程 才能正确执行此操作。

+1

没有'SocketChannel.attach()'这样的方法。这里没有证据表明“基础反应堆模式”。什么是“制作冰沙”?什么是最后引用的报价? – EJP

+0

是的,它是SocketChannel.register - 我编辑了回复。对于使用此模式的请求响应情况,可以大大减少缓存缺失。我的理解是,米娜和内蒂都是这样工作的,但我必须稍后再验证一下。 –