将Azure持久功能用于ETL流程

时间:2018-07-23 23:51:04

标签: azure-functions azure-durable-functions

我有以下情况:

我必须执行一个检索N(0到无穷大之间的N)记录的函数。我必须调用一个映射函数以将记录转换为其他记录并向前移动(通过http,服务总线,cosmos db等)

由于10分钟的限制,我无法使用常规的Azure函数,因此我正在寻找耐用函数能否解决我的问题。

我的想法如下:
1-持久功能触发时,它将从数据库流式传输记录。
2-对于每个记录,它调用映射函数。
3-映射后,它将通过服务总线将记录发送到消息。

作为概念证明,我做了以下示例。我模拟了在持久功能中接收1000条消息,但是它的行为非常不可靠。如果我发送1000条消息,则该函数有点崩溃或完成时间太长,我希望此代码几乎立即完成。

#r "Microsoft.Azure.WebJobs.Extensions.DurableTask"

public static async Task<List<string>> Run(DurableOrchestrationContext context, TraceWriter log)
{
    var outputs = new List<string>();

    var tasks = new List<Task<string>>();
    for(int i = 0; i < 1000; i++)
    {
        log.Info(i.ToString());
        tasks.Add(context.CallActivityAsync<string>("Hello", i.ToString()));
    }

    outputs.AddRange(await Task.WhenAll(tasks.ToArray()));

    return outputs;
}

我的问题是:持久功能是否适合这种情况? 我是否应该研究一些非无用函数方法从数据库中提取数据?

是否有一种从持久函数内部同步调用另一个Azure函数的方法?

2 个答案:

答案 0 :(得分:2)

开始之前,您必须考虑耐用功能的真正工作原理。要了解流程,请看以下示例:

#r "Microsoft.Azure.WebJobs.Extensions.DurableTask"

public static async Task Run(DurableOrchestrationContext context, TraceWriter log)
{
    await context.CallActivityAsync<string>("Hello1");
    await context.CallActivityAsync<string>("Hello2");
}

运行时的工作方式如下:

  1. 它进入编排并命中第一个await,在其中称为活动 Hello1
  2. 将控件返回到名为 Dispatcher 的组件,该组件是框架的内部部分。它检查是否已为该特定活动调用了当前的业务流程ID。如果没有,它将等待结果并重新分配业务流程使用的资源
  3. Task等待完成后,调度程序重新创建编排并从头开始重播
  4. 它再次等待活动 Hello1 ,但是这一次在查询编排历史之后,它知道它已经被调用并保存了结果-它使用保存的结果并继续执行
  5. 它击中了第二个await,整个循环再次进行

如您所见,引擎盖下需要进行认真的工作。将工作委派给业务流程和活动也有一个经验法则:

  • 编排应该只进行编排-因为它有很多限制,例如单线程,仅等待安全任务(这意味着可以在 DurableOrchestrationContext 类型上使用),并且可以在其中进行扩展几个队列(而不是虚拟机)。而且它必须是幂等的(因此它不能使用例如DateTime.Now或直接查询数据库)
  • 活动应该执行这项工作-它是一种典型的功能(没有业务流程的限制),并且可以扩展到多个不同的虚拟机

在您的方案中,您应该只执行一个活动,该活动将完成所有工作,而不是遍历业务流程中的记录(特别是因为您无法在业务流程中使用对Service Bus的绑定,但是您可以在活动中执行此操作,活动可以获取数据,对其进行转换,然后推送到您想要的任何类型的服务)。因此,在您的代码中,您可能会遇到这样的事情:

[FunctionName("Orchestration_Client")]
public static async Task<string> Orchestration_Client(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "start")] HttpRequestMessage input,
    [OrchestrationClient] DurableOrchestrationClient starter)
{
    return await starter.StartNewAsync("Orchestration", await input.Content.ReadAsStringAsync());
}

[FunctionName("Orchestration")]
public static async Task Orchestration_Start([OrchestrationTrigger] DurableOrchestrationContext context)
{
    var payload = context.GetInput<string>();
    await context.CallActivityAsync(nameof(Activity), payload);
}

[FunctionName("Activity")]
public static string Activity(
    [ActivityTrigger] DurableActivityContext context,
    [Table(TableName, Connection = "TableStorageConnectionName")] IAsyncCollector<FooEntity> foo)
{
    // Get data from request
    var payload = context.GetInput<string>();

    // Fetch data from database
    using(var conn = new SqlConnection())
    ...

    // Transform it
    foreach(var record in databaseResult) 
    {
        // Do some work and push data
        await foo.AddAsync(new FooEntity() { // Properties });
    }

    // Result
    return $"Processed {count} records!!";
}

与其说是真实的例子,不如说是个主意,但您应该能够明白这一点。另一件事是,持久功能是否真的是此类操作的最佳解决方案-我相信像Azure Data Factory这样的服务会更好。

答案 1 :(得分:0)