Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
O Azure Service Fabric enquanto plataforma é completamente agnóstico quanto à comunicação entre serviços. Todos os protocolos e stacks são aceitáveis, desde UDP a HTTP. Cabe ao programador do serviço decidir como os serviços devem comunicar. O framework de aplicações Reliable Services fornece pilhas de comunicação integradas, bem como APIs que pode usar para construir os seus componentes de comunicação personalizados.
Configurar comunicação de serviço
A API de Serviços Fiáveis utiliza uma interface simples para comunicação de serviços. Para abrir um endpoint para o seu serviço, basta implementar esta interface:
public interface ICommunicationListener
{
Task<string> OpenAsync(CancellationToken cancellationToken);
Task CloseAsync(CancellationToken cancellationToken);
void Abort();
}
public interface CommunicationListener {
CompletableFuture<String> openAsync(CancellationToken cancellationToken);
CompletableFuture<?> closeAsync(CancellationToken cancellationToken);
void abort();
}
Pode então adicionar a implementação do seu ouvinte de comunicação retornando-a num substituto de método de classe baseado em serviços.
Para serviços apátridas:
public class MyStatelessService : StatelessService
{
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
...
}
...
}
public class MyStatelessService extends StatelessService {
@Override
protected List<ServiceInstanceListener> createServiceInstanceListeners() {
...
}
...
}
Para serviços com estado:
@Override
protected List<ServiceReplicaListener> createServiceReplicaListeners() {
...
}
...
public class MyStatefulService : StatefulService
{
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
...
}
...
}
Em ambos os casos, devolves uma coleção de ouvintes. Usar múltiplos ouvintes permite que o seu serviço ouça em múltiplos endpoints, potencialmente usando protocolos diferentes. Por exemplo, pode ter um ouvinte HTTP e um ouvinte WebSocket separado. Pode migrar de comunicação remota insegura para segura, primeiro ativando ambos os cenários ao ter tanto um listener inseguro como um listener seguro. Cada ouvinte recebe um nome, e a coleção resultante de pares de nomes : endereços é representada como um objeto JSON quando um cliente solicita os endereços de escuta para uma instância de serviço ou uma partição.
Num serviço sem estado, o override devolve uma coleção de ServiceInstanceListeners. A ServiceInstanceListener contém uma função para criar um ICommunicationListener(C#) / CommunicationListener(Java) e dá-lhe um nome. Para serviços com estado, a substituição devolve uma coleção de ServiceReplicaListeners. Isto é ligeiramente diferente do seu equivalente sem estado, porque um ServiceReplicaListener tem a opção de abrir uma ICommunicationListener nas réplicas secundárias. Não só pode usar múltiplos ouvintes de comunicação num serviço, como também pode especificar quais os ouvintes que aceitam pedidos em réplicas secundárias e quais ouvem apenas nas réplicas primárias.
Por exemplo, pode ter um ServiceRemotingListener que aceita chamadas RPC apenas em réplicas primárias, e um segundo ouvinte personalizado que aceita pedidos de leitura em réplicas secundárias via HTTP:
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
return new[]
{
new ServiceReplicaListener(context =>
new MyCustomHttpListener(context),
"HTTPReadonlyEndpoint",
true),
new ServiceReplicaListener(context =>
this.CreateServiceRemotingListener(context),
"rpcPrimaryEndpoint",
false)
};
}
Observação
Ao criar múltiplos ouvintes para um serviço, cada ouvinte deve receber um nome único.
Por fim, descreva os endpoints que são necessários para o serviço no manifesto de serviço , na secção sobre endpoints.
<Resources>
<Endpoints>
<Endpoint Name="WebServiceEndpoint" Protocol="http" Port="80" />
<Endpoint Name="OtherServiceEndpoint" Protocol="tcp" Port="8505" />
<Endpoints>
</Resources>
O ouvinte de comunicação pode aceder aos recursos de endpoint que lhe estão alocados no CodePackageActivationContext a partir do ServiceContext. O ouvinte pode então começar a ouvir pedidos quando é aberto.
var codePackageActivationContext = serviceContext.CodePackageActivationContext;
var port = codePackageActivationContext.GetEndpoint("ServiceEndpoint").Port;
CodePackageActivationContext codePackageActivationContext = serviceContext.getCodePackageActivationContext();
int port = codePackageActivationContext.getEndpoint("ServiceEndpoint").getPort();
Observação
Os recursos do endpoint são comuns a todo o pacote de serviços e são alocados pelo Service Fabric quando o pacote de serviço é ativado. Múltiplas réplicas de serviço alojadas no mesmo ServiceHost podem partilhar a mesma porta. Isto significa que o ouvinte de comunicação deve suportar a partilha de portas. A forma recomendada de fazer isto é o ouvinte de comunicação usar o ID da partição e o ID da réplica/instância quando gerar o endereço de escuta.
Registo do endereço de serviço
Um serviço do sistema chamado Naming Service executa em clusters Service Fabric. O Naming Service é um registador de serviços e seus endereços onde cada instância ou réplica do serviço está a operar. Quando o OpenAsync(C#) / openAsync(Java) método de um ICommunicationListener(C#) / CommunicationListener(Java) termina, o seu valor de retorno é registado no Naming Service. Este valor de retorno que é publicado no Naming Service é uma cadeia cujo valor pode ser qualquer coisa. Este valor de cadeia de caracteres é o que os clientes veem quando solicitam um endereço ao serviço a partir do Serviço de Nomeação.
public Task<string> OpenAsync(CancellationToken cancellationToken)
{
EndpointResourceDescription serviceEndpoint = serviceContext.CodePackageActivationContext.GetEndpoint("ServiceEndpoint");
int port = serviceEndpoint.Port;
this.listeningAddress = string.Format(
CultureInfo.InvariantCulture,
"http://+:{0}/",
port);
this.publishAddress = this.listeningAddress.Replace("+", FabricRuntime.GetNodeContext().IPAddressOrFQDN);
this.webApp = WebApp.Start(this.listeningAddress, appBuilder => this.startup.Invoke(appBuilder));
// the string returned here will be published in the Naming Service.
return Task.FromResult(this.publishAddress);
}
public CompletableFuture<String> openAsync(CancellationToken cancellationToken)
{
EndpointResourceDescription serviceEndpoint = serviceContext.getCodePackageActivationContext.getEndpoint("ServiceEndpoint");
int port = serviceEndpoint.getPort();
this.publishAddress = String.format("http://%s:%d/", FabricRuntime.getNodeContext().getIpAddressOrFQDN(), port);
this.webApp = new WebApp(port);
this.webApp.start();
/* the string returned here will be published in the Naming Service.
*/
return CompletableFuture.completedFuture(this.publishAddress);
}
O Service Fabric fornece APIs que permitem a clientes e outros serviços pedir este endereço pelo nome do serviço. Isto é importante porque o endereço de serviço não é estático. Os serviços são deslocados no cluster para fins de equilíbrio e disponibilidade de recursos. Este é o mecanismo que permite aos clientes resolver o endereço de escuta de um serviço.
Observação
Para uma explicação completa de como escrever um ouvinte de comunicação, veja Service Fabric Web API Services com auto-hospedagem OWIN para C#, enquanto para Java pode escrever a sua própria implementação de servidor HTTP, veja exemplo de aplicação EchoServer em https://github.com/Azure-Samples/service-fabric-java-getting-started.
Comunicação com um serviço
A API de Serviços Fiáveis fornece as seguintes bibliotecas para escrever clientes que comunicam com os serviços.
Resolução de endpoints de serviço
O primeiro passo para comunicar com um serviço é resolver um endereço de endpoint da partição ou instância do serviço com que pretende comunicar. A ServicePartitionResolver(C#) / FabricServicePartitionResolver(Java) classe utilitária é uma primitiva básica que ajuda os clientes a determinar o ponto final de um serviço em tempo de execução. Na terminologia do Service Fabric, o processo de determinar o ponto final de um serviço é referido como resolução do ponto final do serviço.
Para se ligar a serviços dentro de um cluster, pode-se criar ServicePartitionResolver usando as definições predefinidas. Este é o uso recomendado para a maioria das situações:
ServicePartitionResolver resolver = ServicePartitionResolver.GetDefault();
FabricServicePartitionResolver resolver = FabricServicePartitionResolver.getDefault();
Para se ligar a serviços num cluster diferente, pode ser criado um ServicePartitionResolver com um conjunto de endpoints de gateway de cluster. Note que os endpoints de gateway são apenas diferentes pontos de acesso para se ligar ao mesmo cluster. Por exemplo:
ServicePartitionResolver resolver = new ServicePartitionResolver("mycluster.cloudapp.azure.com:19000", "mycluster.cloudapp.azure.com:19001");
FabricServicePartitionResolver resolver = new FabricServicePartitionResolver("mycluster.cloudapp.azure.com:19000", "mycluster.cloudapp.azure.com:19001");
Alternativamente, ServicePartitionResolver pode ser atribuída uma função para criar um FabricClient para usar internamente:
public delegate FabricClient CreateFabricClientDelegate();
public FabricServicePartitionResolver(CreateFabricClient createFabricClient) {
...
}
public interface CreateFabricClient {
public FabricClient getFabricClient();
}
FabricClient é o objeto utilizado para comunicar com o cluster Service Fabric para várias operações de gestão no cluster. Isto é útil quando queres mais controlo sobre como um resolvedor de partição de serviço interage com o teu cluster.
FabricClient faz cache internamente e é geralmente dispendioso de criar, por isso é importante reutilizar FabricClient as instâncias tanto quanto possível.
ServicePartitionResolver resolver = new ServicePartitionResolver(() => CreateMyFabricClient());
FabricServicePartitionResolver resolver = new FabricServicePartitionResolver(() -> new CreateFabricClientImpl());
Um método resolve é então usado para recuperar o endereço de um serviço ou de uma partição de serviço para serviços particionados.
ServicePartitionResolver resolver = ServicePartitionResolver.GetDefault();
ResolvedServicePartition partition =
await resolver.ResolveAsync(new Uri("fabric:/MyApp/MyService"), new ServicePartitionKey(), cancellationToken);
FabricServicePartitionResolver resolver = FabricServicePartitionResolver.getDefault();
CompletableFuture<ResolvedServicePartition> partition =
resolver.resolveAsync(new URI("fabric:/MyApp/MyService"), new ServicePartitionKey());
Um endereço de serviço pode ser facilmente resolvido usando um ServicePartitionResolver, mas é necessário mais trabalho para garantir que o endereço resolvido pode ser usado corretamente. O seu cliente precisa de detetar se a tentativa de ligação falhou devido a um erro transitório e pode ser tentado novamente (por exemplo, serviço transferido ou temporariamente indisponível), ou um erro permanente (por exemplo, o serviço foi eliminado ou o recurso solicitado deixou de existir). Instâncias de serviço ou réplicas podem mover-se de nó em nó a qualquer momento por múltiplas razões. O endereço do serviço resolvido através do ServicePartitionResolver pode estar desatualizado no momento em que o código do cliente tentar ligar-se. Nesse caso, novamente, o cliente precisa de resolver novamente o endereço. Fornecer o anterior ResolvedServicePartition indica que o resolvedor precisa de tentar novamente em vez de simplesmente recuperar um endereço em cache.
Normalmente, o código cliente não precisa de trabalhar diretamente com o ServicePartitionResolver. É criado e transmitido às fábricas de clientes de comunicação na API de Serviços Fiáveis. As fábricas usam o resolver internamente para gerar um objeto cliente que pode ser usado para comunicar com os serviços.
Clientes de comunicação e fábricas
A biblioteca de fábrica de comunicações implementa um padrão típico de retentativas de gestão de falhas que facilita a retentativa de ligações aos endpoints de serviço resolvidos. A biblioteca de factory fornece o mecanismo de repetição enquanto você fornece os manipuladores de erros.
ICommunicationClientFactory(C#) / CommunicationClientFactory(Java) define a interface base implementada por uma fábrica de clientes de comunicação que produz clientes capazes de comunicar com um serviço Service Fabric. A implementação do CommunicationClientFactory depende da pilha de comunicação usada pelo serviço Service Fabric onde o cliente pretende comunicar. A API de Serviços Fiáveis fornece um CommunicationClientFactoryBase<TCommunicationClient>. Isto fornece uma implementação base da interface CommunicationClientFactory e executa tarefas comuns a todas as pilhas de comunicação. (Estas tarefas incluem usar um ServicePartitionResolver para determinar o endpoint do serviço). Os clientes normalmente implementam a classe abstrata CommunicationClientFactoryBase para lidar com a lógica específica da stack de comunicação.
O cliente de comunicação apenas recebe um endereço e usa-o para se ligar a um serviço. O cliente pode usar qualquer protocolo que quiser.
public class MyCommunicationClient : ICommunicationClient
{
public ResolvedServiceEndpoint Endpoint { get; set; }
public string ListenerName { get; set; }
public ResolvedServicePartition ResolvedServicePartition { get; set; }
}
public class MyCommunicationClient implements CommunicationClient {
private ResolvedServicePartition resolvedServicePartition;
private String listenerName;
private ResolvedServiceEndpoint endPoint;
/*
* Getters and Setters
*/
}
A fábrica de clientes é principalmente responsável por criar clientes de comunicação. Para clientes que não mantêm uma ligação persistente, como um cliente HTTP, a fábrica só precisa de criar e devolver o cliente. Outros protocolos que mantêm uma ligação persistente, como alguns protocolos binários, também devem ser validados (ValidateClient(string endpoint, MyCommunicationClient client)) pela fábrica para determinar se a ligação precisa de ser recriada.
public class MyCommunicationClientFactory : CommunicationClientFactoryBase<MyCommunicationClient>
{
protected override void AbortClient(MyCommunicationClient client)
{
}
protected override Task<MyCommunicationClient> CreateClientAsync(string endpoint, CancellationToken cancellationToken)
{
}
protected override bool ValidateClient(MyCommunicationClient clientChannel)
{
}
protected override bool ValidateClient(string endpoint, MyCommunicationClient client)
{
}
}
public class MyCommunicationClientFactory extends CommunicationClientFactoryBase<MyCommunicationClient> {
@Override
protected boolean validateClient(MyCommunicationClient clientChannel) {
}
@Override
protected boolean validateClient(String endpoint, MyCommunicationClient client) {
}
@Override
protected CompletableFuture<MyCommunicationClient> createClientAsync(String endpoint) {
}
@Override
protected void abortClient(MyCommunicationClient client) {
}
}
Finalmente, um tratador de exceções é responsável por determinar que ação tomar quando ocorre uma exceção. As exceções são categorizadas em retentáveis e não retentáveis.
- Exceções não repetíveis são simplesmente relançadas ao chamador.
- As exceções retentáveis são ainda categorizadas em transitórias e não transitórias.
- Exceções transitórias são aquelas que podem ser tentadas novamente sem reresolver o endereço do endpoint do serviço. Estes incluirão problemas de rede transitórios ou respostas a erros de serviço que não sejam aquelas que indicam que o endereço do endpoint de serviço não existe.
- Exceções não transitórias são aquelas que exigem que o endereço do endpoint de serviço seja reresolvido. Estas incluem exceções que indicam que o endpoint do serviço não pôde ser alcançado, indicando que o serviço foi movido para um nó diferente.
O TryHandleException toma uma decisão sobre uma determinada exceção. Se não souber que decisões tomar sobre uma exceção, deve responder falso. Se souber que decisão tomar, deve definir o resultado em conformidade e responder verdadeiro.
class MyExceptionHandler : IExceptionHandler
{
public bool TryHandleException(ExceptionInformation exceptionInformation, OperationRetrySettings retrySettings, out ExceptionHandlingResult result)
{
// if exceptionInformation.Exception is known and is transient (can be retried without re-resolving)
result = new ExceptionHandlingRetryResult(exceptionInformation.Exception, true, retrySettings, retrySettings.DefaultMaxRetryCount);
return true;
// if exceptionInformation.Exception is known and is not transient (indicates a new service endpoint address must be resolved)
result = new ExceptionHandlingRetryResult(exceptionInformation.Exception, false, retrySettings, retrySettings.DefaultMaxRetryCount);
return true;
// if exceptionInformation.Exception is unknown (let the next IExceptionHandler attempt to handle it)
result = null;
return false;
}
}
public class MyExceptionHandler implements ExceptionHandler {
@Override
public ExceptionHandlingResult handleException(ExceptionInformation exceptionInformation, OperationRetrySettings retrySettings) {
/* if exceptionInformation.getException() is known and is transient (can be retried without re-resolving)
*/
result = new ExceptionHandlingRetryResult(exceptionInformation.getException(), true, retrySettings, retrySettings.getDefaultMaxRetryCount());
return true;
/* if exceptionInformation.getException() is known and is not transient (indicates a new service endpoint address must be resolved)
*/
result = new ExceptionHandlingRetryResult(exceptionInformation.getException(), false, retrySettings, retrySettings.getDefaultMaxRetryCount());
return true;
/* if exceptionInformation.getException() is unknown (let the next ExceptionHandler attempt to handle it)
*/
result = null;
return false;
}
}
Compilando todos os elementos
Com um ICommunicationClient(C#) / CommunicationClient(Java), ICommunicationClientFactory(C#) / CommunicationClientFactory(Java), e IExceptionHandler(C#) / ExceptionHandler(Java) construído em torno de um protocolo de comunicação, a ServicePartitionClient(C#) / FabricServicePartitionClient(Java) junta tudo e fornece o ciclo de resolução de endereços de partição de serviço e de gestão de falhas em torno destes componentes.
private MyCommunicationClientFactory myCommunicationClientFactory;
private Uri myServiceUri;
var myServicePartitionClient = new ServicePartitionClient<MyCommunicationClient>(
this.myCommunicationClientFactory,
this.myServiceUri,
myPartitionKey);
var result = await myServicePartitionClient.InvokeWithRetryAsync(async (client) =>
{
// Communicate with the service using the client.
},
CancellationToken.None);
private MyCommunicationClientFactory myCommunicationClientFactory;
private URI myServiceUri;
FabricServicePartitionClient myServicePartitionClient = new FabricServicePartitionClient<MyCommunicationClient>(
this.myCommunicationClientFactory,
this.myServiceUri,
myPartitionKey);
CompletableFuture<?> result = myServicePartitionClient.invokeWithRetryAsync(client -> {
/* Communicate with the service using the client.
*/
});