首先让我解释一下我所拥有的。我有一个带有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?(关于推迟邮件,但您需要手动将其从队列/主题中取出。)
答案 0 :(得分:1)
使用耐用功能也许可以解决您的问题。例如,有一个内置方法CallActivityWithRetryAsync()
,当活动功能引发异常时,该方法可以重试。
您的流程可能像这样:
服务总线触发的功能。这将启动Orchestrator功能
协调器调用您的活动函数(使用上述方法)
答案 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;