Azure Durable 编排函数 ILogger 两次输出日志

Azure Durable orchestration function ILogger outputs logs twice

有几个相互调用的持久函数。 主编排 -> 子编排 -> Activity -> 辅助异步方法

每个 func 都具有 ILogger 依赖项,并在函数开始和函数结束时登录。 出于某种原因,两个协调器都复制了“启动时”消息。 (见图) Activity没有这个效果。 (见图) 运行 以下示例多次 - 同样的故事。

我也确定整个过程触发了一次

这是协调器中的错误还是预期行为?

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;

namespace Issues
{
    public static class Log_Issue
    {
        [FunctionName("Main")]
        public static async Task RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context,
            ILogger log)
        {
            try
            {
                log.LogWarning("Main Start");
                await context.CallSubOrchestratorAsync("Sub", null);
                log.LogWarning("Main End");
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

        [FunctionName("Sub")]
        public static async Task RunSubOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context,
            ILogger log)
        {
            log.LogWarning("Sub Start");
            var data = await context.CallActivityAsync<string>("Activity", null);
            log.LogWarning("Sub End");
        }

        [FunctionName("Activity")]
        public static async Task<string> GetDataActivity([ActivityTrigger] string name, ILogger log)
        {
            log.LogWarning("Activity Start");
            var data = await GetDataAsync("https://www.google.com");
            log.LogWarning("Activity End");

            return data;
        }

        [FunctionName("Start")]
        public static async Task<IActionResult> HttpStart(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]
            HttpRequestMessage req,
            [DurableClient] IDurableOrchestrationClient starter,
            ILogger log)
        {
            var instanceId = await starter.StartNewAsync("Main", null);
            log.LogWarning($"Started orchestration with ID = '{instanceId}'.");
            return new OkResult();
        }

        private static async Task<string> GetDataAsync(string url)
        {
            var httpClient = new HttpClient();

            using var request = new HttpRequestMessage
            {
                RequestUri = new Uri(url),
                Method = HttpMethod.Get,
            };

            var response = await httpClient.SendAsync(request);
            response.EnsureSuccessStatusCode();

            return await response.Content.ReadAsStringAsync();
        }
    }
}

这是意料之中的。例如等待 context.CallActivityAsync("Activity", null);代码会自行暂停,甚至可能被加载出内存(为了节省成本)。

然后协调器等待将事件放置在 activity 创建的另一个 Azure 存储 Table 中,这可能会在许多天后发生。对于活动,它们通常是非常即时的,但它仍在等待此事件发生。

发生这种情况时,代码需要从上次停止的地方开始,但没有办法做到这一点。因此代码 re运行s 从头开始​​但不是等待 activity 再次完成它首先查看 table 并看到我们已经完成了这个 activity并且可以继续 运行ning。如果 activity 函数返回了某个值,它将从 await 调用中返回。在编排器的两个 运行 期间,它都会记录,但由于我们只进入 activity 那些只会被记录的。

这就是协调器必须具有确定性的原因,例如第一个 运行 的随机值与第二个 运行 的随机值不同。相反,我们会将 random.Next() 放入 activity 函数中,以便将值保存到 Azure Table 存储中,以便在后续的 re运行 中使用。编排器也可能正在等待正常功能创建的一些外部事件。例如,有人必须验证他们的电子邮件帐户,这可能需要几天时间,这就是为什么持久函数可以在事件触发时自行卸载并重新启动的原因。

@FilipB 说的都是真的。它只是缺少解决它的实际代码 ;)

[FunctionName("Main")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context,
ILogger log)
{
    log = context.CreateReplaySafeLogger(log); // this is what you should use at the start of every Orchestrator