Introdução ao Service Fabric Reliable Actors

Reliable Actors é uma arquitetura de aplicação do Service Fabric baseada no padrão actor virtual . A API Reliable Actors fornece um modelo de programação de thread único baseado nas garantias de escalabilidade e fiabilidade fornecidas pelo Service Fabric.

O que são os Actores?

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

Quando utilizar o Reliable Actors

O Service Fabric Reliable Actors é uma implementação do padrão de design do ator. Tal como acontece com qualquer padrão de design de software, a decisão de utilizar um padrão específico é tomada com base no facto de um problema de design de software se adequar ou não ao padrão.

Embora o padrão de design do ator possa ser uma boa opção para vários cenários e problemas de sistemas distribuídos, tem de ter em consideração cuidadosamente as restrições do padrão e a arquitetura que o implementa. Como orientação geral, considere o padrão do ator para modelar o seu problema ou cenário se:

  • O espaço problemático envolve um grande número (milhares ou mais) de unidades pequenas, independentes e isoladas de estado e lógica.
  • Quer trabalhar com objetos de thread único que não requerem interação significativa de componentes externos, incluindo o estado de consulta num conjunto de atores.
  • As instâncias de ator não bloqueiam os chamadores com atrasos imprevisíveis ao emitir operações de E/S.

Atores no Service Fabric

No Service Fabric, os atores são implementados na arquitetura Reliable Actors: uma arquitetura de aplicação baseada em padrões de ator criada com base no Service Fabric Reliable Services. Cada serviço Reliable Actor que escreve é, na verdade, um Reliable Service particionado e com estado.

Cada ator é definido como uma instância de um tipo de ator, idêntico à forma 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 num cluster. Cada ator é identificado exclusivamente por um ID de ator.

Duração do Ator

Os atores do Service Fabric são virtuais, o que significa que a sua vida não está ligada à sua representação dentro da memória. Como resultado, não precisam de ser explicitamente criados ou destruídos. O runtime reliable Actors ativa automaticamente um ator na primeira vez que recebe um pedido para esse ID de ator. Se um ator não for utilizado durante um período de tempo, o runtime reliable Actors recolhe o objeto dentro da memória. Também manterá o conhecimento da existência do ator caso precise de ser reativado mais tarde. Para obter mais detalhes, veja Ciclo de vida do ator e recolha de lixo.

Esta abstração de vida de ator virtual tem algumas ressalvas como resultado do modelo de ator virtual e, na verdade, a implementação reliable Actors desvia-se por vezes deste modelo.

  • Um ator é ativado automaticamente (fazendo com que um objeto de ator seja construído) na primeira vez que uma mensagem é enviada para o seu ID de ator. Após algum período de tempo, o objeto do ator é lixo recolhido. No futuro, utilizar novamente o ID do ator faz com que seja construído um novo objeto de ator. O estado de um ator sobrevive à duração do objeto quando armazenado no gestor de estado.
  • Chamar qualquer método de ator para um ID de ator ativa esse ator. Por esta razão, os tipos de ator têm o seu construtor chamado implicitamente pelo runtime. Por conseguinte, o código de cliente não pode transmitir parâmetros ao construtor do tipo ator, embora os parâmetros possam ser transmitidos ao construtor do ator pelo próprio serviço. O resultado é que os atores podem ser construídos num estado parcialmente inicializado quando forem chamados outros métodos, se o ator necessitar de parâmetros de inicialização do cliente. Não existe um único ponto de entrada para a ativação de um ator do cliente.
  • Embora a Reliable Actors crie implicitamente objetos de ator; tem a capacidade de eliminar explicitamente um ator e o respetivo estado.

Distribuição e ativação pós-falha

Para proporcionar escalabilidade e fiabilidade, o Service Fabric distribui atores por todo o cluster e migra-os automaticamente de nós com falhas para os que estão em bom estado de funcionamento, conforme necessário. Trata-se de uma abstração sobre um Reliable Service particionado e com estado. A distribuição, a escalabilidade, a fiabilidade e a ativação pós-falha automática são fornecidas devido ao facto de os atores estarem a ser executados dentro de um Serviço Fiável com estado denominado 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 num cluster do Service Fabric. Cada partição de serviço contém um conjunto de atores. O Service Fabric gere a distribuição e a ativação pós-falha das partições de serviço.

Por exemplo, um serviço de ator com nove partições implementadas em três nós através da colocação de partição de ator predefinida seria distribuído assim:

Distribuição reliable Actors

