Azure Function App的Azure Service Bus延迟重试

时间:2019-05-01 14:01:00

标签: c# azure cloud azure-functions azureservicebus

首先让我解释一下我所拥有的。我有一个带有Azure功能应用程序的Azure服务总线。设置服务总线以使用SQL筛选器将特定的消息类型推送到特定的主题中。然后,使用我的Azure Function应用程序,这些将获取最新消息,然后对其进行处理。

一个基本示例

1:我向我的EmailAPI发送请求

2:EmailAPI然后将类型为“电子邮件”的新消息推送到服务总线中

3:然后,SQL筛选器会看到类型为“电子邮件”,并被放入服务Bux中的电子邮件主题中。

4:EmailListener Azure函数监视服务总线并注意到新消息

5:收集并处理服务总线消息(基本上只是使用提供的信息发送电子邮件)

现在让我们说说由于某种原因SMTP服务器连接有点中断,有时我们在尝试发送电子邮件(EmailListener)时会收到TimeOutException。现在,当引发异常时,Function App EmailListener将立即尝试再次发送该异常,而无需等待,它将尝试再次发送该异常。它将总共执行10次,然后通知服务总线将消息放入“死信”队列中。

我试图做的是当引发异常(例如TimeOutException)时,我们等待X时间,然后再次尝试处理同一条消息。我到处逛逛了许多有关host.json并尝试设置这些设置的文章,但是这些没有用。我已经找到了解决方案,但是该解决方案要求您创建消息的克隆,并将其推回到服务总线中,并延迟处理时间。如果Azure Service Bus / Function App可以自行处理重试,则我不希望实施自己的手动延迟系统。

我遇到的最大问题(这可能取决于我的理解)是谁在过错?是处理重试策略的服务总线设置,还是处理X次后重试的Azure功能应用程序。

我提供了一些代码,但是我觉得代码并不能真正帮助解释我的问题。

// Pseudo code
public static class EmailListenerTrigger
{
    [FunctionName("EmailListenerTrigger")]
    public static void Run([ServiceBusTrigger("messages", "email", Connection = "ConnectionString")]string mySbMsg, TraceWriter log)
    {
           var emailLauncher = new EmailLauncher("SmtpAddress", "SmtpPort", "FromAddress");
           try
           {
               emailLauncher.SendServiceBusMessage(mySbMsg);
           }
           catch(Exception ex)
           {
               log.Info($"Audit Log: {mySbMsg}, Excpetion: {ex.message}");
           }
    }
}

参考文献一:https://blog.kloud.com.au/2017/05/22/message-retry-patterns-in-azure-functions/(Thread.Sleep似乎不是一个好主意)

参考二:https://github.com/Azure/azure-functions-host/issues/2192(手动重试)

参考文献三:https://www.feval.ca/posts/function-queue-retry/(当我使用主题时,这似乎是指队列)

参考文献四:Can the Azure Service Bus be delayed before retrying a message?(关于推迟邮件,但您需要手动将其从队列/主题中取出。)

2 个答案:

答案 0 :(得分:1)

使用耐用功能也许可以解决您的问题。例如,有一个内置方法CallActivityWithRetryAsync(),当活动功能引发异常时,该方法可以重试。

https://docs.microsoft.com/en-us/sandbox/functions-recipes/durable-diagnostics#calling-activity-functions-with-retry

您的流程可能像这样:

  1. 服务总线触发的功能。这将启动Orchestrator功能

  2. 协调器调用您的活动函数(使用上述方法)

  3. 您的电子邮件发送是在“活动功能”中实现的,并且可以根据需要抛出异常

答案 1 :(得分:0)

虽然没有对您想做的事情的原生支持,但它仍然是可行的,而无需进行大量的定制开发。您基本上可以将服务总线输出绑定添加到您的 Azure 函数,它连接到您的函数使用消息的同一个队列。然后,使用自定义属性来跟踪重试次数。下面是一个例子:

private static TimeSpan[] BackoffDurationsBetweenFailures = new[] { }; // add delays here

[FunctionName("retrying-poc")]
public async Task Run(
  [ServiceBusTrigger("myQueue")] Message rawRequest,
  IDictionary<string, object> userProperties,
  [ServiceBus("myQueue")] IAsyncCollector<Message> collector)
{
  var request = GetRequest(rawRequest);
  var retryCount = GetRetryCount(userProperties);
  var shouldRetry = false;

  try
  {
    await _unreliableService.Call(request);
  }
  catch (Exception ex)
  {
     // I don't retry if it is a timeout, but that's my own choice.
    shouldRetry = !(ex is TimeoutException) && retryCount < BackoffDurationsBetweenFailures.Length;
  }

  if (shouldRetry)
  {
    var retryMessage = new Message(rawRequest.Body);
    retryMessage.UserProperties.Add("RetryCount", retryCount + 1);
    retryMessage.ScheduledEnqueueTimeUtc = DateTime.UtcNow.Add(BackoffDurationsBetweenFailures[retryCount]);
    await collector.AddAsync(retryMessage);
  }
}


private MyBusinessObject GetRequest(Message rawRequest)
  => JsonConvert.DeserializeObject<MyBusinessObject>(Encoding.UTF8.GetString(rawRequest.Body));

private int GetRetryCount(IDictionary<string, object> properties)
  => properties.TryGetValue("RetryCount", out var value) && int.TryParse(value.ToString(), out var retryCount)
            ? retryCount
            : 0;