Hubs de tarefas no Durable Functions (Funções do Azure)

Um hub de tarefas no Durable Functions é uma representação do estado atual da aplicação no armazenamento, incluindo todo o trabalho pendente. Enquanto uma aplicação de funções está em execução, o progresso das funções de orquestração, atividade e entidade é continuamente armazenado no hub de tarefas. Isto garante que a aplicação pode retomar o processamento onde parou, caso tenha de ser reiniciada depois de ter sido temporariamente parada ou interrompida por algum motivo. Além disso, permite que a aplicação de funções dimensione dinamicamente as funções de trabalho de computação.

Diagrama a mostrar o conceito da aplicação de funções e do conceito do hub de tarefas.

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

  • Os estados da instância de todas as instâncias de orquestração e entidade.
  • As mensagens a processar, incluindo
    • quaisquer mensagens de atividade que representem atividades à espera de serem executadas.
    • quaisquer mensagens de instância que estejam à espera de serem entregues em instâncias.

A diferença entre as mensagens de atividade e de instância é que as mensagens de atividade não têm estado e, portanto, podem ser processadas em qualquer lugar, enquanto as mensagens de instância têm de ser entregues a uma instância com estado específico (orquestração ou entidade), identificada pelo ID de instância.

Internamente, cada fornecedor de armazenamento pode utilizar uma organização diferente para representar estados e mensagens de instâncias. Por exemplo, as mensagens são armazenadas nas Filas de Armazenamento do Azure pelo fornecedor de Armazenamento do Azure, mas em tabelas relacionais pelo fornecedor MSSQL. Estas diferenças não importam no que diz respeito à conceção da aplicação, mas algumas delas podem influenciar as características de desempenho. Vamos abordá-los na secçã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 a aplicação de funções precisa de processar. Enquanto a aplicação de funções está em execução, obtém continuamente itens de trabalho do hub de tarefas. Cada item de trabalho está a processar uma ou mais mensagens. Distinguimos dois tipos de itens de trabalho:

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

Os trabalhadores podem processar múltiplos itens de trabalho ao mesmo tempo, sujeitos aos limites de simultaneidade configurados por trabalho.

Depois de um trabalho concluir um item de trabalho, consolida os efeitos novamente no hub de tarefas. Estes efeitos variam consoante o tipo de função que foi executada:

  • Uma função de atividade concluída cria uma mensagem de instância que contém o resultado, endereçada à instância do orquestrador principal.
  • 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 existem novas mensagens para o orquestrador processar. Tal mensagem pode indicar que a orquestração deve ser iniciada; ou pode indicar que uma atividade, chamada de entidade, temporizador ou suborchestração foi concluída; ou pode representar um evento externo. A mensagem aciona um item de trabalho que permite ao orquestrador processar o resultado e continuar com o próximo episódio. Esse episódio termina quando o orquestrador é concluído ou chega a um ponto em que tem de esperar por novas mensagens.

Exemplo de execução

Considere uma orquestração 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);
}

Após esta orquestração ser iniciada por um cliente, é processada pela aplicação 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 é consolidado. São estes os passos:

  1. Um cliente pede para iniciar uma nova orquestração com o instance-id "123". Depois de o cliente concluir este pedido, o hub de tarefas contém um marcador de posição para o estado de orquestração e uma mensagem de instância:

    workitems-illustration-step-1

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

  2. Uma função de trabalho executa um item de trabalho do orquestrador para processar a ExecutionStarted mensagem. Chama a função orchestrator que começa a executar o código de orquestração. Este código agenda duas atividades e, em seguida, deixa de ser executado quando está à espera dos resultados. Depois de o trabalho consolidar este item de trabalho, o hub de tarefas contém

    workitems-illustration-step-2

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

  3. Uma função de trabalho executa um item de trabalho de atividade para processar uma das TaskScheduled mensagens. Chama a função de atividade com a entrada "2". Quando a função de atividade é concluída, cria uma TaskCompleted mensagem que contém o resultado. Depois de o trabalho consolidar este item de trabalho, o hub de tarefas contém

    workitems-illustration-step-3

  4. Uma função de trabalho executa um item de trabalho do orquestrador para processar a TaskCompleted mensagem. Se a orquestração ainda estiver em cache na memória, pode simplesmente retomar a execução. Caso contrário, a função de trabalho reproduz primeiro o histórico para recuperar o estado atual da orquestração. Em seguida, continua a orquestração, fornecendo o resultado da atividade. Depois de receber este resultado, a orquestração ainda está à espera do resultado da outra atividade, pelo que deixa de ser executada mais uma vez. Depois de o trabalho consolidar este item de trabalho, o hub de tarefas contém

    workitems-illustration-step-4

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

  5. Uma função de trabalho executa um item de trabalho de atividade para processar a mensagem restante TaskScheduled . Chama a função de atividade com a entrada "1". Depois de o trabalho consolidar este item de trabalho, o hub de tarefas contém

    workitems-illustration-step-5

  6. Uma função de trabalho executa outro item de trabalho do orquestrador para processar a TaskCompleted mensagem. Depois de receber este segundo resultado, a orquestração é concluída. Depois de o trabalho consolidar este item de trabalho, o hub de tarefas contém

    workitems-illustration-step-6

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

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

