按定义的顺序处理一组相关的消息,不需阻止对其他消息组的处理。
上下文和问题
应用程序通常需要按照消息到达的顺序处理一系列的消息,同时仍然能够横向扩展来处理增加的负载。 在分布式体系结构中,按顺序处理这些消息并不简单,因为辅助角色可以使用竞争使用者模式独立缩放且经常独立拉取消息。
例如,订单跟踪系统接收包含订单以及与这些订单相关的操作的账本。 这些操作可以是创建订单、将事务添加到订单、修改过去的事务,或者删除订单。 在此系统中,必须以先进先出 (FIFO) 的方式执行操作,但只能在订单级别执行。 但是,初始队列接收一个包含多个订单的事务账本,这些事务可能会交错。
解决方案
将相关消息推送到队列系统中的类别中,并让队列侦听器一次只锁定和拉取一个类别中的一条消息。
一般的顺序保护模式如下所示:
在队列中,不同类别的消息可能会交错,如下图所示:
问题和注意事项
在决定如何实现此模式时,请考虑以下几点:
- 类别/缩放单元。 可以横向扩展传入消息的哪些属性? 在订单跟踪方案中,此属性是订单 ID。
- 吞吐量。 目标消息吞吐量是多少? 如果此数值非常大,可能需要重新考虑你的 FIFO 要求。 例如,能否强制执行开始/结束消息,按时间排序,然后发送批进行处理?
- 服务功能。 你所选的消息总线是否允许在某个队列或某个类别的队列中对消息进行一次处理?
- 可进化性。 你将如何在系统中添加新的消息类别? 例如,假设上面所述的账本系统特定于一个使用者。 如果需要加入新客户,是否可以有一组按客户 ID 分配工作的账本处理器?
- 由于发送消息时的网络延迟不一,使用者有可能不按顺序收到消息。 请考虑使用序列号来验证顺序。 还可以在事务的最后一条消息中包含一个特殊的“序列结束”标记。 流处理技术(如 Spark 或 Azure 流分析)可以在一个时间窗口内按顺序处理消息。
何时使用此模式
在以下情况下使用此模式:
- 如果你的消息按顺序到达,则必须按同样的顺序处理。
- 到达的消息被或可以按这样一种方式“分类”,即类别成为系统的规模单位。
此模式可能不适用于:
- 极高的吞吐量方案(数百万条消息/分钟或秒),因为 FIFO 要求限制系统可以完成的缩放。
工作负荷设计
架构师应评估如何在其工作负载的设计中使用“顺序保护模式”,以解决 Azure Well-Architected Framework 支柱中涵盖的目标和原则。 例如:
支柱 | 此模式如何支持支柱目标 |
---|---|
可靠性设计决策有助于工作负荷在发生故障后复原,并确保它在发生故障后恢复到正常运行状态。 | 此模式可以消除难以排除故障的争用条件、有争议的消息处理或其他解决方法,以解决可能导致故障的排序不正确的消息。 - RE:02 关键流 - RE:07 后台作业 |
与任何设计决策一样,请考虑对可能采用此模式引入的其他支柱的目标进行权衡。
示例
在 Azure 上,可以通过使用 Azure 服务总线消息会话来实现此模式。 对于使用者,你可以使用有服务总线 peek-lock 连接器的逻辑应用,或是有服务总线触发器的 Azure Functions。
对于前一个订单跟踪示例,按照收到的顺序处理每个账本消息,并将每个事务发送到另一个队列(其中类别设置为订单 ID)。 在此情况下事务永远不会跨越多个订单,因此使用者将并行处理每个类别,但在该类别中以 FIFO 的方式进行。
账本处理器通过对第一队列中每个消息的内容进行去批处理来扩散消息:
账本处理器负责以下内容:
- 一次处理账本的一个事务。
- 设置消息的会话 ID 来匹配订单 ID。
- 将每个账本事务发送到辅助队列(其会话 ID 设置为订单 ID)。
使用者侦听辅助队列,在此按队列中的顺序处理具有匹配订单 ID 的所有消息。 使用者使用 peek-lock 模式。
考虑可伸缩性时,账本队列是首要瓶颈。 发布到账本的不同事务可以引用相同的订单 ID。 但是,在无服务器环境中,可以在账本后将消息扩散到订单数。
后续步骤
实现此模式时,以下信息可能相关: