Compartilhar via


Introdução aos Reliable Actors do Service Fabric

Reliable Actors é uma estrutura de aplicativo do Service Fabric baseada no padrão Virtual Ator . A API Reliable Actors fornece um modelo de programação de thread único baseado nas garantias de escalabilidade e confiabilidade fornecidas pelo Service Fabric.

O que são atores?

Um ator é uma unidade isolada e independente de computação e estado com execução de thread único. O padrão ator é um modelo computacional para sistemas simultâneos ou distribuídos no qual um grande número desses atores pode executar simultaneamente e independentemente uns dos outros. Os atores podem comunicar uns com os outros e podem criar mais atores.

Quando usar atores confiáveis

O Service Fabric Reliable Actors é uma implementação do padrão de design do ator. Como acontece com qualquer padrão de design de software, a decisão de usar um padrão específico é tomada com base em se um problema de design de software se encaixa ou não no padrão.

Embora o padrão de design do ator possa ser um bom ajuste para uma série de problemas e cenários de sistemas distribuídos, uma consideração cuidadosa das restrições do padrão e da estrutura que o implementa deve ser feita. Como orientação geral, considere o padrão do ator para modelar seu problema ou cenário se:

  • Seu espaço de problema envolve um grande número (milhares ou mais) de unidades pequenas, independentes e isoladas de estado e lógica.
  • Você deseja trabalhar com objetos de thread único que não exigem interação significativa de componentes externos, incluindo o estado de consulta em um conjunto de atores.
  • Suas instâncias de ator não bloquearão chamadores com atrasos imprevisíveis emitindo operações de E/S.

Atores no Service Fabric

No Service Fabric, os atores são implementados na estrutura Reliable Players: uma estrutura de aplicativo baseada em padrões de atores criada com base nos Serviços Confiáveis do Service Fabric. Cada serviço de Ator Confiável que você escreve é, na verdade, um Serviço Confiável particionado e com monitoração de estado.

Cada ator é definido como uma instância de um tipo de ator, idêntica à maneira como um objeto .NET é uma instância de um tipo .NET. Por exemplo, pode haver um tipo de ator que implementa a funcionalidade de uma calculadora e pode haver muitos atores desse tipo que são distribuídos em vários nós em um cluster. Cada um desses atores é identificado exclusivamente por um ID de ator.

Ator Lifetime

Os atores do Service Fabric são virtuais, o que significa que seu tempo de vida não está vinculado à sua representação na memória. Como resultado, eles não precisam ser explicitamente criados ou destruídos. O tempo de execução de Atores Confiáveis ativa automaticamente um ator na primeira vez que ele recebe uma solicitação para esse ID de ator. Se um ator não for usado por um período de tempo, o tempo de execução dos Atores Confiáveis coletará o objeto na memória. Também manterá o conhecimento da existência do ator, caso precise ser reativado posteriormente. Para obter mais detalhes, consulte Ciclo de vida do ator e coleta de lixo.

Esta abstração ao longo da vida do ator virtual carrega algumas ressalvas como resultado do modelo de ator virtual, e de fato a implementação do Reliable Actors às vezes se desvia desse modelo.

  • Um ator é ativado automaticamente (fazendo com que um objeto ator seja construído) na primeira vez que uma mensagem é enviada para seu ID de ator. Depois de algum tempo, o objeto do ator é o lixo coletado. No futuro, usar o ID do ator novamente, faz com que um novo objeto ator seja construído. O estado de um ator sobrevive à vida útil do objeto quando armazenado no gestor do estado.
  • Chamar qualquer método de ator para um ID de ator ativa esse ator. Por esse motivo, os tipos de ator têm seu construtor chamado implicitamente pelo tempo de execução. Portanto, o código do cliente não pode passar parâmetros para o construtor do tipo ator, embora os parâmetros possam ser passados para o construtor do ator pelo próprio serviço. O resultado é que os atores podem ser construídos em um estado parcialmente inicializado no momento em que outros métodos são chamados nele, se o ator exigir parâmetros de inicialização do cliente. Não há um ponto de entrada único para a ativação de um ator do cliente.
  • Embora os Atores Confiáveis criem implicitamente objetos de atores; Você tem a capacidade de excluir explicitamente um ator e seu estado.

Distribuição e failover

Para fornecer escalabilidade e confiabilidade, o Service Fabric distribui atores por todo o cluster e os migra automaticamente de nós com falha para nós íntegros, conforme necessário. Esta é uma abstração sobre um Serviço Confiável particionado e com monitoração de estado. Distribuição, escalabilidade, confiabilidade e failover automático são fornecidos em virtude do fato de que os atores estão executando dentro de um Serviço Confiável com estado chamado Serviço de Ator.

Os atores são distribuídos pelas partições do Serviço de Ator, e essas partições são distribuídas pelos nós em um cluster do Service Fabric. Cada partição de serviço contém um conjunto de atores. O Service Fabric gerencia a distribuição e o failover das partições de serviço.