Nota

A agenda apresentada não é a única: existem muitas agendas ligeiramente diferentes possíveis. Por exemplo, se a segunda atividade for concluída anteriormente, ambas as TaskCompleted mensagens de instância podem ser processadas por um único item de trabalho. Nesse caso, o histórico de execuções é um pouco mais curto, porque existem apenas dois episódios e contém os seguintes 10 eventos: OrchestratorStarted, , ExecutionStarted, TaskScheduled, TaskScheduledOrchestratorCompleted, OrchestratorStarted, TaskCompleted, TaskCompleted, , , ExecutionCompleted. OrchestratorCompleted

Gestão do hub de tarefas

Em seguida, vamos ver mais detalhadamente como os hubs de tarefas são criados ou eliminados, como utilizar os hubs de tarefas corretamente ao executar várias aplicações de funções e como os conteúdos dos hubs de tarefas podem ser inspecionados.

Criação e eliminação

Um hub de tarefas vazio com todos os recursos necessários é criado automaticamente no armazenamento quando uma aplicação de funções é iniciada pela primeira vez.

Se utilizar o fornecedor de Armazenamento do Azure predefinido, não é necessária nenhuma configuração adicional. Caso contrário, siga as instruções para configurar fornecedores de armazenamento para garantir que o fornecedor de armazenamento pode aprovisionar e aceder corretamente aos recursos de armazenamento necessários para o hub de tarefas.

Nota

O hub de tarefas não é eliminado automaticamente quando para ou elimina a aplicação de funções. Tem de eliminar manualmente o hub de tarefas, o respetivo conteúdo ou a conta de armazenamento que contém, se já não quiser manter esses dados.

Dica

Num cenário de desenvolvimento, poderá ter de reiniciar a partir de um estado limpo com frequência. Para o fazer rapidamente, pode apenas alterar o nome do hub de tarefas configurado. Isto forçará a criação de um novo hub de tarefas vazio quando reiniciar a aplicação. Tenha em atenção que os dados antigos não são eliminados neste caso.

Várias aplicações de funções

Se várias aplicações de funções partilharem uma conta de armazenamento, cada aplicação de funções tem de ser configurada com um nome de hub de tarefas separado. Este requisito também se aplica aos blocos de teste: cada bloco de teste tem de ser configurado com um nome exclusivo do hub de tarefas. Uma única conta de armazenamento pode conter vários hubs de tarefas. Geralmente, esta restrição também se aplica a outros fornecedores de armazenamento.

O diagrama seguinte ilustra um hub de tarefas por aplicação de funções em contas de Armazenamento do Azure partilhadas e dedicadas.

Diagrama a mostrar contas de armazenamento partilhadas e dedicadas.

Nota

A exceção à regra de partilha do hub de tarefas é se estiver a configurar a sua aplicação para recuperação após desastre regional. Veja o artigo recuperação após desastre e distribuição geográfica para obter mais informações.

Inspeção de conteúdo

Existem várias formas comuns de inspecionar o conteúdo de um hub de tarefas:

  1. Numa aplicação de funções, o objeto de cliente fornece métodos para consultar o arquivo de instâncias. Para saber mais sobre que tipos de consultas são suportadas, veja o artigo Gestão de Instâncias .
  2. Da mesma forma, a API HTTP oferece pedidos REST para consultar o estado das orquestrações e entidades. Consulte 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 visualização visual.

Para alguns dos fornecedores de armazenamento, também é possível inspecionar o hub de tarefas acedendo diretamente ao armazenamento subjacente:

  • Se utilizar o fornecedor de Armazenamento do Azure, os estados da instância são armazenados na Tabela de Instâncias e na Tabela de Histórico que pode ser inspecionada com ferramentas como Explorador de Armazenamento do Azure.
  • Se utilizar o fornecedor de armazenamento MSSQL, podem ser utilizadas consultas e ferramentas SQL para inspecionar os conteúdos do hub de tarefas dentro da base de dados.

Representação no armazenamento

Cada fornecedor de armazenamento utiliza uma organização interna diferente para representar os hubs de tarefas no armazenamento. Compreender esta organização, embora não seja necessário, pode ser útil ao resolver problemas de uma aplicação de funções ou ao tentar garantir destinos de desempenho, escalabilidade ou custos. Assim, explicamos brevemente, para cada fornecedor de armazenamento, como os dados estão organizados no armazenamento. Para obter mais informações sobre as várias opções do fornecedor de armazenamento e como se comparam, veja os fornecedores de armazenamento Durable Functions.

Fornecedor de Armazenamento do Azure

O fornecedor de Armazenamento do Azure representa o hub de tarefas no armazenamento com 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 destas chamadas filas de controlo representa uma partição que é atribuída a um subconjunto de todas as mensagens de instância, com base no hash do ID da instância.
  • Alguns contentores de blobs adicionais utilizados para blobs de concessão e/ou mensagens grandes.

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