O Actor Framework gere o esquema de partição e as definições de intervalo de chaves por si. Isto simplifica algumas opções, mas também tem alguma consideração:

  • O Reliable Services permite-lhe escolher um esquema de criação de partições, um intervalo de chaves (ao utilizar um esquema de criação de partições de intervalo) e a contagem de partições. O Reliable Actors está restrito ao esquema de criação de partições de intervalo (o esquema Int64 uniforme) e requer que utilize o intervalo de chaves int64 completo.
  • Por predefinição, os atores são colocados aleatoriamente em partições, resultando numa distribuição uniforme.
  • Uma vez que os atores são colocados aleatoriamente, deve esperar-se que as operações de ator exijam sempre comunicação de rede, incluindo serialização e desserialização de dados de chamadas de métodos, latência incorreta e sobrecarga.
  • Em cenários avançados, é possível controlar a colocação de partições de ator através de IDs de ator Int64 que mapeiam para partições específicas. No entanto, fazê-lo pode resultar numa distribuição desequilibrada de atores entre partições.

Para obter mais informações sobre como os serviços de ator são particionados, veja conceitos de criação de partições para atores.

Comunicação de ator

As interações de ator são definidas numa interface partilhada pelo ator que implementa a interface e o cliente que obtém um proxy para um ator através da mesma interface. Uma vez que esta interface é utilizada para invocar métodos de ator de forma assíncrona, todos os métodos na interface têm de estar a devolver tarefas.

As invocações de métodos e as respetivas respostas resultam, em última análise, em pedidos de rede em todo o cluster, pelo que os argumentos e os tipos de resultados das tarefas que devolvem têm de ser serializáveis pela plataforma. Em particular, têm de ser serializáveis por contratos de dados.

O proxy do ator

A API de cliente Reliable Actors fornece comunicação entre uma instância de ator e um cliente ator. Para comunicar com um ator, um cliente cria um objeto proxy de ator que implementa a interface do ator. O cliente interage com o ator ao invocar métodos no objeto proxy. O proxy de ator pode ser usado para comunicação cliente a ator e actor-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();

Tenha em atenção que as duas informações utilizadas para criar o objeto proxy do ator são o ID do ator e o nome da aplicação. O ID do ator identifica exclusivamente o ator, enquanto o nome da aplicação identifica a aplicação do Service Fabric onde o ator é implementado.