Por exemplo, um serviço de ator com nove partições implantadas em três nós usando o posicionamento de partição de ator padrão seria distribuído assim:

Distribuição de atores confiáveis

O Ator Framework gere o esquema de partições e as definições do intervalo de chaves para si. Isso simplifica algumas escolhas, mas também leva algumas considerações:

  • Os Serviços Confiáveis permitem que você escolha um esquema de particionamento, intervalo de chaves (ao usar um esquema de particionamento de intervalo) e contagem de partições. Reliable Actors é restrito ao esquema de particionamento de intervalo (o esquema Int64 uniforme) e requer que você use o intervalo de teclas Int64 completo.
  • Por padrão, os atores são colocados aleatoriamente em partições, resultando em distribuição uniforme.
  • Como os atores são colocados aleatoriamente, deve-se esperar que as operações do ator sempre exijam comunicação de rede, incluindo serialização e desserialização de dados de chamada de método, incorrendo em latência e sobrecarga.
  • Em cenários avançados, é possível controlar o posicionamento da partição do ator usando IDs de ator Int64 que mapeiam para partições específicas. No entanto, isso pode resultar em uma distribuição desequilibrada de atores entre partições.

Para obter mais informações sobre como os serviços de ator são particionados, consulte Conceitos de particionamento para atores.

Comunicação com atores

As interações do ator são definidas em uma interface que é compartilhada pelo ator que implementa a interface e pelo cliente que obtém um proxy para um ator através da mesma interface. Como essa interface é usada para invocar métodos de ator de forma assíncrona, cada método na interface deve ser Task-returning.

As invocações de método e suas respostas resultam em solicitações de rede em todo o cluster, portanto, os argumentos e os tipos de resultado das tarefas que eles retornam devem ser serializáveis pela plataforma. Em particular, eles devem ser serializáveis por contrato de dados.

O representante do ator

A API do cliente Reliable Actors fornece comunicação entre uma instância de ator e um cliente de ator. Para se comunicar com um ator, um cliente cria um objeto proxy de ator que implementa a interface do ator. O cliente interage com o ator invocando métodos no objeto proxy. O proxy ator pode ser usado para comunicação cliente-a-ator e ator-ator.

// Create a randomly distributed actor ID
ActorId actorId = ActorId.CreateRandom();

// This only creates a proxy object, it does not activate an actor or invoke any methods yet.
IMyActor myActor = ActorProxy.Create<IMyActor>(actorId, new Uri("fabric:/MyApp/MyActorService"));

// This will invoke a method on the actor. If an actor with the given ID does not exist, it will be activated by this method call.
await myActor.DoWorkAsync();
// Create actor ID with some name
ActorId actorId = new ActorId("Actor1");

// This only creates a proxy object, it does not activate an actor or invoke any methods yet.
MyActor myActor = ActorProxyBase.create(actorId, new URI("fabric:/MyApp/MyActorService"), MyActor.class);

// This will invoke a method on the actor. If an actor with the given ID does not exist, it will be activated by this method call.
myActor.DoWorkAsync().get();

Observe que as duas informações usadas para criar o objeto proxy do ator são o ID do ator e o nome do aplicativo. O ID do ator identifica exclusivamente o ator, enquanto o nome do aplicativo identifica o aplicativo do Service Fabric onde o ator está implantado.

