Java NIO - 消息累积

3
我正在编写一个使用 NIO 套接字的 Java 应用程序。它由 3 个服务端和一组客户端组成。客户端可以与服务端通信,而服务端可以与客户端以及其他服务端通信。
服务器与服务器以及客户端与服务器之间会发送已序列化为 byte[] 数组的 Message。每个 Message 的第一个字节包含消息的大小,并且自然保证每个消息不超过 127 (2^8 -1) 字节。你可以将服务器和客户端发送消息的操作视为循环执行:
Message msg = new Message()
while (true) {
    sendMessage(msg, server or client)
    receiveMessage()
}

然后,实现使用了ByteBuffer。像Java NIO实现中的任何一个一样,每个服务器都会执行selector.select(),然后检索SelectionKeys,看看是否需要处理读取(调用handleRead()方法)、写入(调用handleWrite()方法)或接受(调用handleAccept()方法)。所有的handleXX方法都采取有限数量的步骤,不会阻塞等待任何其他内容。
在为特定key检索数据(handleRead())时,我只是将数据存储在特定的map(Map<SelectionKey, List<byte[]>> readDataForKey)中。然后遍历列表并提取所有已接收到的消息。
然而,我注意到有时在为某些键输入handleRead时,有数千条消息要等待处理。我无法弄清楚为什么会这样? 我期望handleRead只能看到一些消息,然后就可以结束了。
有时候会积累数千条消息未被处理。这是什么意思?这意味着我的handleRead或handleWrite或NIO实现的其他部分花费了太长时间,底层缓冲区变满了吗?这意味着偶尔会出现GC甚至(约10毫秒),在此期间缓冲区变满了吗?这意味着我的代码在handleRead中可能很慢,因此消息被积累了吗?
这么多消息积累起来是正常的吗?

1
这意味着您的处理速度太慢了。 - user207421
我会调查一下。谢谢。 另外,是否有可能是由于我在通道方面使用的ByteBuffer大小而导致了累积?例如,如果缓冲区太大,这会使Java在调用处理程序之前填充更多的消息吗? - insumity
你是否因为这些消息积累(例如崩溃、丢失消息等)而遇到了问题,还是仅仅想要理解你所看到的行为? - dimo414
我的性能问题是系统的吞吐量非常低。但我也看不出为什么会积累那么多消息。 - insumity
1
  1. 在消息被消费后,您是否正确更新了地图(簿记)?
  2. 发出有关读取计数、写入计数、读取处理延迟、写入处理延迟和地图大小的指标。这将有助于确定问题所在。
  3. 我们应该检查内存泄漏(无意)。如果您能提供写入/读取的示例代码(没有业务逻辑),那将有所帮助。
- Raghu Kumar
2个回答

1
您的消息非常小,因此发送和接收缓冲区可以包含许多消息。如果您没有明确指定发送和接收缓冲区的大小,则TCP堆栈将尽最大努力优化大小,以便在网络上进行有效传输。虽然大多数Linux发行版的默认大小约为128 KB,但最大大小可能是几兆字节(例如,在延迟非常高的网络上)。因此,单个handleRead很容易看到数百或数千条消息。TCP试图尽可能好地使用您的网络带宽。在接收缓冲区中看到许多消息并不意味着您的接收器已过载。TCP甚至避免使接收器过载。唯一衡量系统是否“过载”的方法是测量发送消息所需的时间(例如,在发送消息之前将其排队并测量其大小)。
有些情况下手动优化缓冲区大小有意义(例如,在延迟非常高的网络上),但大多数情况下TCP堆栈都做得很好。还有非常罕见的情况需要禁用Nagle算法(例如Telnet和SSH)以最小化用户输入的反应时间,但大多数情况下,绝对不需要手动干预。让TCP完成其工作,即优化传输效率并避免接收器过载。

即使在操作系统级别上有一个64KB的缓冲区,也可以在发送任何数据之前包含许多您的消息。 - rmunge
谢谢你的回答。我已经禁用了 Nagle 算法,你认为改变 SO_SNDBUF 缓冲区是否有意义? - insumity
1
我不建议禁用Nagle算法或更改发送缓冲区大小。你的接收器只是超载了。这就是你应该关注的重点。TCP只是避免你过载的接收器引起愚蠢的窗口综合症/低效的传输。虽然这解释了为什么消息会以批量发送(由于它们的小尺寸),但这绝对不是你应该尝试优化的地方。(另请参见https://en.wikipedia.org/wiki/Silly_window_syndrome)。 - rmunge
另一个导致批量消息的原因可能是您的发件人负载过重。在这种情况下,禁用 Nagle 算法可能有所帮助,但使用多个连接肯定是更好的选择。 - rmunge
我已经编辑了我的答案,因为刷新提示是误导性的。希望更新能够澄清事情。 - rmunge

0

我不能评论,所以我在这里添加我的评论...

问题是否发生在
1. 单个客户端和单个服务器?2. 仅服务器(即没有客户端)?

  1. 单个服务器有多个客户端吗?

  2. 关于:

这是否意味着我的 handleRead 或 handleWrite 或 NIO 实现的其他部分花费了太长时间

您可以添加一些日志来测量您在那里花费的时间(或者如果您想进一步使用 MAT 或 VisualVM,这也可能提示您是否由于 GC 而延迟)


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