活动功能出错,来自服务总线队列的消息消失

时间:2018-12-31 04:01:47

标签: c# azure-functions azure-servicebus-queues azure-durable-functions azure-triggers

我开发了一个Azure耐用功能应用程序,该应用程序会在新的servicebus队列消息上触发。当没有错误发生时,它工作正常,但是在活动功能中发生错误时,它记录失败,但是消息从队列中消失了。可能是什么原因造成的?如何防止错误消息从队列中消失?

这是可复制的代码,它是从VS2017中的新Azure函数模板生成的代码,当城市为“西雅图”时仅添加了一个例外,它是ServicebusTrigger而不是HttpTrigger。

            [FunctionName("Test")]
    public static async Task<List<string>> RunOrchestrator(
        [OrchestrationTrigger] DurableOrchestrationContext context)
    {
        var outputs = new List<string>();

        // Replace "hello" with the name of your Durable Activity Function.
        outputs.Add(await context.CallActivityAsync<string>("Test_Hello", "Tokyo"));
        outputs.Add(await context.CallActivityAsync<string>("Test_Hello", "Seattle"));
        outputs.Add(await context.CallActivityAsync<string>("Test_Hello", "London"));

        // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
        return outputs;
    }

    [FunctionName("Test_Hello")]
    public static string SayHello([ActivityTrigger] string name, ILogger log)
    {
        log.LogInformation($"Saying hello to {name}.");
        if (name == "Seattle")
            throw new Exception("An error occurs");
        return $"Hello {name}!";
    }

    [FunctionName("Test_HttpStart")]
    public static async Task ServiceBusStart(
        [ServiceBusTrigger("somequeue", Connection = "ServiceBusQueueListenerConnectionString")]string queuemsg,
        [OrchestrationClient]DurableOrchestrationClient starter,
        ILogger log)
    {
        // Function input comes from the request content.
        var msg = JsonConvert.DeserializeObject<IncomingMessage>(queuemsg);
        string instanceId = await starter.StartNewAsync("Test", msg);
        log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
    }

更新:当我在Orchestration客户端功能中遇到异常时,它会执行正确的操作,例如如果重试失败x次,则重试并将消息放在死信队列中。

因此,我设法通过使用while循环更新客户端功能并检查失败/终止/取消状态来解决此问题。

    [FunctionName("Test_HttpStart")]
    public static async Task ServiceBusStart(
        [ServiceBusTrigger("somequeue", Connection = "ServiceBusQueueListenerConnectionString")]string queuemsg,
        [OrchestrationClient]DurableOrchestrationClient starter,
        ILogger log)
    {
        // Function input comes from the request content.
        var msg = JsonConvert.DeserializeObject<IncomingMessage>(queuemsg);
        string instanceId = await starter.StartNewAsync("Test", msg);
        log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

        var status = await starter.GetStatusAsync(instanceId);

        while (status.RuntimeStatus != OrchestrationRuntimeStatus.Completed)
        {
            System.Threading.Thread.Sleep(1000);
            status = await starter.GetStatusAsync(instanceId);
            if (status.RuntimeStatus == OrchestrationRuntimeStatus.Failed 
                || status.RuntimeStatus == OrchestrationRuntimeStatus.Terminated
                || status.RuntimeStatus == OrchestrationRuntimeStatus.Canceled)
            {
                throw new Exception("Orchestration failed with error: " + status.Output);
            }
        }

    }

但是,对我来说,这似乎是一种破解,并且在任何MS示例代码中都没有看到这种类型的代码。我想这应该由持久功能框架来解决。还有其他方法可以使Servicebus触发器在持久功能中工作吗?

2 个答案:

答案 0 :(得分:2)

此行为是设计使然。启动业务流程是异步的-即StartNewAsync API不会自动等待业务流程运行或完成。在内部,StartNewAsync只是将消息放入Azure存储队列,并将条目写入Azure存储表。如果成功完成,那么您的服务总线功能将继续运行并成功完成,这时将删除该消息。

如果您确实需要重试服务总线队列消息,则您的解决方法是可以接受的,但是我质疑为什么您需要这样做。编排本身可以管理自己的重试,而无需依赖Service Bus。例如,您可以使用CallActivityWithRetryAsync在业务流程内部进行重试。

请参见“持久功能”文档的Error Handling主题。

答案 1 :(得分:0)

我知道这是一个旧线程,但我想分享一下我是如何使用 ServiceBusTriggerWaitForCompletionOrCreateCheckStatusResponseAsync 实现的。

[FunctionName(nameof(QueueTriggerFunction))]
public async Task QueueTriggerFunction(
    [ServiceBusTrigger("queue-name", Connection = "connectionstring-key")]string queueMessage,
    MessageReceiver messageReceiver,
    string lockToken,
    string messageId,
    [DurableClient] IDurableOrchestrationClient starter,
    ILogger log)
{
    //note: autocomplete is disabled
    try
    {
        //start durable function
        var instanceId = await starter.StartNewAsync(nameof(OrchestratorFunction), queueMessage);

        //get the payload (we want to use the status uri)
        var payload = starter.CreateHttpManagementPayload(instanceId);

        //instruct QueueTriggerFunction to wait for response
        await starter.WaitForCompletionOrCreateCheckStatusResponseAsync(new HttpRequestMessage(HttpMethod.Get, payload.StatusQueryGetUri), instanceId);

        //response ready, get status
        var status = await starter.GetStatusAsync(instanceId);

        //act on status
        if (status.RuntimeStatus == OrchestrationRuntimeStatus.Completed)
        {
            //like completing the message
            await messageReceiver.CompleteAsync(lockToken);
            log.LogInformation($"{nameof(Functions)}.{nameof(QueueTriggerFunction)}: {nameof(OrchestratorFunction)} succeeded [MessageId={messageId}]");
        }
        else
        {
            //or deadletter the sob
            await messageReceiver.DeadLetterAsync(lockToken);
            log.LogError($"{nameof(Functions)}.{nameof(QueueTriggerFunction)}: {nameof(OrchestratorFunction)} failed [MessageId={messageId}]");
        }
    }
    catch (Exception ex)
    {
        //not sure what went wrong, let the lock expire and try again (until max retry attempts is reached)
        log.LogError(ex, $"{nameof(Functions)}.{nameof(QueueTriggerFunction)}: handler failed [MessageId={messageId}]");
    }
}

问题是,互联网上的所有示例都使用 HttpTrigger 并使用该触发器的 httprequest 来检查完成情况,但您没有使用 ServiceBusTrigger。此外,我认为这是不正确的,您应该使用负载调用中的状态 uri,就像我在这里使用协调器函数的 instanceId 一样。