Hubs de tarefas nas Funções Duráveis (Azure Functions)

Um hub de tarefas no Durable Functions é uma representação do estado atual do aplicativo no armazenamento, incluindo todo o trabalho pendente. Enquanto um aplicativo de funções está em execução, o progresso das funções de orquestração, atividade e entidade é continuamente armazenado no hub de tarefas. Isso garante que o aplicativo possa retomar o processamento de onde parou, caso precise ser reiniciado após ser temporariamente parado ou interrompido por algum motivo. Além disso, permite que o aplicativo de funções dimensione os trabalhos de computação dinamicamente.

Diagram showing concept of function app and task hub concept.

Conceitualmente, um hub de tarefas armazena as seguintes informações:

  • Os estados de instância de todas as instâncias de orquestração e entidade.
  • As mensagens a serem processadas, incluindo
    • quaisquer mensagens de atividade que representem atividades aguardando para serem executadas.
    • quaisquer mensagens de instância que estão aguardando para serem entregues às instâncias.

A diferença entre mensagens de atividade e de instância é que as mensagens de atividade são sem estado e, portanto, podem ser processadas em qualquer lugar, enquanto as mensagens de instância precisam ser entregues a uma instância com estado específica (orquestração ou entidade), identificada pela respectiva ID de instância.

Internamente, cada provedor de armazenamento pode usar uma organização diferente para representar estados e mensagens de instância. Por exemplo, as mensagens são armazenadas em Filas de Armazenamento do Microsoft Azure pelo provedor de Armazenamento do Microsoft Azure, mas em tabelas relacionais pelo provedor MSSQL. Essas diferenças não importam no que diz respeito ao design do aplicativo, mas algumas delas podem influenciar as características de desempenho. Eles são discutidos na seção Representação no armazenamento abaixo.

Itens de trabalho

As mensagens de atividade e as mensagens de instância no hub de tarefas representam o trabalho que o aplicativo de funções precisa processar. Enquanto o aplicativo de funções está em execução, ele busca continuamente itens de trabalho do hub de tarefas. Cada item de trabalho está processando uma ou mais mensagens. Distinguimos dois tipos de itens de trabalho:

  • Itens de trabalho de atividade: executam uma função de atividade para processar uma mensagem de atividade.
  • Item de trabalho do orquestrador: executam uma função de orquestrador ou entidade para processar uma ou mais mensagens de instância.

Os trabalhos podem processar vários itens de trabalho ao mesmo tempo, sujeitos aos limites de simultaneidade por trabalho configurados.

Depois que um trabalho conclui um item de trabalho, ele confirma os efeitos de volta para o hub de tarefas. Esses efeitos variam de acordo com o tipo de função que foi executada:

  • Uma função de atividade concluída cria uma mensagem de instância contendo o resultado, endereçada à instância do orquestrador pai.
  • Uma função de orquestrador concluída atualiza o estado e o histórico da orquestração e pode criar novas mensagens.
  • Uma função de entidade concluída atualiza o estado da entidade e também pode criar novas mensagens de instância.

Para orquestrações, cada item de trabalho representa um episódio da execução dessa orquestração. Um episódio começa quando há novas mensagens para o orquestrador processar. Essa mensagem pode indicar que a orquestração deve começar; ou pode indicar que uma atividade, chamada de entidade, temporizador ou suborquestração foi concluída; ou pode representar um evento externo. A mensagem dispara um item de trabalho que permite que o orquestrador processe o resultado e continue com o próximo episódio. Esse episódio termina quando o orquestrador é concluído ou chega a um ponto em que deve aguardar novas mensagens.

Exemplo de execução

Considere uma orquestração do tipo fan-out-fan-in que inicia duas atividades em paralelo e aguarda que ambas sejam concluídas:

[FunctionName("Example")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    Task t1 = context.CallActivityAsync<int>("MyActivity", 1);
    Task t2 = context.CallActivityAsync<int>("MyActivity", 2);
    await Task.WhenAll(t1, t2);
}

