WCF de la performance, de la latence et de l'évolutivité
Je suis en train de le port d'un simple async serveur TCP en F# C# 4. Le serveur reçoit une connexion, se lit d'une seule demande et des ruisseaux d'une séquence de réponses avant de fermer la connexion.
Async en C# 4 semble fastidieux et source d'erreur, donc je pensais que je voudrais essayer en utilisant WCF à la place. Ce serveur n'est pas rare de voir de 1 000 demandes simultanées dans la nature donc je pense qu'à la fois le débit et la latence sont d'intérêt.
J'ai écrit un minimum duplex service web WCF et client de la console en C#. Même si je suis en utilisant WCF au lieu de les sockets raw, c'est déjà 175 lignes de code par rapport à 80 lignes de l'original. Mais je suis plus préoccupé par la performance et l'évolutivité:
- Temps de latence est de 154× pire avec WCF.
- Le débit est de 54× pire avec WCF.
- TCP gère de 1 000 connexions simultanées facilement, mais WCF étouffe sur 20.
Tout d'abord, je suis en utilisant les paramètres par défaut pour tout, alors je me demandais si il n'y a rien que je peux modifier pour améliorer ces performances?
Deuxièmement, je me demandais si quelqu'un est à l'aide de la WCF pour ce genre de chose ou si c'est le bon outil pour le travail?
Voici mon WCF serveur en C#:
IService1.cs
[DataContract]
public class Stock
{
[DataMember]
public DateTime FirstDealDate { get; set; }
[DataMember]
public DateTime LastDealDate { get; set; }
[DataMember]
public DateTime StartDate { get; set; }
[DataMember]
public DateTime EndDate { get; set; }
[DataMember]
public decimal Open { get; set; }
[DataMember]
public decimal High { get; set; }
[DataMember]
public decimal Low { get; set; }
[DataMember]
public decimal Close { get; set; }
[DataMember]
public decimal VolumeWeightedPrice { get; set; }
[DataMember]
public decimal TotalQuantity { get; set; }
}
[ServiceContract(CallbackContract = typeof(IPutStock))]
public interface IStock
{
[OperationContract]
void GetStocks();
}
public interface IPutStock
{
[OperationContract]
void PutStock(Stock stock);
}
Service1.svc
<%@ ServiceHost Language="C#" Debug="true" Service="DuplexWcfService2.Stocks" CodeBehind="Service1.svc.cs" %>
Service1.svc.cs
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Stocks : IStock
{
IPutStock callback;
#region IStock Members
public void GetStocks()
{
callback = OperationContext.Current.GetCallbackChannel<IPutStock>();
Stock st = null;
st = new Stock
{
FirstDealDate = System.DateTime.Now,
LastDealDate = System.DateTime.Now,
StartDate = System.DateTime.Now,
EndDate = System.DateTime.Now,
Open = 495,
High = 495,
Low = 495,
Close = 495,
VolumeWeightedPrice = 495,
TotalQuantity = 495
};
for (int i=0; i<1000; ++i)
callback.PutStock(st);
}
#endregion
}
Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<services>
<service name="DuplexWcfService2.Stocks">
<endpoint address="" binding="wsDualHttpBinding" contract="DuplexWcfService2.IStock">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
Voici le C# WCF client:
Program.cs
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
class Callback : DuplexWcfService2.IStockCallback
{
System.Diagnostics.Stopwatch timer;
int n;
public Callback(System.Diagnostics.Stopwatch t)
{
timer = t;
n = 0;
}
public void PutStock(DuplexWcfService2.Stock st)
{
++n;
if (n == 1)
Console.WriteLine("First result in " + this.timer.Elapsed.TotalSeconds + "s");
if (n == 1000)
Console.WriteLine("1,000 results in " + this.timer.Elapsed.TotalSeconds + "s");
}
}
class Program
{
static void Test(int i)
{
var timer = System.Diagnostics.Stopwatch.StartNew();
var ctx = new InstanceContext(new Callback(timer));
var proxy = new DuplexWcfService2.StockClient(ctx);
proxy.GetStocks();
Console.WriteLine(i + " connected");
}
static void Main(string[] args)
{
for (int i=0; i<10; ++i)
{
int j = i;
new System.Threading.Thread(() => Test(j)).Start();
}
}
}
Voici mon async TCP code client et serveur en F#:
type AggregatedDeals =
{
FirstDealTime: System.DateTime
LastDealTime: System.DateTime
StartTime: System.DateTime
EndTime: System.DateTime
Open: decimal
High: decimal
Low: decimal
Close: decimal
VolumeWeightedPrice: decimal
TotalQuantity: decimal
}
let read (stream: System.IO.Stream) = async {
let! header = stream.AsyncRead 4
let length = System.BitConverter.ToInt32(header, 0)
let! body = stream.AsyncRead length
let fmt = System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
use stream = new System.IO.MemoryStream(body)
return fmt.Deserialize(stream)
}
let write (stream: System.IO.Stream) value = async {
let body =
let fmt = System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
use stream = new System.IO.MemoryStream()
fmt.Serialize(stream, value)
stream.ToArray()
let header = System.BitConverter.GetBytes body.Length
do! stream.AsyncWrite header
do! stream.AsyncWrite body
}
let endPoint = System.Net.IPEndPoint(System.Net.IPAddress.Loopback, 4502)
let server() = async {
let listener = System.Net.Sockets.TcpListener(endPoint)
listener.Start()
while true do
let client = listener.AcceptTcpClient()
async {
use stream = client.GetStream()
let! _ = stream.AsyncRead 1
for i in 1..1000 do
let aggregatedDeals =
{
FirstDealTime = System.DateTime.Now
LastDealTime = System.DateTime.Now
StartTime = System.DateTime.Now
EndTime = System.DateTime.Now
Open = 1m
High = 1m
Low = 1m
Close = 1m
VolumeWeightedPrice = 1m
TotalQuantity = 1m
}
do! write stream aggregatedDeals
} |> Async.Start
}
let client() = async {
let timer = System.Diagnostics.Stopwatch.StartNew()
use client = new System.Net.Sockets.TcpClient()
client.Connect endPoint
use stream = client.GetStream()
do! stream.AsyncWrite [|0uy|]
for i in 1..1000 do
let! _ = read stream
if i=1 then lock stdout (fun () ->
printfn "First result in %fs" timer.Elapsed.TotalSeconds)
lock stdout (fun () ->
printfn "1,000 results in %fs" timer.Elapsed.TotalSeconds)
}
do
server() |> Async.Start
seq { for i in 1..100 -> client() }
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
- Le premier truc que j'aimerais essayer est de passer la wcf de liaison de wsdualhttp à nettcp (avec sécurité désactivé) pour quelque chose de plus comparables.
- Voir aussi peut-être devproconnections.com/article/net-framework2/...
- Bonne question. J'ai reçu votre C# service s'exécutant localement, mais je ne peux pas obtenir le F# pour compiler pour une comparaison de performances. Je n'ai jamais lu F# avant... que dois-je ajouter pour faire de rassembler au-delà de couper et de coller le code ci-dessus?
Vous devez vous connecter pour publier un commentaire.
WCF sélectionne de très des valeurs sûres pour presque tous ses paramètres par défaut. Ceci est conforme à la philosophie de ne pas laisser le novice développeur de tirer eux-mêmes. Toutefois, si vous savez les manettes de changement et les fixations à utiliser, vous pouvez obtenir des performances acceptables et de mise à l'échelle.
Sur mon core i5-2400 (quad core, pas de l'hyper threading, 3.10 GHz) la solution ci-dessous sera exécuté 1000 clients avec un 1000 rappels de chacun, pour une moyenne de temps de fonctionnement total de 20 secondes. C'est 1 000 000 de WCF appels en 20 secondes.
Malheureusement, je ne pouvais pas obtenir votre F# programme à exécuter pour une comparaison directe. Si vous exécutez ma solution sur votre boîte, pourriez-vous s'il vous plaît poster quelques F# vs C# WCF comparaison des performances des chiffres?
Avertissement: La ci-dessous est destiné à être une preuve de concept. Certains de ces paramètres ne font pas sens pour la production.
Ce que j'ai fait:
le service des hôtes pour recevoir les rappels. C'est essentiellement ce qu'est un
duplex de la liaison est de le faire sous le capot. (Il est aussi Pratique de l'
suggestion)
maxConcurrentInstances tous à 1000
Noter que dans ce prototype de tous les services et les clients sont dans le même Domaine d'Application et partage le même pool de threads.
Ce que j'ai appris:
De sortie du programme en cours d'exécution sur un core i5-2400.
Remarque les compteurs sont utilisés de la même manière que dans la question d'origine (voir le code).
Code dans une application console fichier:
app.config:
Mise à jour:
J'ai juste essayé la solution ci-dessus avec un netNamedPipeBinding:
Elle a effectivement mis 3 secondes de plus (de 20 à 23 secondes). Depuis cet exemple particulier est tous les inter-processus, je ne sais pas pourquoi. Si quelqu'un a des idées, s'il vous plaît commentaire.
Pour répondre à votre deuxième question, WCF aura toujours une surcharge par rapport à raw sockets. Mais il a une tonne de fonctionnalités (comme la sécurité, la fiabilité, l'interopérabilité, plusieurs protocoles de transport, de traçage, etc.) par rapport à raw sockets, si le compromis est acceptable pour vous, c'est en fonction de votre scénario. On dirait que vous faites des opérations financières de l'application et de la WCF est peut-être pas adapté à votre cas (bien que je ne suis pas dans la finance, l'industrie afin de qualifier cette avec de l'expérience).
Pour votre première question, au lieu de la double liaison http essayer d'hébergement séparé service WCF dans le client de sorte que le client peut être un service en soi, et d'utiliser le netTCP liaison si possible. Ajuster les attributs dans maxconcurrentcalls élément du comportement en service. Les valeurs par défaut ont été plus faibles avant .Net 4.
Je dirais que cela dépend de vos objectifs. Si vous voulez pousser votre matériel autant que possible, alors il est certainement possible d'obtenir+ de 10 000 clients connectés facilement, le secret est de minimiser le temps passé dans le garbage collector et à l'aide de sockets efficacement.
J'ai un peu de posts sur les Sockets en F# ici: http://moiraesoftware.com
Im faire certains travaux en cours avec une bibliothèque appelée Fracture-IO ici: https://github.com/fractureio/fracture
Vous pourriez vouloir vérifier ceux d'idées...