从严格意义上讲,我怀疑“去抖动”在这里并不完全正确,因为它在技术上指的是避免将预期的单个动作误解为多个动作(按音轨前进⏭一次,但无意中跳过两首歌曲而不是只是一个)因为按钮“弹回” - 而您想要避免的不是真正的重复消息,而是不必要的闲聊,可以合并为一个动作,代表一组触发器中最新或最相关的动作。
当然,这很像去抖动,因为它从根本上归结为“记住”你最后一次做某件事的任务,并且在特定时间过去之前不允许相同或相似的事情再次发生。
您可能正在寻找的相关数据结构是关联数组,它有许多其他名称,如哈希映射并存储键(如作业标识符)和值(如您触摸作业时的最后时间戳(触发事件) .
但是当您意识到下一件事并决定不立即采取行动时,它会变得有点棘手,但是您无法保证是否或何时可能会有更多后续相关的事情,因此您需要计时器以便您不要留下最后一件事,或不恰当地拖延它。并且您需要在已知完成特定作业时清理内存结构,并定期扫描它以查找废弃的键,以处理由于任何原因从未正确完成的作业的情况......否则您最终会内存不足。而且你必须知道事件的到达顺序,特别是如果有一个最终的“完成”事件,因为在那之后收到的任何东西,无论出于何种原因,都不再相关。
当然,这一切都是可行的,但它可能会变得一团糟,正如你无疑得出的结论。
SQS 中最近添加的一项功能可能在这方面有所帮助:FIFO 队列。
重要提示:冒着被误解的风险,我并不是说 FIFO 队列是灵丹妙药,或者它是必要的或足够的。相反,我建议它具有一些可能有助于实现或简化任务的功能,即使其中一些功能甚至与 FIFO 队列的严格排序的 FIFO 行为没有直接关系。
FIFO 队列中的每条消息都有一个MessageGroupId 属性,它是您在将每条消息发送到队列时指定的不透明字母数字字符串。这可以表示特定的作业 ID——从而确保同一作业的队列消息的分组传递和按顺序传递。有用,不是吗?
从队列接收消息时,您在一批中收到的所有消息都应该(根据文档)是具有相同 MessageGroupId 的消息...因此,如果您将其设置为唯一代表作业的字符串,这意味着如果多条消息对于给定的作业在队列中,您会将它们中的许多或全部放在一起,并且您将按照它们被发送到队列的顺序来获取它们——这意味着您可以查看并可能丢弃除最后一个之外的所有作业,触发通知,从队列中删除消息,然后返回循环顶部并再次轮询 SQS。¹
这似乎大大简化了流程,但不一定完全解决您所描述的问题的核心问题——因为下一批可能是针对同一工作的,因此您几乎会立即发送另一个通知。另一方面,也许这没关系,因为您可能会从队列中为同一作业读取多达 10 条消息²,在一个请求中,您仍然有可能消除多达 90% 的不必要消息。您的工作人员一次只会向远程端点发送一个通知。
如果您遇到一条您现在不想变成事件的消息,您还可以通过更改消息可见性超时来利用另一个 SQS 功能——FIFO 队列显然会改变消息可见性的行为,以便所有具有给定消息的消息MessageGroupId,包括将来具有相同 ID 的消息,作为一个组一起保持不可见。(您需要验证此行为。)
现在,上述内容可能会协同工作以从根本上完成您所需要的,即使这并不是您所要求的全部 - 因为在工作量较低的时候,它可能会比工作量较高的时候传递更多的消息。如果您需要从字面上将外部通知传递限制为每个作业“每 n 秒不超过一次”,事情会变得更复杂一些,因为您肯定需要“记住”上次为给定作业发送消息的时间,所以您可以决定发送另一个消息是否“为时过早”......所以您需要一个数据结构(上面提到的关联数组),您可以在其中“记住”您何时发送每个作业的最后一条消息,以便你可以告诉 SQS 多长时间让一批消息不可见......但不是
¹ 根据您希望在此设计中的积极程度,在外部循环中每次成功的 SQS 长轮询时,如果您收到最大数量的消息,您可能需要考虑可能还有更多消息的事实对于在队列中等待的相同 MessageGroupId,这意味着即使批次中的最后一条消息一定是批次中的最新消息,您收到的最后一条消息不一定是该组的最新消息,因为后面的消息仍然存在在队列中。考虑到这种特殊情况会增加复杂性。不考虑它只会增加您在低流量时生成通知的频率......这可能是简单性的可接受折衷。
² 根据文档,10 仍然是您在单个读取请求中可以接收的最大消息数,即使使用 FIFO 队列也是如此。文档确实表明 FIFO 队列在某种意义上比传统队列更有可能接收尽可能多的消息,因为 SQS 内部的分布式扩展的实现方式有所不同。这需要权衡 300 TPS 的性能限制和 FIFO 队列。由于 SQS 的内部扩展,传统队列实际上具有无限的 TPS,但代价是可能出现乱序和潜在的(但很少见)重复传递消息。