Depois que essa orquestração é iniciada por um cliente, ela é processada pelo aplicativo de funções como uma sequência de itens de trabalho. Cada item de trabalho concluído atualiza o estado do hub de tarefas quando ele é confirmado. Estas são as etapas:

  1. Um cliente solicita iniciar uma nova orquestração com a ID da instância "123". Depois que o cliente conclui essa solicitação, o hub de tarefas contém um espaço reservado para o estado de orquestração e uma mensagem de instância:

    workitems-illustration-step-1

    O rótulo ExecutionStarted é um dos muitos tipos de eventos de histórico que identificam os vários tipos de mensagens e eventos que participam do histórico de uma orquestração.

  2. Um trabalho executa um item de trabalho do orquestrador para processar a mensagem ExecutionStarted. Ele chama a função de orquestrador que começa a executar o código de orquestração. Esse código agenda duas atividades e, em seguida, interrompe a execução quando aguarda os resultados. Depois que o trabalho confirmar esse item de trabalho, o hub de tarefas conterá

    workitems-illustration-step-2

    O estado do runtime é agora Running, duas novas mensagens TaskScheduled foram adicionadas e o histórico agora contém os cinco eventosOrchestratorStarted, ExecutionStarted, TaskScheduled, TaskScheduled, OrchestratorCompleted. Esses eventos representam o primeiro episódio da execução desta orquestração.

  3. Um trabalho executa um item de trabalho de atividade para processar uma das mensagens TaskScheduled. Ele chama a função de atividade com a entrada "2". Quando a função de atividade for concluída, ela criará uma mensagem TaskCompleted contendo o resultado. Depois que o trabalho confirmar esse item de trabalho, o hub de tarefas conterá

    workitems-illustration-step-3

  4. Um trabalho executa um item de trabalho do orquestrador para processar a mensagem TaskCompleted. Se a orquestração ainda estiver armazenada em cache na memória, ela poderá apenas retomar a execução. Caso contrário, o trabalho primeiro repete a história para recuperar o estado atual da orquestração. Em seguida, continua a orquestração, entregando o resultado da atividade. Depois de receber esse resultado, a orquestração ainda aguarda o resultado da outra atividade, de modo que mais uma vez para de ser executada. Depois que o trabalho confirmar esse item de trabalho, o hub de tarefas conterá

    workitems-illustration-step-4

    O histórico de orquestração agora contém mais três eventosOrchestratorStarted, TaskCompleted, OrchestratorCompleted. Esses eventos representam o segundo episódio da execução desta orquestração.

  5. Um trabalho executa um item de trabalho de atividade para processar a mensagem TaskScheduled restante. Ele chama a função de atividade com a entrada "1". Depois que o trabalho confirmar esse item de trabalho, o hub de tarefas conterá

    workitems-illustration-step-5

  6. Um trabalho executa outro item de trabalho do orquestrador para processar a mensagem TaskCompleted. Depois de receber este segundo resultado, a orquestração é concluída. Depois que o trabalho confirmar esse item de trabalho, o hub de tarefas conterá

    workitems-illustration-step-6

    O estado do runtime é agora Completed, e o histórico de orquestração agora contém mais quatro eventos OrchestratorStarted, TaskCompleted, ExecutionCompleted, OrchestratorCompleted. Esses eventos representam o terceiro e último episódio da execução desta orquestração.

O histórico final para a execução desta orquestração contém os 12 eventos OrchestratorStarted, ExecutionStarted, TaskScheduled, TaskScheduled, OrchestratorCompleted, OrchestratorStarted, TaskCompleted, OrchestratorCompleted, OrchestratorStarted, TaskCompleted, ExecutionCompleted, OrchestratorCompleted.

Observação

O agendamento mostrado não é o único: há muitos agendamentos possíveis ligeiramente diferentes. Por exemplo, se a segunda atividade for concluída antes, ambas as mensagens de instância TaskCompleted poderão ser processadas por um único item de trabalho. Nesse caso, o histórico de execução é um pouco menor, porque há apenas dois episódios e contém os seguintes 10 eventos: OrchestratorStarted, ExecutionStarted, TaskScheduled, TaskScheduled, OrchestratorCompleted, OrchestratorStarted, TaskCompleted, TaskCompleted, ExecutionCompleted, OrchestratorCompleted.

