Share via


De Communicatie-API's van Reliable Services gebruiken

Azure Service Fabric als platform is volledig agnostisch over communicatie tussen services. Alle protocollen en stacks zijn acceptabel, van UDP tot HTTP. Het is aan de serviceontwikkelaar om te kiezen hoe services moeten communiceren. Het Reliable Services-toepassingsframework biedt ingebouwde communicatiestacks en API's die u kunt gebruiken om uw aangepaste communicatieonderdelen te bouwen.

Servicecommunicatie instellen

De Reliable Services-API maakt gebruik van een eenvoudige interface voor servicecommunicatie. Als u een eindpunt voor uw service wilt openen, implementeert u deze 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();
}

Vervolgens kunt u de implementatie van uw communicatielistener toevoegen door deze te retourneren in een op service gebaseerde klassemethodeoverschrijving.

Voor staatloze services:

public class MyStatelessService : StatelessService
{
    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        ...
    }
    ...
}
public class MyStatelessService extends StatelessService {

    @Override
    protected List<ServiceInstanceListener> createServiceInstanceListeners() {
        ...
    }
    ...
}

Voor stateful services:

    @Override
    protected List<ServiceReplicaListener> createServiceReplicaListeners() {
        ...
    }
    ...
public class MyStatefulService : StatefulService
{
    protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
    {
        ...
    }
    ...
}

In beide gevallen retourneert u een verzameling listeners. Als u meerdere listeners gebruikt, kan uw service luisteren op meerdere eindpunten, mogelijk met behulp van verschillende protocollen. U hebt bijvoorbeeld een HTTP-listener en een afzonderlijke WebSocket-listener. U kunt migreren van onbeveiligd naar beveiligd extern verkeer door eerst beide scenario's in te schakelen door zowel een niet-beveiligde listener als een beveiligde listener te hebben. Elke listener krijgt een naam en de resulterende verzameling naam: adresparen worden weergegeven als een JSON-object wanneer een client de luisteradressen voor een service-exemplaar of partitie aanvraagt.

In een staatloze service retourneert de onderdrukking een verzameling ServiceInstanceListeners. Een ServiceInstanceListener bevat een functie om een ICommunicationListener(C#) / CommunicationListener(Java) te maken en geeft deze een naam. Voor stateful services retourneert de onderdrukking een verzameling ServiceReplicaListeners. Dit is iets anders dan de staatloze tegenhanger, omdat een een ServiceReplicaListener optie heeft om een ICommunicationListener te openen op secundaire replica's. U kunt niet alleen meerdere communicatielisteners in een service gebruiken, maar u kunt ook opgeven welke listeners aanvragen accepteren op secundaire replica's en welke alleen op primaire replica's luisteren.

U kunt bijvoorbeeld een ServiceRemotingListener hebben die alleen RPC-aanroepen accepteert op primaire replica's en een tweede, aangepaste listener die leesaanvragen op secundaire replica's via HTTP verwerkt:

protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
    return new[]
    {
        new ServiceReplicaListener(context =>
            new MyCustomHttpListener(context),
            "HTTPReadonlyEndpoint",
            true),

        new ServiceReplicaListener(context =>
            this.CreateServiceRemotingListener(context),
            "rpcPrimaryEndpoint",
            false)
    };
}

Notitie

Wanneer u meerdere listeners voor een service maakt, moet elke listener een unieke naam krijgen.

Beschrijf ten slotte de eindpunten die vereist zijn voor de service in het servicemanifest onder de sectie over eindpunten.

<Resources>
    <Endpoints>
      <Endpoint Name="WebServiceEndpoint" Protocol="http" Port="80" />
      <Endpoint Name="OtherServiceEndpoint" Protocol="tcp" Port="8505" />
    <Endpoints>
</Resources>

De communicatielistener heeft toegang tot de eindpuntresources die eraan zijn toegewezen vanuit de CodePackageActivationContext in de ServiceContext. De listener kan vervolgens naar aanvragen luisteren wanneer deze wordt geopend.

var codePackageActivationContext = serviceContext.CodePackageActivationContext;
var port = codePackageActivationContext.GetEndpoint("ServiceEndpoint").Port;

CodePackageActivationContext codePackageActivationContext = serviceContext.getCodePackageActivationContext();
int port = codePackageActivationContext.getEndpoint("ServiceEndpoint").getPort();

Notitie

Eindpuntresources zijn gemeenschappelijk voor het hele servicepakket en worden door Service Fabric toegewezen wanneer het servicepakket wordt geactiveerd. Meerdere servicereplica's die in dezelfde ServiceHost worden gehost, kunnen dezelfde poort delen. Dit betekent dat de communicatielistener poort delen moet ondersteunen. De aanbevolen manier om dit te doen is dat de communicatielistener de partitie-id en replica-/exemplaar-id gebruikt bij het genereren van het listen-adres.

