分布式消息队列设计评估
基于分布式消息队列的功能和非功能要求评估所提议的系统设计。我们完成了分布式消息队列的设计过程,现在让我们分析设计是否符合分布式消息队列的功能和非功能要求。
功能要求
队列创建和删除: 当前端接收到队列的请求时,通过一些必要的检查,使用客户端提供的所有必要细节创建队列。相应的集群管理器将服务器分配给新创建的队列,并通过元数据服务更新元数据存储和缓存中的信息。 同样,当客户端不再需要队列时,队列将被删除。负责的集群管理器会取消队列占用的空间,并从所有元数据存储和缓存中删除数据。
相关信息
问题:
当消费者在最大处理尝试次数后无法处理消息时,我们如何处理这些消息?
回答:
可以提供一种特殊类型的队列,称为死信队列,来处理消费者在最大处理尝试次数后无法消费的消息。这种类型的队列也用于存储无法成功处理的消息,原因可能是以下因素:
消息原本应该发送到的队列已经不存在了。
虽然在我们当前的设计中这种情况很少发生,但超出了队列长度限制。
消息由于单个消息的存活时间(TTL)而过期。
死信队列还重要用于确定失败的原因和识别系统中的故障。
发送和接收消息: 生产者可以在创建特定队列后向队列传递消息。在后端,接收到的消息根据时间戳排序以保留它们的顺序,并放置在队列中。同样,消费者可以从指定的队列中检索消息。
当针对特定队列的消息从生产者那里收到时,前端会识别出主机或集群位置(取决于复制模型),这里队列存储。然后将请求转发到相应的实体并放入队列中。
- 消息删除: 主要有两个选项用于从队列中删除消息。
- 第一种是在消息被消耗后不删除消息。但是,在这种情况下,消费者负责跟踪已消耗的内容。为此,我们需要在队列中维护消息的顺序并跟踪队列中的消息。当满足过期条件时,作业可以删除消息。Apache Kafka大多使用这个想法,其中多个进程可以消耗一条消息。
- 第二种方法在消息被消耗后也不删除消息。但是,通过属性(例如,visibility_timeout)将其隐藏一段时间,以使其他消费者无法获取已经被消费的消息。然后,消费者通过API调用删除消息。
在这两种情况下,消费者检索的消息仅由消费者删除。原因是在某些故障情况下消费者无法处理消息,因此提供高耐久性。在这种情况下,在删除调用缺失的情况下,当消息回来时消费者可以再次检索消息。
此外,此方法还提供至少一次交付语义。例如,当工人无法处理消息时,另一个工人可以在消息再次出现在队列中时检索该消息。提供至少一次传递语义的原因在于系统的可靠性。
警告
问题:
当特定消息的可见性超时到期,消费者仍在处理消息时会发生什么情况?
答案:
消息变为可见状态,另一个工作程序可以接收到该消息,由此会导致处理的重复。为避免这种情况,我们确保应用程序设置可靠的可见性超时阈值。
非功能要求
持久性: 为了实现持久性,队列的元数据会在不同节点上进行复制。同样,当接收到消息时,它会被复制到驻留在不同节点上的队列中。因此,如果一个节点失败,其他节点可以用于传递或检索消息。
可扩展性: 我们的设计组件,如前端服务器、元数据服务器、缓存、后端集群等都是横向可扩展的。我们可以增加或删除它们的容量来匹配我们的需求。可扩展性可以分为两个纬度:
消息数量增加: 当消息数量达到特定限制时,例如 80%,指定的队列会扩展。同样地,当消息数量低于某个特定阈值时,队列会收缩。
队列数量增加: 随着队列数量的增加,需要更多的服务器,这时集群管理器负责添加额外的服务器。我们启用节点以便在不同队列之间进行性能隔离。一个队列的负载增加不应影响其他队列。
可用性: 我们的数据组件,元数据和实际消息,在数据中心内部或外部适当地进行复制,并且负载均衡器可以在故障节点周围路由流量。这些机制共同确保我们的系统在故障下仍然可用进行服务。
性能: 为了提高性能,我们使用缓存、数据复制和分区,从而减少数据读写时间。此外,使用尽力而为的消息排序策略可以在必要时增加吞吐量并降低延迟。在严格排序的情况下,我们也建议使用基于时间窗口的排序来潜在地降低延迟。
结论
我们讨论了在分布式环境中设计FIFO队列的许多微妙之处。我们看到严格消息生产、消息提取顺序和可实现的吞吐量和延迟之间存在一个折衷。松散的排序给我们带来了更高的吞吐量和更低的延迟。要求严格排序会迫使系统执行额外的工作来执行挂钟或基于因果关系的排序。我们使用不同的数据存储库进行适当的复制和分区来处理数据扩展。这个设计练习强调了一个简单的生产-消费队列构造在单个操作系统基础系统中容易实现,在分布式设置中变得更加困难。