Gerenciamento do hub de tarefas

Em seguida, vamos examinar mais de perto como os hubs de tarefas são criados ou excluídos, como usar os hubs de tarefas corretamente ao executar vários aplicativos de funções e como o conteúdo dos hubs de tarefas pode ser inspecionado.

Criação e exclusão

Um hub de tarefas vazio com todos os recursos necessários é criado automaticamente no armazenamento quando um aplicativo de funções é iniciado pela primeira vez.

Se estiver usando o provedor de Armazenamento do Microsoft Azure padrão, nenhuma configuração extra será necessária. Caso contrário, siga as instruções para configurar provedores de armazenamento para garantir que o provedor de armazenamento possa provisionar e acessar corretamente os recursos de armazenamento necessários para o hub de tarefas.

Observação

O hub de tarefas não é excluído automaticamente quando você interrompe ou exclui o aplicativo de funções. Você deve excluir o hub de tarefas, o conteúdo dele ou a conta de armazenamento que o contém manualmente se não quiser mais manter esses dados.

Dica

Em um cenário de desenvolvimento, talvez seja necessário reiniciar com frequência de um estado limpo. Para fazer isso rapidamente, basta alterar o nome do hub de tarefas configurado. Isso forçará a criação de um novo hub de tarefas vazio quando você reiniciar o aplicativo. Lembre-se de que os dados antigos não são excluídos nesse caso.

Vários aplicativos de funções

Se vários aplicativos de funções compartilham uma conta de armazenamento, cada aplicativo de funções deve ser configurado com um nome de hub de tarefas separado. Esse requisito também se aplica aos slots de preparo: cada slot de preparo precisa ser configurado com um nome exclusivo do hub de tarefas. Uma única conta de armazenamento pode conter vários hubs de tarefas. Essa restrição geralmente se aplica também a outros provedores de armazenamento.

O diagrama a seguir ilustra um hub de tarefas por aplicativo de funções em contas de Armazenamento do Azure compartilhadas e dedicadas.

Diagram showing shared and dedicated storage accounts.

Observação

A exceção à regra de compartilhamento do hub de tarefas é se você estiver configurando seu aplicativo para recuperação de desastre regional. Consulte o artigo sobre Recuperação de desastre e distribuição geográfica, para obter mais informações.

Inspeção de conteúdo

Há várias maneiras comuns de inspecionar o conteúdo de um hub de tarefas:

  1. Em um aplicativo de funções, o objeto cliente fornece métodos para consultar o repositório de instâncias. Para saber mais sobre quais tipos de consultas têm suporte, consulte o artigo Gerenciamento de Instâncias.
  2. Da mesma forma, a API HTTP oferece solicitações REST para consultar o estado de orquestrações e entidades. Confira a Referência da API HTTP para obter mais detalhes.
  3. A ferramenta Durable Functions Monitor pode inspecionar os hubs de tarefas e oferece várias opções para exibição visual.

Para alguns dos provedores de armazenamento, também é possível inspecionar o hub de tarefas acessando diretamente o armazenamento subjacente:

  • Se estiver usando o provedor de Armazenamento do Azure, os estados de instância serão armazenados na Tabela de Instância e na Tabela de Histórico que podem ser inspecionadas usando ferramentas como Gerenciador de Armazenamento do Microsoft Azure.
  • Se estiver usando o provedor de armazenamento MSSQL, as consultas SQL e as ferramentas poderão ser usadas para inspecionar o conteúdo do hub de tarefas dentro do banco de dados.

Representação no armazenamento

Cada provedor de armazenamento usa uma organização interna diferente para representar hubs de tarefas no armazenamento. Entender essa organização, embora não seja necessário, pode ser útil ao solucionar problemas de um aplicativo de funções ou ao tentar garantir destinos de desempenho, escalabilidade ou custo. Portanto, explicamos brevemente, para cada provedor de armazenamento, como os dados são organizados no armazenamento. Para obter mais informações sobre as várias opções de provedor de armazenamento e como elas se comparam, confira a documentação de provedores de armazenamento Durable Functions.

