【问题标题】:How to avoid flooding a message queue?如何避免淹没消息队列?
【发布时间】:2023-07-17 23:07:01
【问题描述】:

我正在开发一个应用程序,该应用程序分为瘦客户端和服务器部分,通过 TCP 进行通信。我们经常让服务器向客户端发出异步调用(通知)以报告状态变化。这避免了服务器浪费太多时间等待客户端的确认。更重要的是,它避免了死锁

这样的死锁可能发生如下。假设服务器将同步发送状态更改通知(请注意,这是一个有点构建的示例)。当客户端处理通知时,客户端需要同步向服务器请求信息。但是,服务器无法响应,因为他正在等待对他的问题的回答。

现在,通过异步发送通知可以避免这种死锁,但这会带来另一个问题。当异步调用的速度快于它们的处理速度时,调用队列会不断增长。如果这种情况保持足够长的时间,呼叫队列将完全填满(充斥着消息)。我的问题是:发生这种情况时该怎么办?

我的问题可以总结如下。我是否真的必须选择发送通知而不阻塞(冒淹没消息队列的风险,或者发送通知时阻塞冒引入死锁?有什么技巧可以避免消息队列泛滥吗?

注意:重复一遍,服务器在发送通知时不会停止。它们是异步发送的。

注意:在我的示例中,我使用了两个通信进程,但是两个通信线程存在相同的问题。

【问题讨论】:

  • 它是什么队列 - 一些自定义实现的队列或 Windows 消息队列?
  • 如果有人试图对您的服务器执行此操作,则称为 DDOS(定向拒绝服务)攻击。

标签: multithreading language-agnostic concurrency synchronization


【解决方案1】:

如果您遇到持续的拥塞问题,除了优雅地失败并通知客户端无法发布新消息之外,您几乎无能为力;然后由客户端来维护要发布的消息的积压。

引入优先队列并使用消息过期/过滤可以让您释放队列中的空间,但这实际上只是推迟了问题。如果可能,您还可以聚合消息或忽略重复消息,但问题似乎也不在于队列本身。 (更不用说更复杂的队列逻辑可能会占用宝贵的资源,而这些资源本来可以更好地用于实际处理消息。)

根据服务器端的功能,您可以为长时间计算引入结果散列,将某些类型的消息卸载到专用设备,检查服务器是否等待 I/O 操作的时间过长,以及无数其他技术。如果可能,至少尝试找出导致拥塞的消息。

哦,还有业务解决方案:将预计开发时间的成本与更好的硬件成本进行比较,并得出结论,您应该购买更强大的服务器(或额外的服务器)。

【讨论】:

    【解决方案2】:

    如果服务器正在向客户端发送信息性消息,您自己说这些消息是异步的,它不应该等待来自客户端的回复。如果它们不是信息性的,换句话说,它们需要答案,我会说服务器永远不应该将此类消息发送给客户端,它们的存在表明设计不佳。

    【讨论】:

    • 你是对的,但我正在寻找一种方法来避免淹没消息队列。
    • 究竟是哪个消息队列——Windows实现的线程消息队列?
    • @Dimitri 如果你删除等待的服务器消息,应该不会有洪水。
    • @sharptooth 不,我们在 IPC 库中实现了自己的消息队列。然而,我说的是一般的消息队列,不管它们是如何实现的。
    • 嗯,这是一个不同的问题。如果您的服务器比您的客户端慢,除了丢弃消息之外没有其他解决方案。
    【解决方案3】:

    根据这些消息的重要性,您可能需要查看 Message ExpirationMessage Filter,尽管听起来您的架构可能不正确。

    【讨论】:

    • 我们的架构主要工作如下:服务器异步发送状态更改,瘦客户端使用它来保持用户界面最新。但是,对于某些通知,客户端需要从服务器获取附加信息。你认为这是一种错误的做事方式吗?
    • 你说得对,我的部分问题可以通过过滤队列中的消息来解决。但是,这种过滤会降低性能。此外,它并没有为所有情况提供解决方案。感觉你仍然需要希望队列不会泛滥。
    【解决方案4】:

    我宁愿修复服务器端的逻辑。消息队列不应等待答案。而是有一个状态机,它也可以在等待来自客户端的答案时接收这些信息查询。

    当然你仍然可以淹没你的消息队列,但是使用 TCP 你可以很容易地处理它。

    【讨论】:

    • 不,在我的主要示例中,服务器在发送通知时不会停止(我知道,也许我的问题太混乱了)。
    • TCP 层也存在泛洪问题:如果将数据包添加到 TCP 队列中以 fast,它会泛洪;那该怎么办?
    • 正如我在第二段中所说,是的,它仍然是可能的。但是 TCP 链接的另一端应该有一个数据包超时。如果它没有收到回复,它应该重新发送它。当然,您总是可以淹没一台机器,只需向它发送足够多的请求(如果需要,使用一百个客户端)。
    • 至于不拖延,“但是,服务器无法响应,因为他正在等待他的问题的答案”是什么意思。对我来说,这听起来像是在停滞不前。如果不是,它应该能够响应信息查询,不是吗?
    • (关于停顿问题)是的,您是对的,在我的死锁示例中,服务器正在停顿,因为它同步发送了通知。然而,这个问题是通过异步发送(=非阻塞)来解决的,但是它引入了队列泛滥的问题。
    【解决方案5】:

    我相信,最好的方法是为您的客户端添加另一个状态。这是我从 SMPP 协议规范中借来的。

    向客户端添加一个拥塞状态,它总是检查队列长度,假设这是可能的,因此一旦达到某个阈值,比如 1000 条未处理的消息,客户端就会向服务器发送一条消息,表明它已经拥塞并且服务器将被要求停止所有消息传递,直到它收到指示客户端不再拥塞的通知。

    或者,在服务器端,如果有一定数量的待处理回复,服务器可以简单地停止发送消息,直到客户端回复一定数量的消息。

    这些阈值可以动态计算或固定,具体取决于.....

    【讨论】:

    • 很好的答案!临时安排发件人似乎是最好的选择。但是,我认为这会使应用程序更加复杂:-(
    • 您只需要一点控制工程,这可能很复杂,但从长远来看是值得的。根据您当前的实现,它可能很简单。例如。如果一个服务器线程/进程发送消息而另一个接受回复。而且,例如,您有一个包含所有已发送消息及其各自密钥的数据库,您所要做的就是为回复的消息添加一个字段,如果待处理的回复> x,则让发送者进程休眠,而接收者进程更新收到每条消息时的此字段。