Registratie van serviceadressen

Een systeemservice met de naam Naming Service wordt uitgevoerd op Service Fabric-clusters. De naamgevingsservice is een registrar voor services en hun adressen waarop elk exemplaar of replica van de service luistert. Wanneer de OpenAsync(C#) / openAsync(Java) methode van een ICommunicationListener(C#) / CommunicationListener(Java) is voltooid, wordt de retourwaarde geregistreerd in de naamgevingsservice. Deze retourwaarde die wordt gepubliceerd in de naamgevingsservice is een tekenreeks waarvan de waarde van alles kan zijn. Deze tekenreekswaarde is wat clients zien wanneer ze vragen om een adres voor de service van de naamgevingsservice.

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

Service Fabric biedt API's waarmee clients en andere services vervolgens op servicenaam om dit adres kunnen vragen. Dit is belangrijk omdat het serviceadres niet statisch is. Services worden verplaatst in het cluster voor resourceverdeling en beschikbaarheidsdoeleinden. Dit is het mechanisme waarmee clients het luisteradres voor een service kunnen oplossen.

Notitie

Zie Service Fabric Web API-services met OWIN-selfhosting voor C# voor een volledig overzicht van het schrijven van een communicatielistener, terwijl u voor Java uw eigen HTTP-serverimplementatie kunt schrijven, zie voorbeeld van echoservertoepassing op https://github.com/Azure-Samples/service-fabric-java-getting-started.

Communiceren met een service

De Reliable Services-API biedt de volgende bibliotheken voor het schrijven van clients die communiceren met services.

Oplossing van service-eindpunt

De eerste stap voor communicatie met een service is het omzetten van een eindpuntadres van de partitie of instantie van de service waarmee u wilt communiceren. De ServicePartitionResolver(C#) / FabricServicePartitionResolver(Java) hulpprogrammaklasse is een eenvoudige primitieve waarmee clients het eindpunt van een service tijdens runtime kunnen bepalen. In Service Fabric-terminologie wordt het proces voor het bepalen van het eindpunt van een service aangeduid als de oplossing van het service-eindpunt.

Als u verbinding wilt maken met services binnen een cluster, kan ServicePartitionResolver worden gemaakt met behulp van standaardinstellingen. Dit is het aanbevolen gebruik voor de meeste situaties:

ServicePartitionResolver resolver = ServicePartitionResolver.GetDefault();
FabricServicePartitionResolver resolver = FabricServicePartitionResolver.getDefault();

Als u verbinding wilt maken met services in een ander cluster, kan een ServicePartitionResolver worden gemaakt met een set clustergateway-eindpunten. Gateway-eindpunten zijn slechts verschillende eindpunten om verbinding te maken met hetzelfde cluster. Bijvoorbeeld:

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");

U kunt ook ServicePartitionResolver een functie krijgen voor het maken van een FabricClient om intern te gebruiken:

public delegate FabricClient CreateFabricClientDelegate();
public FabricServicePartitionResolver(CreateFabricClient createFabricClient) {
...
}

public interface CreateFabricClient {
    public FabricClient getFabricClient();
}

FabricClient is het object dat wordt gebruikt om te communiceren met het Service Fabric-cluster voor verschillende beheerbewerkingen op het cluster. Dit is handig als u meer controle wilt over hoe een servicepartitie-resolver communiceert met uw cluster. FabricClient voert intern caching uit en is over het algemeen duur om te maken, dus het is belangrijk om exemplaren zoveel mogelijk opnieuw te gebruiken FabricClient .

ServicePartitionResolver resolver = new  ServicePartitionResolver(() => CreateMyFabricClient());
FabricServicePartitionResolver resolver = new  FabricServicePartitionResolver(() -> new CreateFabricClientImpl());

Vervolgens wordt een methode voor oplossen gebruikt om het adres van een service of een servicepartitie voor gepartitioneerde services op te halen.

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());

Een serviceadres kan eenvoudig worden omgezet met behulp van een ServicePartitionResolver, maar er is meer werk nodig om ervoor te zorgen dat het opgeloste adres correct kan worden gebruikt. Uw client moet detecteren of de verbindingspoging is mislukt vanwege een tijdelijke fout en opnieuw kan worden geprobeerd (bijvoorbeeld de service is verplaatst of tijdelijk niet beschikbaar is), of een permanente fout (bijvoorbeeld dat de service is verwijderd of de aangevraagde resource niet meer bestaat). Service-exemplaren of replica's kunnen op elk gewenst moment van knooppunt naar knooppunt worden verplaatst om verschillende redenen. Het serviceadres dat via ServicePartitionResolver is omgezet, is mogelijk verouderd op het moment dat uw clientcode verbinding probeert te maken. In dat geval moet de client het adres opnieuw oplossen. Als u de vorige ResolvedServicePartition opgeeft, moet de resolver het opnieuw proberen in plaats van gewoon een adres in de cache op te halen.

Normaal gesproken hoeft de clientcode niet rechtstreeks met de ServicePartitionResolver te werken. Het wordt gemaakt en doorgegeven aan communicatieclientfactory's in de Reliable Services-API. De factory's gebruiken de resolver intern om een clientobject te genereren dat kan worden gebruikt om te communiceren met services.

Communicatieclients en factory's

De communication factory-bibliotheek implementeert een typisch foutafhandelingspatroon voor opnieuw proberen dat het opnieuw proberen van verbindingen met opgeloste service-eindpunten eenvoudiger maakt. De factorybibliotheek biedt het mechanisme voor opnieuw proberen terwijl u de fouthandlers opgeeft.

ICommunicationClientFactory(C#) / CommunicationClientFactory(Java) definieert de basisinterface die is geïmplementeerd door een communicatieclientfactory die clients produceert die kunnen communiceren met een Service Fabric-service. De implementatie van de CommunicationClientFactory is afhankelijk van de communicatiestack die wordt gebruikt door de Service Fabric-service waar de client wil communiceren. De Reliable Services-API biedt een CommunicationClientFactoryBase<TCommunicationClient>. Dit biedt een basis implementatie van de Interface CommunicationClientFactory en voert taken uit die gemeenschappelijk zijn voor alle communicatiestacks. (Deze taken omvatten het gebruik van een ServicePartitionResolver om het service-eindpunt te bepalen). Clients implementeren meestal de abstracte communicationClientFactoryBase-klasse om logica te verwerken die specifiek is voor de communicatiestack.

De communicatieclient ontvangt alleen een adres en gebruikt dit om verbinding te maken met een service. De client kan het gewenste protocol gebruiken.

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
     */
}

De client factory is primair verantwoordelijk voor het maken van communicatieclients. Voor clients die geen permanente verbinding onderhouden, zoals een HTTP-client, hoeft de factory alleen de client te maken en te retourneren. Andere protocollen die een permanente verbinding onderhouden, zoals sommige binaire protocollen, moeten ook worden gevalideerd (ValidateClient(string endpoint, MyCommunicationClient client)) door de factory om te bepalen of de verbinding opnieuw moet worden gemaakt.

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) {
    }
}