Provedor do Armazenamento do Azure

O provedor de Armazenamento do Microsoft Azure representa o hub de tarefas no armazenamento usando os seguintes componentes:

  • Duas Tabelas do Azure armazenam os estados da instância.
  • Uma Fila do Azure armazena as mensagens de atividade.
  • Uma ou mais Filas do Azure armazenam as mensagens da instância. Cada uma dessas chamadas filas de controle representa uma partição que é atribuída a um subconjunto de todas as mensagens da instância, com base no hash da ID da instância.
  • Alguns contêineres de blobs adicionais usados para blobs de concessão e/ou mensagens grandes.

Por exemplo, um hub de tarefas nomeado xyz com PartitionCount = 4 contém as seguintes filas e tabelas:

Diagram showing Azure Storage provider storage storage organization for 4 control queues.

A seguir, descreveremos com mais detalhes esses componentes e a função que desempenham.

Para obter mais informações sobre como os hubs de tarefas são representados pelo provedor de Armazenamento do Microsoft Azure, confira a documentação do provedor de Armazenamento do Microsoft Azure.

Provedor de armazenamento Netherite

O Netherite particiona todo o estado do hub de tarefas em um número especificado de partições. No armazenamento, os seguintes recursos são usados:

  • Um contêiner de blobs do Armazenamento do Microsoft Azure que contém todos os blobs, agrupados por partição.
  • Uma Tabela do Azure que contém métricas publicadas sobre as partições.
  • Um namespace dos Hubs de Eventos do Azure para fornecer mensagens entre partições.

Por exemplo, um hub de tarefas nomeado mytaskhub com PartitionCount = 32 é representado no armazenamento da seguinte maneira:

Diagram showing Netherite storage organization for 32 partitions.

Observação

Todo o estado do hub de tarefas é armazenado dentro do contêiner de blobs x-storage. A tabela DurableTaskPartitions e o namespace dos Hubs de Eventos contêm dados redundantes: se o conteúdo deles for perdido, poderá ser recuperado automaticamente. Portanto, não é necessário configurar o namespace dos Hubs de Eventos do Azure para reter mensagens após o tempo de expiração padrão.

O Netherite usa um mecanismo de fornecimento de eventos, com base em um log e pontos de verificação, para representar o estado atual de uma partição. Blobs de blocos e blobs de página são usados. Não é possível ler esse formato diretamente do armazenamento, portanto, o aplicativo de funções precisa estar em execução ao consultar o repositório de instâncias.

Para obter mais informações sobre hubs de tarefas para o provedor de armazenamento Netherite, confira as informações do Hub de Tarefas para o provedor de armazenamento Netherite.

Provedor do armazenamento MSSQL

Todos os dados do hub de tarefas são armazenados em um único banco de dados relacional, usando várias tabelas:

  • As tabelas dt.Instances e dt.History armazenam os estados da instância.
  • A tabela dt.NewEvents armazena as mensagens da instância.
  • A tabela dt.NewTasks armazena as mensagens de atividade.

Diagram showing MSSQL storage organization.

Para permitir que vários hubs de tarefas coexistam independentemente no mesmo banco de dados, cada tabela inclui uma coluna TaskHub como parte da chave primária. Ao contrário dos outros dois provedores, o provedor MSSQL não tem um conceito de partições.

Para obter mais informações sobre hubs de tarefas para o provedor de armazenamento MSSQL, confira as informações do Hub de Tarefas para o provedor de armazenamento MSSQL (Microsoft SQL).

Nomes de hub de tarefas

Os hubs de tarefas são identificados por um nome que deve seguir estas regras:

  • Contém apenas caracteres alfanuméricos
  • É iniciado por uma letra
  • Tem no mínimo três caracteres e no máximo 45 caracteres

O nome do hub de tarefa é declarado no arquivo host.json, conforme mostrado no seguinte exemplo:

host.json (Functions 2.0)

{
  "version": "2.0",
  "extensions": {
    "durableTask": {
      "hubName": "MyTaskHub"
    }
  }
}

