在SQS队列中使用许多使用者

时间:2016-05-26 22:35:21

标签: queue message-queue amazon-sqs

我知道可以使用多个线程来使用SQS队列。我想保证每条消息都会消耗一次。我知道可以更改消息的可见性超时,例如,等于我的处理时间。如果我的进程花费的时间超过可见性超时(例如连接速度慢),则其他线程可以使用相同的消息。

保证邮件处理一次的最佳方法是什么?

4 个答案:

答案 0 :(得分:23)

  

保证邮件处理一次的最佳方法是什么?

您要求保证 - 您赢得 。您可以将消息处理的可能性降低多次非常小,但您无法获得保证

我将解释为什么以及减少重复的策略。

重复来自哪里

  1. 当您在SQS中放入消息时,SQS实际上可能会多次收到该消息
    • 例如:发送邮件时发生轻微网络打嗝导致自动重试的暂时性错误 - 从邮件发件人的角度来看,失败一次,成功发送一次,但SQS收到了这两条消息。
  2. SQS can internally generate duplicates
    • Simlar到第一个例子 - 有很多计算机负责处理消息,SQS需要确保没有丢失 - 消息存储在多个服务器上,这可能导致重复。
  3. 在大多数情况下,通过利用SQS message visibility timeout,从这些来源重复的可能性已经相当小 - 比如小百分之几。

    如果处理副本确实不是那么糟糕strive to make your message consumption idempotent!),我认为这还不错 - 减少重复的可能性更加复杂且可能更昂贵...

    您的应用程序可以做些什么来进一步减少重复?

    好的,这里我们沿着兔子洞走下去......在高层,您需要为您的消息分配唯一的ID,并在开始处理之前检查正在进行或已完成的ID的原子缓存:< / p>

    1. 确保您的邮件在插入时提供了唯一标识符
      • 如果没有这个,你就无法区分重复。
    2. 在该行的末尾处理重复&#39;对于消息。
      • 如果您的消息接收者需要发送消息以供进一步处理,那么它可能是另一个重复来源(出于上述类似的原因)
    3. 您需要某处原子地存储和检查这些唯一ID(并在超时后刷新它们)。有两个重要的状态:&#34; InProgress&#34;和&#34;已完成&#34;
      • InProgress条目应根据处理失败时需要恢复的速度超时。
      • 根据您希望重复数据删除窗口的时长
      • ,已完成的条目应该超时
      • 最简单的可能是Guava cache,但只适用于单个处理应用。如果您有大量消息或分布式消耗,请考虑此作业的数据库(使用后台进程扫描过期条目)
    4. 在处理邮件之前,尝试将messageId存储在&#34; InProgress&#34;中。如果它已经存在,请停止 - 您只需处理一份副本。
    5. 检查消息是否为&#34;已完成&#34; (如果它在那里就停止)
    6. 您的主管现在对该messageId拥有独占锁定 - 处理您的消息
    7. 将messageId标记为&#34;已完成&#34; - 只要此消息留在此处,您就不会处理该messageId 的任何重复项。
      • 你可能无法承受无限的存储空间。
    8. 从&#34; InProgress&#34;中删除messageId (或者让它从这里过期)
    9. 一些注释

      • 请记住,没有所有这些的重复几率已经非常低了。根据消息的重复数据删除时间和金钱的价值,您可以随意跳过或修改任何步骤
        • 例如,您可以省略&#34; InProgress&#34;,但这会使两个线程同时处理重复消息的可能性很小(第二个线程在第一个消息之前开始#34) ;已完成&#34; it)
      • 您的重复数据删除窗口只要您能将messageId保留在&#34;已完成&#34;中。由于您可能无法承受无限存储空间,因此至少要将此时间延长至SQS消息可见性超时的2倍;之后重复的可能性降低(除了已经非常低的机会,但仍然保证)。
      • 即使有了这一切,仍然存在重复的可能性 - 所有预防措施和SQS消息可见性超时有助于将此机会减少到非常小,但机会仍然存在:
        • 您的应用可以在处理完邮件后立即崩溃/挂起/执行一个非常长的GC,但在messageId是&#34;已完成&#34; (也许你正在为这个存储使用数据库并且与它的连接已经关闭)
        • 在这种情况下,&#34;处理&#34;最终将到期,另一个线程可以处理此消息(在SQS可见性超时也到期之后或因为SQS中有重复)。

答案 1 :(得分:1)

在收到消息时,将消息或对消息的引用存储在消息ID上具有唯一约束的数据库中。如果表中存在ID,您已经收到它,并且由于唯一约束,数据库将不允许您再次插入它。

答案 2 :(得分:1)

您可以对邮件和批处理使用setVisibilityTimeout(),以便延长可见时间,直到线程完成处理邮件。

这可以通过使用scheduledExecutorService来完成,并在初始可见时间的一半之后安排可运行事件。下面的代码片段在visibilityTime的每一半创建并执行VisibilityTimeExtender,其周期为可见时间的一半。 (时间应保证消息处理,使用visibilityTime / 2进行扩展)

private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> futureEvent = scheduler.scheduleAtFixedRate(new VisibilityTimeExtender(..), visibilityTime/2, visibilityTime/2, TimeUnit.SECONDS);

VisibilityTimeExtender必须实现Runnable,并且是您更新新可见性时间的地方。

当线程处理完消息后,您可以将其从队列中删除,并调用futureEvent.cancel(true)来停止预定的事件。

答案 3 :(得分:0)

AWS SQS API不会自动&#34;消费&#34;用API等读取时的消息。开发人员需要拨打电话来自行删除邮件。

SQS确实有一个功能调用&#34;重新启动策略&#34;作为“死信队列设置”的一部分&#34;。您只需将读取请求设置为1.如果使用过程崩溃,则后续读取同一消息会将消息放入死信队列。

SQS队列可见性超时最长可设置为12小时。除非您有特殊需要,否则您需要实现将消息处理程序存储在数据库中以允许其进行检查的过程。