Ten slotte is een uitzonderingshandler verantwoordelijk voor het bepalen welke actie moet worden ondernomen wanneer een uitzondering optreedt. Uitzonderingen worden gecategoriseerd in opnieuw proberen en niet opnieuw proberen.

  • Uitzonderingen die niet opnieuw kunnen worden geprobeerd, worden gewoon teruggeworpen naar de aanroeper.
  • Uitzonderingen die opnieuw kunnen worden geprobeerd, worden verder onderverdeeld in tijdelijk en niet-tijdelijk.
    • Tijdelijke uitzonderingen zijn de uitzonderingen die eenvoudig opnieuw kunnen worden geprobeerd zonder het adres van het service-eindpunt opnieuw om te lossen. Deze omvatten tijdelijke netwerkproblemen of andere reacties op servicefouten dan de antwoorden die aangeven dat het adres van het service-eindpunt niet bestaat.
    • Niet-tijdelijke uitzonderingen zijn de uitzonderingen waarvoor het adres van het service-eindpunt opnieuw moet worden omgezet. Deze omvatten uitzonderingen die aangeven dat het service-eindpunt niet kan worden bereikt, wat aangeeft dat de service naar een ander knooppunt is verplaatst.

De TryHandleException neemt een beslissing over een bepaalde uitzondering. Als het niet weet welke beslissingen moeten worden genomen over een uitzondering, moet deze onwaar retourneren. Als het wel weet welke beslissing moet worden genomen, moet het resultaat dienovereenkomstig worden ingesteld en waar worden geretourneerd.

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;

    }
}

Alles samenvoegen

Met een ICommunicationClient(C#) / CommunicationClient(Java), ICommunicationClientFactory(C#) / CommunicationClientFactory(Java)en IExceptionHandler(C#) / ExceptionHandler(Java) gebouwd rond een communicatieprotocol, verpakt een ServicePartitionClient(C#) / FabricServicePartitionClient(Java) alles bij elkaar en biedt de foutafhandeling en de oplossingslus voor servicepartitieadressen rond deze onderdelen.

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.
       */
   });

Volgende stappen