host.json (funções 1.x)

{
  "durableTask": {
    "hubName": "MyTaskHub"
  }
}

Os hubs de tarefas também podem ser configurados usando as configurações do aplicativo, conforme mostrado no seguinte arquivo de exemplo host.json:

host.json (Functions 1.0)

{
  "durableTask": {
    "hubName": "%MyTaskHub%"
  }
}

host.json (Functions 2.0)

{
  "version": "2.0",
  "extensions": {
    "durableTask": {
      "hubName": "%MyTaskHub%"
    }
  }
}

O nome do hub de tarefas será definido com o valor da configuração do aplicativo MyTaskHub. O seguinte local.settings.json demonstra como definir a MyTaskHubconfigurar como samplehubname:

{
  "IsEncrypted": false,
  "Values": {
    "MyTaskHub" : "samplehubname"
  }
}

Observação

Ao usar slots de implantação, é uma prática recomendada configurar o nome do hub de tarefas usando as configurações do aplicativo. Se você quiser garantir que um slot específico sempre use um hub de tarefas específico, use as configurações do aplicativo "slot-sticky".

Além de host.json, os nomes dos hubs de tarefas também podem ser configurados em metadados de associação de cliente de orquestração. Isso é útil caso você precise acessar orquestrações ou entidades que residem em um aplicativo de funções separado. O seguinte código demonstra como escrever uma função que usa a associação de cliente de orquestração para trabalhar com um hub de tarefas que está configurado como uma Configuração de Aplicativo:

[FunctionName("HttpStart")]
public static async Task<HttpResponseMessage> Run(
    [HttpTrigger(AuthorizationLevel.Function, methods: "post", Route = "orchestrators/{functionName}")] HttpRequestMessage req,
    [DurableClient(TaskHub = "%MyTaskHub%")] IDurableOrchestrationClient starter,
    string functionName,
    ILogger log)
{
    // Function input comes from the request content.
    object eventData = await req.Content.ReadAsAsync<object>();
    string instanceId = await starter.StartNewAsync(functionName, eventData);

    log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

    return starter.CreateCheckStatusResponse(req, instanceId);
}

Observação

O exemplo anterior é para Durable Functions 2.x. No Durable Functions 1.x, use DurableOrchestrationContext em vez de IDurableOrchestrationContext. Para obter mais informações sobre as diferenças entre versões, confira o artigo Versões do Durable Functions.

Observação

A configuração dos nomes de hub de tarefas em metadados de associação de cliente só é necessária quando você usa um aplicativo de funções para acessar orquestrações e entidades em outro aplicativo de funções. Se as funções de cliente forem definidas no mesmo aplicativo de funções que as orquestrações e entidades, você deve evitar especificar os nomes do hub de tarefas nos metadados de associação. Por padrão, todas as associações de cliente obtêm seus metadados do hub de tarefas da configuração do host.json.

Nomes de hubs de tarefas devem começar com uma letra e devem ser compostos somente por letras e números. Se não for especificado, um nome de hub de tarefas padrão será usado conforme mostrado na seguinte tabela:

Versão de extensão durável Nome do hub de tarefas padrão
2. x Quando implantado no Azure, o nome do hub de tarefas é derivado do nome do aplicativo de funções. Ao executar fora do Azure, o nome do hub de tarefas padrão é TestHubName.
1.x O nome do hub de tarefas padrão para todos os ambientes é DurableFunctionsHub.

Para obter mais informações sobre as diferenças entre versões de extensão, confira o artigo Versões do Durable Functions.

Observação

O nome é o que diferencia um hub de tarefas de outro quando há vários em uma conta de armazenamento compartilhada. Se você tiver vários aplicativos de funções compartilhando uma conta de armazenamento, será necessário configurar, explicitamente, nomes diferentes para cada hub de tarefas nos arquivos host.json. Caso contrário, os aplicativos de várias funções competirão entre si para mensagens, o que pode resultar em um comportamento indefinido, incluindo orquestrações inesperadamente "presas" no estado Pending ou Running.

Próximas etapas