De Reliable Services-communicatie-API's gebruiken
Azure Service Fabric als platform is volledig agnostisch over communicatie tussen services. Alle protocollen en stacks zijn acceptabel, van UDP naar 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 klassemethode op basis van een service.
Voor stateless 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. Door meerdere listeners te gebruiken, 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 om externe communicatie te beveiligen 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 aanvraagt voor een service-exemplaar of een partitie.
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 verschilt enigszins van de staatloze tegenhanger, omdat een ServiceReplicaListener
optie heeft om een ICommunicationListener
op secundaire replica's te openen. 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 luisteren op primaire replica's.
U kunt bijvoorbeeld een ServiceRemotingListener hebben die alleen RPC-aanroepen gebruikt op primaire replica's en een tweede, aangepaste listener die leesaanvragen op secundaire replica's via HTTP accepteert:
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
return new[]
{
new ServiceReplicaListener(context =>
new MyCustomHttpListener(context),
"HTTPReadonlyEndpoint",
true),
new ServiceReplicaListener(context =>
this.CreateServiceRemotingListener(context),
"rpcPrimaryEndpoint",
false)
};
}
Notitie
Bij het maken van meerdere listeners voor een service moet elke listener een unieke naam krijgen.
Beschrijf ten slotte de eindpunten die vereist zijn voor de service in het servicemanifest in 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 aan de listener zijn toegewezen vanuit de CodePackageActivationContext
ServiceContext
. De listener kan vervolgens luisteren naar aanvragen 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 gebruikelijk voor het hele servicepakket en worden toegewezen door Service Fabric wanneer het servicepakket wordt geactiveerd. Meerdere servicereplica's die in dezelfde ServiceHost worden gehost, delen mogelijk dezelfde poort. Dit betekent dat de communicatielistener ondersteuning moet bieden voor het delen van poorten. De aanbevolen manier om dit te doen, is dat de communicatielistener de partitie-id en replica-/exemplaar-id gebruikt wanneer het listenadres wordt gegenereerd.
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 elke instantie of replica van de service luistert. Wanneer de methode van een ICommunicationListener(C#) / CommunicationListener(Java)
bewerking is voltooid, wordt de OpenAsync(C#) / openAsync(Java)
retourwaarde geregistreerd in de Naamgevingsservice. Deze retourwaarde die wordt gepubliceerd in naming service is een tekenreeks waarvan de waarde helemaal 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 dit adres vervolgens op servicenaam 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 het voorbeeld van de EchoServer-toepassing 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 oplossen van een eindpuntadres van de partitie of het exemplaar van de service waarmee u wilt communiceren. De ServicePartitionResolver(C#) / FabricServicePartitionResolver(Java)
hulpprogrammaklasse is een basisprimitief 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 voor het maken van verbinding met hetzelfde cluster. Voorbeeld:
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 intern FabricClient
gebruik:
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 in 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 instanties zoveel mogelijk opnieuw te gebruiken FabricClient
.
ServicePartitionResolver resolver = new ServicePartitionResolver(() => CreateMyFabricClient());
FabricServicePartitionResolver resolver = new FabricServicePartitionResolver(() -> new CreateFabricClientImpl());
Vervolgens wordt een oplossingsmethode 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 opgelost 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 service verplaatst of tijdelijk niet beschikbaar is), of een permanente fout (bijvoorbeeld de service is verwijderd of de aangevraagde resource niet meer bestaat). Service-exemplaren of replica's kunnen om meerdere redenen van knooppunt naar knooppunt navigeren. Het serviceadres dat is omgezet via ServicePartitionResolver, kan verlopen zijn wanneer de clientcode verbinding probeert te maken. In dat geval moet de client het adres opnieuw oplossen. Als u de vorige ResolvedServicePartition
opgeeft, wordt aangegeven dat de resolver het opnieuw moet 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 communicatieclientfabrieken 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 patroon voor het opnieuw proberen van foutafhandeling, waardoor het opnieuw proberen van verbindingen met opgeloste service-eindpunten eenvoudiger wordt. De factorybibliotheek biedt het mechanisme voor opnieuw proberen terwijl u de fouthandlers opgeeft.
ICommunicationClientFactory(C#) / CommunicationClientFactory(Java)
definieert de basisinterface die wordt geïmplementeerd door een communicatieclientfactory die clients produceert die met een Service Fabric-service kunnen communiceren. De implementatie van 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 CommunicationClientFactory-interface en voert taken uit die gebruikelijk 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 het 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 clientfactory is voornamelijk verantwoordelijk voor het maken van communicatieclients. Voor clients die geen permanente verbinding onderhouden, zoals een HTTP-client, hoeft de fabriek alleen de client te maken en te retourneren. Andere protocollen die een permanente verbinding onderhouden, zoals sommige binaire protocollen, moeten ook door de fabriek worden gevalideerdValidateClient(string endpoint, MyCommunicationClient client)
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 er een uitzondering optreedt. Uitzonderingen worden gecategoriseerd in opnieuw proberen en niet opnieuw kunnen worden geprobeerd.
- Niet-opnieuw te proberen uitzonderingen worden gewoon teruggeworpen naar de beller.
- Uitzonderingen die opnieuw kunnen worden geprobeerd, worden verder onderverdeeld in tijdelijke en niet-tijdelijke uitzonderingen.
- Tijdelijke uitzonderingen zijn uitzonderingen die eenvoudig opnieuw kunnen worden geprobeerd zonder het adres van het service-eindpunt opnieuw op te lossen. Deze omvatten tijdelijke netwerkproblemen of andere servicefoutreacties dan de antwoorden die aangeven dat het adres van het service-eindpunt niet bestaat.
- Niet-tijdelijke uitzonderingen zijn uitzonderingen waarvoor het adres van het service-eindpunt opnieuw moet worden omgezet. Dit zijn uitzonderingen die aangeven dat het service-eindpunt niet kan worden bereikt, wat aangeeft dat de service is verplaatst naar een ander knooppunt.
De TryHandleException
beslissing wordt genomen over een bepaalde uitzondering. Als het niet weet welke beslissingen u moet nemen over een uitzondering, moet deze onwaar retourneren. Als het 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, wordt ServicePartitionClient(C#) / FabricServicePartitionClient(Java)
het allemaal verpakt en wordt de foutafhandeling en de oplossingslus voor het adresomzetting van de servicepartitie rond deze onderdelen geboden.
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.
*/
});