A ActorProxyclasse (C#) / ActorProxyBase(Java) do lado do cliente executa a resolução necessária para localizar o ator por ID e abrir um canal de comunicação com o mesmo. Também tenta localizar o ator em casos de falhas de comunicação e ativações pós-falha. 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 runtime reliable Actors fornece um modelo de acesso baseado em turn simples para aceder a métodos de ator. Isto significa que não é possível ativar mais do que um thread dentro do código de um objeto de ator em qualquer altura. O acesso baseado em turn-based simplifica consideravelmente os sistemas simultâneos, uma vez que não é necessário mecanismos de sincronização para o acesso a dados. Também significa que os sistemas têm de ser concebidos com considerações especiais para a natureza de acesso de thread único de cada instância de ator.

  • Uma única instância de ator não pode processar mais do que um pedido de cada vez. Uma instância de ator pode causar um estrangulamento de débito se se esperar que processe pedidos simultâneos.
  • Os atores podem bloquear-se mutuamente se houver um pedido circular entre dois atores enquanto é feito um pedido externo a um dos atores em simultâneo. O runtime do ator excederá automaticamente o tempo limite das chamadas do ator e lançará uma exceção ao autor da chamada para interromper possíveis situações de impasse.

Comunicação reliable Actors

Acesso baseado em turn-based

Uma vez consiste na execução completa de um método de ator em resposta a um pedido de outros atores ou clientes ou à execução completa de uma chamada de retorno de temporizador/lembrete . Embora estes métodos e chamadas de retorno sejam assíncronos, o runtime actors não os intercala. Uma volta tem de estar totalmente concluída antes de ser permitida uma nova volta. Por outras palavras, um método de ator ou chamada de retorno de temporizador/lembrete que está atualmente a ser executada tem de estar totalmente concluído antes de ser permitida uma nova chamada para um método ou chamada de retorno. Considera-se que um método ou chamada de retorno foi concluído se a execução tiver regressado do método ou chamada de retorno e a tarefa devolvida pelo método ou chamada de retorno tiver sido concluída. Vale a pena enfatizar que a simultaneidade baseada na transformação é respeitada mesmo em diferentes métodos, temporizadores e chamadas de retorno.

O runtime actors impõe a simultaneidade baseada em curvas, adquirindo um bloqueio por ator no início de uma curva e libertando o bloqueio no final da curva. Assim, a simultaneidade baseada na reviravolta é imposta por actor e não entre actores. Os métodos de ator e chamadas de retorno de temporizador/lembrete podem ser executadas em simultâneo em nome de diferentes atores.

O exemplo seguinte ilustra os conceitos acima. Considere um tipo de ator que implementa dois métodos assíncronos (por exemplo, Method1 e Method2), um temporizador e um lembrete. O diagrama abaixo mostra um exemplo de uma linha cronológica para a execução destes métodos e chamadas de retorno em nome de dois atores (ActorId1 e ActorId2) que pertencem a este tipo de ator.

Simultaneidade e acesso baseados em turn-based do runtime reliable Actors

Este diagrama segue estas convenções:

  • Cada linha vertical mostra o fluxo lógico de execução de um método ou uma chamada de retorno em nome de um ator específico.
  • Os eventos marcados em cada linha vertical ocorrem por ordem cronológica, com eventos mais recentes a ocorrerem abaixo dos mais antigos.
  • São utilizadas cores diferentes para linhas cronológicas correspondentes a diferentes atores.
  • O realce é utilizado para indicar a duração para a qual o bloqueio por ator é mantido em nome de um método ou chamada de retorno.

Alguns pontos importantes a considerar:

  • Enquanto a Method1 está a ser executada em nome do ActorId2 em resposta ao pedido de cliente xyz789, chega outro pedido de cliente (abc123) que também requer que o Method1 seja executado pelo ActorId2. No entanto, a segunda execução do Method1 não começa até que a execução anterior seja concluída. Da mesma forma, um lembrete registado pelo ActorId2 é acionado enquanto o Method1 está a ser executado em resposta ao pedido de cliente xyz789. A chamada de retorno do lembrete só é executada depois de ambas as execuções do Method1 estarem concluídas. Tudo isto deve-se à imposição da simultaneidade baseada na mudança para ActorId2.
  • Da mesma forma, a simultaneidade baseada na transformação também é imposta para ActorId1, conforme demonstrado pela execução da Method1, Method2 e a chamada de retorno do temporizador em nome de ActorId1 que ocorre de forma em série.
  • A execução do Method1 em nome do ActorId1 sobrepõe-se à execução em nome de ActorId2. Isto acontece porque a simultaneidade baseada na transformação é imposta apenas dentro de um ator e não entre atores.
  • Em algumas das execuções de método/chamada de retorno, o Task(C#) / CompletableFuture(Java) devolvido pelo método/chamada de retorno é concluído após a devolução do método. Noutras, a operação assíncrona já foi concluída quando o método/chamada de retorno é devolvido. Em ambos os casos, o bloqueio por ator é libertado apenas após o método/chamada de retorno devolver e a operação assíncrona terminar.

Reentrada

O runtime Atores permite a reentraência por predefinição. Isto significa que, se um método actor de Actor A chamar um método no Actor B, que por sua vez chama outro método no Ator A, esse método é autorizado a ser executado. Isto deve-se ao facto de fazer parte do mesmo contexto lógico de cadeia de chamadas. Todas as chamadas de temporizador e lembrete começam com o novo contexto de chamada lógica. Veja a reentraência do Reliable Actors para obter mais detalhes.

Âmbito das garantias de simultaneidade

O runtime actors fornece estas garantias de simultaneidade em situações em que controla a invocação destes métodos. Por exemplo, fornece estas garantias para as invocações do método que são feitas em resposta a um pedido de cliente, bem como para chamadas de retorno de temporizador e lembrete. No entanto, se o código do ator invocar diretamente estes métodos fora dos mecanismos fornecidos pelo runtime dos Atores, o runtime não pode fornecer quaisquer garantias de simultaneidade. Por exemplo, se o método for invocado no contexto de uma tarefa que não está associada à tarefa devolvida pelos métodos de ator, o runtime não pode fornecer garantias de simultaneidade. Se o método for invocado a partir de um thread que o ator cria por si só, o runtime também não pode fornecer garantias de simultaneidade. Por conseguinte, para realizar operações em segundo plano, os atores devem utilizar temporizadores de ator e lembretes de ator que respeitam a simultaneidade baseada na transformação.

Passos seguintes

Comece por criar o seu primeiro serviço Reliable Actors: