Externe service in C# met Reliable Services
Voor services die niet zijn gekoppeld aan een bepaald communicatieprotocol of een bepaalde stack, zoals een web-API, Windows Communication Foundation of andere, biedt het Reliable Services-framework een extern mechanisme om snel en eenvoudig externe procedure-aanroepen voor services in te stellen. In dit artikel wordt beschreven hoe u externe procedureoproepen instelt voor services die zijn geschreven met C#.
Externe toegang instellen voor een service
U kunt externe toegang voor een service in twee eenvoudige stappen instellen:
- Maak een interface voor uw service die u wilt implementeren. Deze interface definieert de methoden die beschikbaar zijn voor een externe procedure-aanroep in uw service. De methoden moeten asynchrone methoden zijn die taken retourneren. De interface moet implementeren
Microsoft.ServiceFabric.Services.Remoting.IService
om aan te geven dat de service een externe interface heeft. - Gebruik een externe listener in uw service. Een externe listener is een
ICommunicationListener
implementatie die externe mogelijkheden biedt. DeMicrosoft.ServiceFabric.Services.Remoting.Runtime
naamruimte bevat de extensiemethodeCreateServiceRemotingInstanceListeners
voor staatloze en stateful services die kunnen worden gebruikt om een externe listener te maken met behulp van het standaard externe transportprotocol.
Notitie
De Remoting
naamruimte is beschikbaar als een afzonderlijk NuGet-pakket met de naam Microsoft.ServiceFabric.Services.Remoting
.
De volgende staatloze service maakt bijvoorbeeld één methode beschikbaar om 'Hallo wereld' te krijgen via een externe procedure-aanroep.
using Microsoft.ServiceFabric.Services.Communication.Runtime;
using Microsoft.ServiceFabric.Services.Remoting;
using Microsoft.ServiceFabric.Services.Remoting.Runtime;
using Microsoft.ServiceFabric.Services.Runtime;
public interface IMyService : IService
{
Task<string> HelloWorldAsync();
}
class MyService : StatelessService, IMyService
{
public MyService(StatelessServiceContext context)
: base (context)
{
}
public Task<string> HelloWorldAsync()
{
return Task.FromResult("Hello!");
}
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return this.CreateServiceRemotingInstanceListeners();
}
}
Notitie
De argumenten en de retourtypen in de service-interface kunnen eenvoudige, complexe of aangepaste typen zijn, maar ze moeten kunnen worden geserialiseerd door de .NET DataContractSerializer.
Externe servicemethoden aanroepen
Notitie
Als u meer dan één partitie gebruikt, moet ServiceProxy.Create() de juiste ServicePartitionKey worden opgegeven. Dit is niet nodig voor een scenario met één partitie.
Het aanroepen van methoden voor een service met behulp van de externe stack wordt uitgevoerd met behulp van een lokale proxy naar de service via de Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxy
klasse. De ServiceProxy
methode maakt een lokale proxy met behulp van dezelfde interface die door de service wordt geïmplementeerd. Met die proxy kunt u methoden op afstand aanroepen op de interface.
IMyService helloWorldClient = ServiceProxy.Create<IMyService>(new Uri("fabric:/MyApplication/MyHelloWorldService"));
string message = await helloWorldClient.HelloWorldAsync();
Het externe framework doorgeeft uitzonderingen die door de service aan de client worden gegenereerd. Als gevolg hiervan ServiceProxy
is de client verantwoordelijk voor het afhandelen van de uitzonderingen die door de service worden gegenereerd.
Levensduur van serviceproxy
Het maken van een serviceproxy is een eenvoudige bewerking, zodat u zoveel kunt maken als u nodig hebt. Serviceproxy-exemplaren kunnen opnieuw worden gebruikt zolang ze nodig zijn. Als een aanroep van een externe procedure een uitzondering genereert, kunt u hetzelfde proxy-exemplaar nog steeds opnieuw gebruiken. Elke serviceproxy bevat een communicatieclient die wordt gebruikt om berichten via de kabel te verzenden. Tijdens het aanroepen van externe aanroepen worden interne controles uitgevoerd om te bepalen of de communicatieclient geldig is. Op basis van de resultaten van deze controles wordt de communicatieclient indien nodig opnieuw gemaakt. Als er een uitzondering optreedt, hoeft u daarom niet opnieuw te maken ServiceProxy
.
Levensduur van serviceproxyfactory
ServiceProxyFactory is een fabriek die proxy-exemplaren maakt voor verschillende externe interfaces. Als u de API ServiceProxyFactory.CreateServiceProxy
gebruikt om een proxy te maken, maakt het framework een singleton-serviceproxy.
Het is handig om er handmatig een te maken wanneer u IServiceRemotingClientFactory-eigenschappen moet overschrijven.
Fabriek maken is een dure bewerking. Een serviceproxyfactory onderhoudt een interne cache van de communicatieclient.
Een best practice is om de serviceproxyfactory zo lang mogelijk in de cache te plaatsen.
Verwerking van externe uitzonderingen
Alle externe uitzonderingen die door de service-API worden gegenereerd, worden teruggestuurd naar de client als AggregateException. Externe uitzonderingen moeten kunnen worden geserialiseerd door DataContract. Als dat niet het is, genereert de proxy-API ServiceException met de serialisatiefout erin.
De serviceproxy verwerkt alle failover-uitzonderingen voor de servicepartitie waarvoor deze is gemaakt. De eindpunten worden opnieuw opgelost als er failover-uitzonderingen (niet-tijdelijke uitzonderingen) zijn en de aanroep opnieuw probeert uit te voeren met het juiste eindpunt. Het aantal nieuwe pogingen voor failover-uitzonderingen is onbepaald. Als er tijdelijke uitzonderingen optreden, probeert de proxy de aanroep opnieuw uit te voeren.
Standaardparameters voor opnieuw proberen worden geleverd door OperationRetrySettings.
Een gebruiker kan deze waarden configureren door het Object OperationRetrySettings door te geven aan de ServiceProxyFactory-constructor.
De externe V2-stack gebruiken
Vanaf versie 2.8 van het nuGet remoting-pakket hebt u de mogelijkheid om de externe V2-stack te gebruiken. De externe V2-stack presteert beter. Het biedt ook functies zoals aangepaste serialisatie en meer pluggable API's. Sjablooncode blijft de externe V1-stack gebruiken. Externe communicatie V2 is niet compatibel met V1 (de vorige externe stack). Volg de instructies in het artikel Upgraden van V1 naar V2 om gevolgen voor de beschikbaarheid van de service te voorkomen.
De volgende benaderingen zijn beschikbaar om de V2-stack in te schakelen.
Een assemblykenmerk gebruiken om de V2-stack te gebruiken
Met deze stappen wijzigt u de sjablooncode om de V2-stack te gebruiken met behulp van een assemblykenmerk.
Wijzig de eindpuntresource van
"ServiceEndpoint"
in"ServiceEndpointV2"
het servicemanifest.<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2" /> </Endpoints> </Resources>
Gebruik de
Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners
extensiemethode om externe listeners te maken (gelijk aan zowel V1 als V2).protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return this.CreateServiceRemotingInstanceListeners(); }
Markeer de assembly die de externe interfaces met een
FabricTransportServiceRemotingProvider
kenmerk bevat.[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2, RemotingClientVersion = RemotingClientVersion.V2)]
Er zijn geen codewijzigingen vereist in het clientproject. Bouw de clientassembly met de interfaceassembly om ervoor te zorgen dat het eerder weergegeven assemblykenmerk wordt gebruikt.
Expliciete V2-klassen gebruiken om de V2-stack te gebruiken
Als alternatief voor het gebruik van een assemblykenmerk kan de V2-stack ook worden ingeschakeld met behulp van expliciete V2-klassen.
Met deze stappen wijzigt u de sjablooncode om de V2-stack te gebruiken met behulp van expliciete V2-klassen.
Wijzig de eindpuntresource van
"ServiceEndpoint"
in"ServiceEndpointV2"
het servicemanifest.<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2" /> </Endpoints> </Resources>
Gebruik FabricTransportServiceRemotingListener uit de
Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime
naamruimte.protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return new[] { new ServiceInstanceListener((c) => { return new FabricTransportServiceRemotingListener(c, this); }) }; }
Gebruik FabricTransportServiceRemotingClientFactory vanuit de
Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client
naamruimte om clients te maken.var proxyFactory = new ServiceProxyFactory((c) => { return new FabricTransportServiceRemotingClientFactory(); });
Upgrade uitvoeren van externe communicatie V1 naar externe communicatie V2
Als u een upgrade wilt uitvoeren van V1 naar V2, zijn upgrades in twee stappen vereist. Volg de stappen in deze volgorde.
Werk de V1-service bij naar de V2-service met behulp van dit kenmerk. Deze wijziging zorgt ervoor dat de service luistert op de V1- en V2-listener.
a. Voeg een eindpuntresource toe met de naam ServiceEndpointV2 in het servicemanifest.
<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2" /> </Endpoints> </Resources>
b. Gebruik de volgende extensiemethode om een externe listener te maken.
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return this.CreateServiceRemotingInstanceListeners(); }
c. Voeg een assemblykenmerk toe voor externe interfaces om de V1- en V2-listener en de V2-client te gebruiken.
[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2|RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2)]
Werk de V1-client bij naar een V2-client met behulp van het V2-clientkenmerk. Deze stap zorgt ervoor dat de client gebruikmaakt van de V2-stack. Er is geen wijziging in het clientproject/de clientservice vereist. Het bouwen van clientprojecten met bijgewerkte interface-assembly is voldoende.
Deze stap is optioneel. Gebruik het kenmerk V2-listener en voer vervolgens een upgrade uit van de V2-service. Deze stap zorgt ervoor dat de service alleen luistert op de V2-listener.
[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2, RemotingClientVersion = RemotingClientVersion.V2)]
De externe V2-stack (interface-compatibel) gebruiken
De externe V2-stack (interfacecompatibel) wordt V2_1 genoemd en is de meest recente versie. Het heeft alle functies van de V2 remoting stack. De interfacestack is compatibel met de externe V1-stack, maar is niet achterwaarts compatibel met V2 en V1. Als u een upgrade wilt uitvoeren van V1 naar V2_1 zonder dat dit van invloed is op de beschikbaarheid van de service, volgt u de stappen in het artikel Upgraden van V1 naar V2 (compatibel met de interface).
Een assemblykenmerk gebruiken om de externe V2-stack (interface-compatibel) te gebruiken
Volg deze stappen om over te stappen op een V2_1 stack.
Voeg een eindpuntresource toe met de naam 'ServiceEndpointV2_1' in het servicemanifest.
<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2_1" /> </Endpoints> </Resources>
Gebruik de externe extensiemethode om een externe listener te maken.
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return this.CreateServiceRemotingInstanceListeners(); }
Voeg een assemblykenmerk toe aan externe interfaces.
[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion= RemotingListenerVersion.V2_1, RemotingClientVersion= RemotingClientVersion.V2_1)]
Er zijn geen wijzigingen vereist in het clientproject. Bouw de clientassembly met de interfaceassembly om ervoor te zorgen dat het vorige assemblykenmerk wordt gebruikt.
Gebruik expliciete externe klassen om een listener/client factory te maken voor de versie V2 (compatibel met interface)
Volg vervolgens deze stappen:
Voeg een eindpuntresource toe met de naam 'ServiceEndpointV2_1' in het servicemanifest.
<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2_1" /> </Endpoints> </Resources>
Gebruik de externe V2-listener. De resourcenaam van het standaardservice-eindpunt die wordt gebruikt, is 'ServiceEndpointV2_1'. Deze moet worden gedefinieerd in het servicemanifest.
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return new[] { new ServiceInstanceListener((c) => { var settings = new FabricTransportRemotingListenerSettings(); settings.UseWrappedMessage = true; return new FabricTransportServiceRemotingListener(c, this,settings); }) }; }
-
var proxyFactory = new ServiceProxyFactory((c) => { var settings = new FabricTransportRemotingSettings(); settings.UseWrappedMessage = true; return new FabricTransportServiceRemotingClientFactory(settings); });
Upgrade van externe V1 naar externe V2 (interface compatibel)
Als u een upgrade wilt uitvoeren van V1 naar V2 (compatibel met de interface, ook wel V2_1 genoemd), zijn upgrades in twee stappen vereist. Volg de stappen in deze volgorde.
Notitie
Wanneer u een upgrade uitvoert van V1 naar V2, moet u ervoor zorgen dat de Remoting
naamruimte wordt bijgewerkt voor gebruik van V2. Voorbeeld: Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client
Werk de V1-service bij naar V2_1 service met behulp van het volgende kenmerk. Deze wijziging zorgt ervoor dat de service luistert op de V1 en de V2_1-listener.
a. Voeg een eindpuntresource toe met de naam 'ServiceEndpointV2_1' in het servicemanifest.
<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2_1" /> </Endpoints> </Resources>
b. Gebruik de volgende extensiemethode om een externe listener te maken.
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return this.CreateServiceRemotingInstanceListeners(); }
c. Voeg een assemblykenmerk toe voor externe interfaces om de V1- V2_1-listener en V2_1-client te gebruiken.
[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1 | RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2_1)]
Werk de V1-client bij naar de V2_1-client met behulp van het V2_1 clientkenmerk. Deze stap zorgt ervoor dat de client de V2_1 stack gebruikt. Er is geen wijziging in het clientproject/de clientservice vereist. Het bouwen van clientprojecten met bijgewerkte interface-assembly is voldoende.
Deze stap is optioneel. Verwijder de versie van de V1-listener uit het kenmerk en voer vervolgens een upgrade uit van de V2-service. Deze stap zorgt ervoor dat de service alleen luistert op de V2-listener.
[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1, RemotingClientVersion = RemotingClientVersion.V2_1)]
Aangepaste serialisatie gebruiken met een bericht met externe communicatie
Voor een bericht met externe communicatie maken we één verpakt object met alle parameters als een veld erin. Volg vervolgens deze stappen:
Implementeer de
IServiceRemotingMessageSerializationProvider
interface om implementatie te bieden voor aangepaste serialisatie. Dit codefragment laat zien hoe de implementatie eruitziet.public class ServiceRemotingJsonSerializationProvider : IServiceRemotingMessageSerializationProvider { public IServiceRemotingMessageBodyFactory CreateMessageBodyFactory() { return new JsonMessageFactory(); } public IServiceRemotingRequestMessageBodySerializer CreateRequestMessageSerializer(Type serviceInterfaceType, IEnumerable<Type> requestWrappedType, IEnumerable<Type> requestBodyTypes = null) { return new ServiceRemotingRequestJsonMessageBodySerializer(); } public IServiceRemotingResponseMessageBodySerializer CreateResponseMessageSerializer(Type serviceInterfaceType, IEnumerable<Type> responseWrappedType, IEnumerable<Type> responseBodyTypes = null) { return new ServiceRemotingResponseJsonMessageBodySerializer(); } }
class JsonMessageFactory : IServiceRemotingMessageBodyFactory { public IServiceRemotingRequestMessageBody CreateRequest(string interfaceName, string methodName, int numberOfParameters, object wrappedRequestObject) { return new JsonBody(wrappedRequestObject); } public IServiceRemotingResponseMessageBody CreateResponse(string interfaceName, string methodName, object wrappedRequestObject) { return new JsonBody(wrappedRequestObject); } }
class ServiceRemotingRequestJsonMessageBodySerializer : IServiceRemotingRequestMessageBodySerializer { private JsonSerializer serializer; public ServiceRemotingRequestJsonMessageBodySerializer() { serializer = JsonSerializer.Create(new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); } public IOutgoingMessageBody Serialize(IServiceRemotingRequestMessageBody serviceRemotingRequestMessageBody) { if (serviceRemotingRequestMessageBody == null) { return null; } using (var writeStream = new MemoryStream()) { using (var jsonWriter = new JsonTextWriter(new StreamWriter(writeStream))) { serializer.Serialize(jsonWriter, serviceRemotingRequestMessageBody); jsonWriter.Flush(); var bytes = writeStream.ToArray(); var segment = new ArraySegment<byte>(bytes); var segments = new List<ArraySegment<byte>> { segment }; return new OutgoingMessageBody(segments); } } } public IServiceRemotingRequestMessageBody Deserialize(IIncomingMessageBody messageBody) { using (var sr = new StreamReader(messageBody.GetReceivedBuffer())) { using (JsonReader reader = new JsonTextReader(sr)) { var ob = serializer.Deserialize<JsonBody>(reader); return ob; } } } }
class ServiceRemotingResponseJsonMessageBodySerializer : IServiceRemotingResponseMessageBodySerializer { private JsonSerializer serializer; public ServiceRemotingResponseJsonMessageBodySerializer() { serializer = JsonSerializer.Create(new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); } public IOutgoingMessageBody Serialize(IServiceRemotingResponseMessageBody responseMessageBody) { if (responseMessageBody == null) { return null; } using (var writeStream = new MemoryStream()) { using (var jsonWriter = new JsonTextWriter(new StreamWriter(writeStream))) { serializer.Serialize(jsonWriter, responseMessageBody); jsonWriter.Flush(); var bytes = writeStream.ToArray(); var segment = new ArraySegment<byte>(bytes); var segments = new List<ArraySegment<byte>> { segment }; return new OutgoingMessageBody(segments); } } } public IServiceRemotingResponseMessageBody Deserialize(IIncomingMessageBody messageBody) { using (var sr = new StreamReader(messageBody.GetReceivedBuffer())) { using (var reader = new JsonTextReader(sr)) { var obj = serializer.Deserialize<JsonBody>(reader); return obj; } } } }
class JsonBody : WrappedMessage, IServiceRemotingRequestMessageBody, IServiceRemotingResponseMessageBody { public JsonBody(object wrapped) { this.Value = wrapped; } public void SetParameter(int position, string parameName, object parameter) { //Not Needed if you are using WrappedMessage throw new NotImplementedException(); } public object GetParameter(int position, string parameName, Type paramType) { //Not Needed if you are using WrappedMessage throw new NotImplementedException(); } public void Set(object response) { //Not Needed if you are using WrappedMessage throw new NotImplementedException(); } public object Get(Type paramType) { //Not Needed if you are using WrappedMessage throw new NotImplementedException(); } }
Overschrijf de standaard serialisatieprovider met
JsonSerializationProvider
voor een externe listener.protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return new[] { new ServiceInstanceListener((c) => { return new FabricTransportServiceRemotingListener(context, _calculatorFactory.GetCalculator(Context), serializationProvider: new ServiceRemotingJsonSerializationProvider()); }) }; }
Overschrijf de standaard serialisatieprovider met
JsonSerializationProvider
voor een externe clientfactory.var proxyFactory = new ServiceProxyFactory((c) => { return new FabricTransportServiceRemotingClientFactory( serializationProvider: new ServiceRemotingJsonSerializationProvider()); });