Diagrama a mostrar a organização de armazenamento do fornecedor de armazenamento do Armazenamento do Azure para 4 filas de controlo.

Em seguida, descrevemos estes componentes e a função que desempenham mais detalhadamente.

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

Fornecedor de armazenamento netherite

As partições netherite dividem todo o estado do hub de tarefas num número especificado de partições. No armazenamento, são utilizados os seguintes recursos:

  • Um contentor de blobs do Armazenamento do 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 espaço de nomes Hubs de Eventos do Azure para entregar mensagens entre partições.

Por exemplo, um hub de tarefas com o PartitionCount = 32 nome mytaskhub é representado no armazenamento da seguinte forma:

Diagrama a mostrar a organização de armazenamento Netherite para 32 partições.

Nota

Todo o estado do hub de tarefas está armazenado dentro do contentor de x-storage blobs. A DurableTaskPartitions tabela e o espaço de nomes do EventHubs contêm dados redundantes: se os respetivos conteúdos forem perdidos, podem ser recuperados automaticamente. Por conseguinte, não é necessário configurar o espaço de nomes Hubs de Eventos do Azure para reter mensagens após o tempo de expiração predefinido.

Netherite utiliza um mecanismo de origem de eventos, com base num registo e pontos de verificação, para representar o estado atual de uma partição. São utilizados blobs de blocos e blobs de página. Não é possível ler este formato diretamente a partir do armazenamento, pelo que a aplicação de funções tem de estar em execução ao consultar o arquivo de instâncias.

Para obter mais informações sobre os hubs de tarefas do fornecedor de armazenamento Netherite, veja Informações do Hub de Tarefas para o fornecedor de armazenamento Netherite.

Fornecedor de armazenamento MSSQL

Todos os dados do hub de tarefas são armazenados numa única base de dados relacional com várias tabelas:

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

Diagrama a mostrar a organização de armazenamento MSSQL.

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

Para obter mais informações sobre os hubs de tarefas do fornecedor de armazenamento MSSQL, veja Informações do Hub de Tarefas para o fornecedor de armazenamento do Microsoft SQL (MSSQL).

Nomes do hub de tarefas

Os hubs de tarefas são identificados por um nome que tem de estar em conformidade com estas regras:

  • Contém apenas carateres alfanuméricos
  • Começa com uma letra
  • Tem um comprimento mínimo de 3 carateres, comprimento máximo de 45 carateres

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

host.json (Funções 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 com as definições da aplicação, conforme mostrado no seguinte host.json ficheiro de exemplo:

host.json (Funções 1.0)

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

host.json (Funções 2.0)

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

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

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

Nota

Ao utilizar blocos de implementação, é uma melhor prática configurar o nome do hub de tarefas com as definições da aplicação. Se quiser garantir que um bloco específico utiliza sempre um hub de tarefas específico, utilize as definições da aplicação "slot-sticky".

Além de host.json, os nomes dos hubs de tarefas também podem ser configurados em metadados de enlace de cliente de orquestração . Isto é útil se precisar de aceder a orquestrações ou entidades que vivem numa aplicação de funções separada. O código seguinte demonstra como escrever uma função que utiliza o enlace de cliente de orquestração para trabalhar com um hub de tarefas configurado como uma Definição de Aplicação:

[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);
}

Nota

O exemplo anterior destina-se a Durable Functions 2.x. Para Durable Functions 1.x, tem de utilizar DurableOrchestrationContext em vez de IDurableOrchestrationContext. Para obter mais informações sobre as diferenças entre versões, consulte o artigo Durable Functions versões.

Nota

Configurar nomes do hub de tarefas em metadados de enlace de cliente só é necessário quando utiliza uma aplicação de funções para aceder a orquestrações e entidades noutra aplicação de funções. Se as funções de cliente estiverem definidas na mesma aplicação de funções que as orquestrações e entidades, deve evitar especificar nomes do hub de tarefas nos metadados de enlace. Por predefinição, todos os enlaces de cliente obtêm os metadados do hub de tarefas a partir das definições host.json .

Os nomes dos hubs de tarefas têm de começar com uma letra e consistir apenas em letras e números. Se não for especificado, será utilizado um nome predefinido do hub de tarefas, conforme mostrado na tabela seguinte:

Versão da extensão Durable Nome do hub de tarefas predefinido
2.x Quando implementado no Azure, o nome do hub de tarefas é derivado do nome da aplicação de funções. Ao executar fora do Azure, o nome predefinido do hub de tarefas é TestHubName.
1.x O nome do hub de tarefas predefinido para todos os ambientes é DurableFunctionsHub.

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

Nota

O nome é o que diferencia um hub de tarefas de outro quando existem vários hubs de tarefas numa conta de armazenamento partilhada. Se tiver várias aplicações de funções a partilhar uma conta de armazenamento partilhada, tem de configurar explicitamente nomes diferentes para cada hub de tarefas nos ficheiros host.json . Caso contrário, as múltiplas aplicações de funções irão competir entre si por mensagens, o que pode resultar num comportamento indefinido, incluindo orquestrações a ficarem inesperadamente "bloqueadas" no Pending estado ou Running .

Passos seguintes