A ActorProxyclasse (C#) / ActorProxyBase(Java) no lado do cliente executa a resolução necessária para localizar o ator por ID e abrir um canal de comunicação com ele. Ele também tenta localizar o ator nos casos de falhas de comunicação e failovers. Como resultado, a entrega de mensagens tem as seguintes características:

  • A entrega de mensagens é o melhor esforço.
  • Os atores podem receber mensagens duplicadas do mesmo cliente.

Simultaneidade

O tempo de execução do Reliable Actors fornece um modelo de acesso simples baseado em turnos para acessar os métodos do ator. Isso significa que não mais de um thread pode estar ativo dentro do código de um objeto ator a qualquer momento. O acesso baseado em turnos simplifica muito os sistemas simultâneos, pois não há necessidade de mecanismos de sincronização para acesso aos dados. Isso também significa que os sistemas devem ser projetados com considerações especiais para a natureza de acesso de thread único de cada instância do ator.

  • Uma única instância de ator não pode processar mais de uma solicitação de cada vez. Uma instância de ator pode causar um afunilamento de taxa de transferência se for esperado que ela trate solicitações simultâneas.
  • Os atores podem bloquear-se mutuamente se houver um pedido circular entre dois atores enquanto um pedido externo é feito a um dos atores simultaneamente. O tempo de execução do ator automaticamente expirará nas chamadas do ator e lançará uma exceção ao chamador para interromper possíveis situações de bloqueio.

Comunicação fiável com os intervenientes

Acesso por turnos

Um turno consiste na execução completa de um método de ator em resposta a uma solicitação de outros atores ou clientes, ou a execução completa de um retorno de chamada de temporizador/lembrete . Embora esses métodos e retornos de chamada sejam assíncronos, o tempo de execução do Actors não os intercala. Uma volta deve estar totalmente concluída antes de ser permitida uma nova volta. Em outras palavras, um método de ator ou retorno de chamada de temporizador/lembrete que está em execução no momento deve ser totalmente concluído antes que uma nova chamada para um método ou retorno de chamada seja permitida. Um método ou retorno de chamada é considerado concluído se a execução tiver retornado do método ou retorno de chamada e a tarefa retornada pelo método ou retorno de chamada tiver sido concluída. Vale a pena enfatizar que a simultaneidade baseada em turnos é respeitada mesmo em diferentes métodos, temporizadores e retornos de chamada.

O tempo de execução dos Atores impõe a simultaneidade baseada em turnos adquirindo um bloqueio por ator no início de um turno e liberando o bloqueio no final do turno. Assim, a simultaneidade baseada em turnos é aplicada por ator e não entre atores. Métodos de atores e retornos de chamada de temporizador/lembrete podem ser executados simultaneamente em nome de diferentes atores.

O exemplo a seguir ilustra os conceitos acima. Considere um tipo de ator que implementa dois métodos assíncronos (digamos, Method1 e Method2), um temporizador e um lembrete. O diagrama abaixo mostra um exemplo de uma linha do tempo para a execução desses métodos e retornos de chamada em nome de dois atores (ActorId1 e ActorId2) que pertencem a esse tipo de ator.

Acesso e simultaneidade baseados em turnos de tempo de execução de atores confiáveis

Este diagrama segue estas convenções:

  • Cada linha vertical mostra o fluxo lógico de execução de um método ou um retorno de chamada em nome de um determinado ator.
  • Os eventos marcados em cada linha vertical ocorrem em ordem cronológica, com eventos mais recentes ocorrendo abaixo dos mais antigos.
  • Cores diferentes são usadas para linhas do tempo correspondentes a diferentes atores.
  • O realce é usado para indicar a duração durante a qual o bloqueio por ator é mantido em nome de um método ou retorno de chamada.

Alguns pontos importantes a considerar:

  • Enquanto Method1 está sendo executado em nome de ActorId2 em resposta à solicitação do cliente xyz789, outra solicitação de cliente (abc123) chega que também requer que Method1 seja executado por ActorId2. No entanto, a segunda execução do Método1 não começa até que a execução anterior tenha terminado. Da mesma forma, um lembrete registrado pelo ActorId2 é acionado enquanto o Method1 está sendo executado em resposta à solicitação do cliente xyz789. O retorno de chamada de lembrete é executado somente depois que ambas as execuções do Method1 são concluídas. Tudo isso se deve à simultaneidade baseada em turnos que está sendo aplicada para o ActorId2.
  • Da mesma forma, a simultaneidade baseada em turnos também é imposta para ActorId1, como demonstrado pela execução de Method1, Method2 e o retorno de chamada do temporizador em nome de ActorId1 acontecendo de forma serial.
  • A execução do Method1 em nome de ActorId1 sobrepõe-se à sua execução em nome de ActorId2. Isso ocorre porque a simultaneidade baseada em turnos é imposta apenas dentro de um ator e não entre eles.
  • Em algumas das execuções de método/retorno de chamada, o Task(C#) / CompletableFuture(Java) retornado pelo método/retorno de chamada termina após o retorno do método. Em alguns outros, a operação assíncrona já foi concluída no momento em que o método/retorno de chamada retorna. Em ambos os casos, o bloqueio por ator é liberado somente depois que o método/retorno de chamada retorna e a operação assíncrona é concluída.

Reentrada

O tempo de execução do Actors permite a reentrância por padrão. Isso significa que se um método de ator do Ator A chamar um método no Ator B, que por sua vez chama outro método no Ator A, esse método poderá ser executado. Isso ocorre porque faz parte do mesmo contexto lógico de cadeia de chamadas. Todas as chamadas de temporizador e lembrete começam com o novo contexto lógico de chamada. Veja a reentrância de Atores Confiáveis para obter mais detalhes.

Âmbito das garantias de simultaneidade

O tempo de execução do Actors fornece essas garantias de simultaneidade em situações em que controla a invocação desses métodos. Por exemplo, ele fornece essas garantias para as invocações de método que são feitas em resposta a uma solicitação do cliente, bem como para retornos de chamada de temporizador e lembrete. No entanto, se o código do ator invocar diretamente esses métodos fora dos mecanismos fornecidos pelo tempo de execução dos atores, o tempo de execução não poderá fornecer garantias de simultaneidade. Por exemplo, se o método for invocado no contexto de alguma tarefa que não esteja associada à tarefa retornada pelos métodos do ator, o tempo de execução não poderá fornecer garantias de simultaneidade. Se o método for invocado a partir de um thread que o ator cria por conta própria, o tempo de execução também não poderá fornecer garantias de simultaneidade. Portanto, para executar operações em segundo plano, os atores devem usar temporizadores e lembretes de atores que respeitem a simultaneidade baseada em turnos.

Próximos passos

Comece criando seu primeiro serviço de Atores Confiáveis: