Tjänstmoting i C# med Reliable Services
För tjänster som inte är knutna till ett visst kommunikationsprotokoll eller en viss stack, till exempel ett webb-API, Windows Communication Foundation eller andra, tillhandahåller Reliable Services-ramverket en fjärrkommunikationsmekanism för att snabbt och enkelt konfigurera fjärrproceduranrop för tjänster. I den här artikeln beskrivs hur du konfigurerar fjärrproceduranrop för tjänster som skrivits med C#.
Konfigurera fjärrkommunikation på en tjänst
Du kan konfigurera fjärrkommunikation för en tjänst i två enkla steg:
- Skapa ett gränssnitt för din tjänst att implementera. Det här gränssnittet definierar de metoder som är tillgängliga för ett fjärrproceduranrop i tjänsten. Metoderna måste vara aktivitetsreturerande asynkrona metoder. Gränssnittet måste implementeras
Microsoft.ServiceFabric.Services.Remoting.IService
för att signalera att tjänsten har ett fjärrkommunikationsgränssnitt. - Använd en fjärrkommunikationslyssnare i tjänsten. En fjärrkommunikationslyssnare är en
ICommunicationListener
implementering som tillhandahåller fjärrkommunikationsfunktioner. NamnområdetMicrosoft.ServiceFabric.Services.Remoting.Runtime
innehåller tilläggsmetodenCreateServiceRemotingInstanceListeners
för både tillståndslösa och tillståndskänsliga tjänster som kan användas för att skapa en fjärrkommunikationslyssnare med hjälp av standardprotokollet för fjärrkommunikation.
Kommentar
Namnområdet Remoting
är tillgängligt som ett separat NuGet-paket med namnet Microsoft.ServiceFabric.Services.Remoting
.
Följande tillståndslösa tjänst exponerar till exempel en enda metod för att hämta "Hello World" via ett fjärrproceduranrop.
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();
}
}
Kommentar
Argumenten och returtyperna i tjänstgränssnittet kan vara enkla, komplexa eller anpassade typer, men de måste kunna serialiseras av .NET DataContractSerializer.
Anropa fjärrtjänstmetoder
Kommentar
Om du använder mer än en partition måste ServiceProxy.Create() tillhandahållas lämplig ServicePartitionKey. Detta behövs inte för ett scenario med en partition.
Anropa metoder för en tjänst med hjälp av fjärrkommunikationsstacken görs med hjälp av en lokal proxy till tjänsten via Microsoft.ServiceFabric.Services.Remoting.Client.ServiceProxy
klassen. Metoden ServiceProxy
skapar en lokal proxy genom att använda samma gränssnitt som tjänsten implementerar. Med den proxyn kan du anropa metoder i gränssnittet via fjärranslutning.
IMyService helloWorldClient = ServiceProxy.Create<IMyService>(new Uri("fabric:/MyApplication/MyHelloWorldService"));
string message = await helloWorldClient.HelloWorldAsync();
Fjärrkommunikationsramverket sprider undantag som genereras av tjänsten till klienten. ServiceProxy
När används ansvarar klienten därför för att hantera de undantag som genereras av tjänsten.
Livslängd för tjänstproxy
Skapande av tjänstproxy är en enkel åtgärd, så du kan skapa så många du behöver. Tjänstproxyinstanser kan återanvändas så länge de behövs. Om ett fjärrproceduranrop utlöser ett undantag kan du fortfarande återanvända samma proxyinstans. Varje tjänstproxy innehåller en kommunikationsklient som används för att skicka meddelanden via kabeln. När fjärranrop anropas utförs interna kontroller för att avgöra om kommunikationsklienten är giltig. Baserat på resultatet av dessa kontroller återskapas kommunikationsklienten om det behövs. Om ett undantag inträffar behöver du därför inte återskapa ServiceProxy
.
Livslängd för tjänstproxyfabrik
ServiceProxyFactory är en fabrik som skapar proxyinstanser för olika fjärrkommunikationsgränssnitt. Om du använder API ServiceProxyFactory.CreateServiceProxy
:et för att skapa en proxy skapar ramverket en singleton-tjänstproxy.
Det är användbart att skapa en manuellt när du behöver åsidosätta egenskaperna IServiceRemotingClientFactory.
Fabriksskapande är en dyr åtgärd. En tjänstproxyfabrik upprätthåller en intern cache för kommunikationsklienten.
Bästa praxis är att cachelagrade tjänstproxyfabriken så länge som möjligt.
Hantering av fjärrkommunikationsfel
Alla fjärrundantag som genereras av tjänst-API:et skickas tillbaka till klienten som AggregateException. Fjärrfel bör kunna serialiseras av DataContract. Om de inte är det genererar proxy-API:et ServiceException med serialiseringsfelet i det.
Tjänstproxyn hanterar alla redundansundans för den tjänstpartition som den skapas för. Den löser slutpunkterna igen om det finns redundansundantag (icke-tillfälliga undantag) och försöker anropa igen med rätt slutpunkt. Antalet återförsök för redundansundantag är obestämt. Om tillfälliga undantag inträffar försöker proxyn anropa igen.
Standardparametrar för återförsök tillhandahålls av OperationRetrySettings.
En användare kan konfigurera dessa värden genom att skicka objektet OperationRetrySettings till konstruktorn ServiceProxyFactory.
Använda V2-stacken för fjärrkommunikation
Från och med version 2.8 av NuGet-fjärrkommunikationspaketet har du möjlighet att använda V2-stacken för fjärrkommunikation. V2-stacken för fjärrkommunikation presterar bättre. Den innehåller även funktioner som anpassad serialisering och mer anslutningsbara API:er. Mallkoden fortsätter att använda V1-stacken för fjärrkommunikation. Fjärrkommunikation V2 är inte kompatibelt med V1 (den tidigare fjärrkommunikationsstacken). Följ anvisningarna i artikeln Uppgradera från V1 till V2 för att undvika effekter på tjänstens tillgänglighet.
Följande metoder är tillgängliga för att aktivera V2-stacken.
Använd ett sammansättningsattribut för att använda V2-stacken
De här stegen ändrar mallkoden så att den använder V2-stacken med hjälp av ett sammansättningsattribut.
Ändra slutpunktsresursen från
"ServiceEndpoint"
till"ServiceEndpointV2"
i tjänstmanifestet.<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2" /> </Endpoints> </Resources>
Microsoft.ServiceFabric.Services.Remoting.Runtime.CreateServiceRemotingInstanceListeners
Använd tilläggsmetoden för att skapa fjärrkommunikationslyssnare (lika med både V1 och V2).protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return this.CreateServiceRemotingInstanceListeners(); }
Markera sammansättningen som innehåller fjärrkommunikationsgränssnitten med ett
FabricTransportServiceRemotingProvider
attribut.[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2, RemotingClientVersion = RemotingClientVersion.V2)]
Inga kodändringar krävs i klientprojektet. Skapa klientsammansättningen med gränssnittssammansättningen för att se till att det sammansättningsattribut som tidigare visades används.
Använda explicita V2-klasser för att använda V2-stacken
Som ett alternativ till att använda ett sammansättningsattribut kan V2-stacken också aktiveras med hjälp av explicita V2-klasser.
De här stegen ändrar mallkoden så att den använder V2-stacken med hjälp av explicita V2-klasser.
Ändra slutpunktsresursen från
"ServiceEndpoint"
till"ServiceEndpointV2"
i tjänstmanifestet.<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2" /> </Endpoints> </Resources>
Använd FabricTransportServiceRemotingListener från
Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Runtime
namnområdet.protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return new[] { new ServiceInstanceListener((c) => { return new FabricTransportServiceRemotingListener(c, this); }) }; }
Använd FabricTransportServiceRemotingClientFactory från
Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client
namnområdet för att skapa klienter.var proxyFactory = new ServiceProxyFactory((c) => { return new FabricTransportServiceRemotingClientFactory(); });
Uppgradera från fjärrkommunikation V1 till fjärrkommunikation V2
Tvåstegsuppgraderingar krävs för att uppgradera från V1 till V2. Följ stegen i den här sekvensen.
Uppgradera V1-tjänsten till V2-tjänsten med hjälp av det här attributet. Den här ändringen ser till att tjänsten lyssnar på V1- och V2-lyssnaren.
a. Lägg till en slutpunktsresurs med namnet "ServiceEndpointV2" i tjänstmanifestet.
<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2" /> </Endpoints> </Resources>
b. Använd följande tilläggsmetod för att skapa en fjärrkommunikationslyssnare.
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return this.CreateServiceRemotingInstanceListeners(); }
c. Lägg till ett sammansättningsattribut i fjärrkommunikationsgränssnitt för att använda V1- och V2-lyssnaren och V2-klienten.
[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2|RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2)]
Uppgradera V1-klienten till en V2-klient med hjälp av V2-klientattributet. Det här steget ser till att klienten använder V2-stacken. Ingen ändring i klientprojektet/-tjänsten krävs. Det räcker att skapa klientprojekt med uppdaterad gränssnittssammansättning.
Steget är valfritt. Använd V2-lyssnarattributet och uppgradera sedan V2-tjänsten. Det här steget ser till att tjänsten bara lyssnar på V2-lyssnaren.
[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2, RemotingClientVersion = RemotingClientVersion.V2)]
Använd V2-stacken för fjärrkommunikation (gränssnittskompatibel)
V2-stacken (gränssnittskompatibel) kallas V2_1 och är den senaste versionen. Den har alla funktioner i V2-fjärrkommunikationsstacken. Dess gränssnittsstacken är kompatibel med V1-stacken för fjärrkommunikation, men den är inte bakåtkompatibel med V2 och V1. Om du vill uppgradera från V1 till V2_1 utan att påverka tjänstens tillgänglighet följer du stegen i artikeln Uppgradera från V1 till V2 (gränssnittskompatibel).
Använd ett sammansättningsattribut för att använda V2-stacken (gränssnittskompatibel)
Följ de här stegen för att ändra till en V2_1 stack.
Lägg till en slutpunktsresurs med namnet "ServiceEndpointV2_1" i tjänstmanifestet.
<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2_1" /> </Endpoints> </Resources>
Använd fjärrkommunikationstilläggsmetoden för att skapa en fjärrkommunikationslyssnare.
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return this.CreateServiceRemotingInstanceListeners(); }
Lägg till ett sammansättningsattribut i fjärrkommunikationsgränssnitt.
[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion= RemotingListenerVersion.V2_1, RemotingClientVersion= RemotingClientVersion.V2_1)]
Inga ändringar krävs i klientprojektet. Skapa klientsammansättningen med gränssnittssammansättningen för att se till att det tidigare sammansättningsattributet används.
Använd explicita fjärrkommunikationsklasser för att skapa en lyssnare/klientfabrik för V2-versionen (gränssnittskompatibel)
Följ de här stegen:
Lägg till en slutpunktsresurs med namnet "ServiceEndpointV2_1" i tjänstmanifestet.
<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2_1" /> </Endpoints> </Resources>
Använd V2-lyssnaren för fjärrkommunikation. Standardnamnet för tjänstslutpunktens resurs som används är "ServiceEndpointV2_1". Den måste definieras i tjänstmanifestet.
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); });
Uppgradera från fjärrkommunikation V1 till fjärrkommunikation V2 (gränssnittskompatibel)
Om du vill uppgradera från V1 till V2 (gränssnittskompatibel, kallas V2_1) krävs tvåstegsuppgraderingar. Följ stegen i den här sekvensen.
Kommentar
När du uppgraderar från V1 till V2 kontrollerar du att Remoting
namnområdet har uppdaterats för att använda V2. Exempel: Microsoft.ServiceFabric.Services.Remoting.V2.FabricTransport.Client
Uppgradera V1-tjänsten till V2_1-tjänsten med hjälp av följande attribut. Den här ändringen ser till att tjänsten lyssnar på V1 och V2_1 lyssnaren.
a. Lägg till en slutpunktsresurs med namnet "ServiceEndpointV2_1" i tjänstmanifestet.
<Resources> <Endpoints> <Endpoint Name="ServiceEndpointV2_1" /> </Endpoints> </Resources>
b. Använd följande tilläggsmetod för att skapa en fjärrkommunikationslyssnare.
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return this.CreateServiceRemotingInstanceListeners(); }
c. Lägg till ett sammansättningsattribut i fjärrkommunikationsgränssnitt för att använda V1, V2_1 lyssnare och V2_1-klienten.
[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1 | RemotingListenerVersion.V1, RemotingClientVersion = RemotingClientVersion.V2_1)]
Uppgradera V1-klienten till den V2_1 klienten med hjälp av V2_1-klientattributet. Det här steget kontrollerar att klienten använder V2_1 stacken. Ingen ändring i klientprojektet/-tjänsten krävs. Det räcker att skapa klientprojekt med uppdaterad gränssnittssammansättning.
Steget är valfritt. Ta bort V1-lyssnarversionen från attributet och uppgradera sedan V2-tjänsten. Det här steget ser till att tjänsten bara lyssnar på V2-lyssnaren.
[assembly: FabricTransportServiceRemotingProvider(RemotingListenerVersion = RemotingListenerVersion.V2_1, RemotingClientVersion = RemotingClientVersion.V2_1)]
Använda anpassad serialisering med ett omslutet meddelande
För ett omslutet meddelande för fjärrkommunikation skapar vi ett enda omslutet objekt med alla parametrar som ett fält i det. Följ de här stegen:
IServiceRemotingMessageSerializationProvider
Implementera gränssnittet för att tillhandahålla implementering för anpassad serialisering. Det här kodfragmentet visar hur implementeringen ser ut.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(); } }
Åsidosätt standard serialiseringsprovidern med
JsonSerializationProvider
för en fjärrkommunikationslyssnare.protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return new[] { new ServiceInstanceListener((c) => { return new FabricTransportServiceRemotingListener(context, _calculatorFactory.GetCalculator(Context), serializationProvider: new ServiceRemotingJsonSerializationProvider()); }) }; }
Åsidosätt standard serialiseringsprovidern med
JsonSerializationProvider
för en klientfabrik för fjärrkommunikation.var proxyFactory = new ServiceProxyFactory((c) => { return new FabricTransportServiceRemotingClientFactory( serializationProvider: new ServiceRemotingJsonSerializationProvider()); });