WCF работает медленно, когда включен надежный сеанс и пакетный асинхронный запрос

Для экспериментов я создал простой сервис WCF "Hello World" и клиент, используя.NET 4.5 на VS2012. Сервер размещен в консольном приложении и использует привязку net.tcp. Я написал клиентский код для отправки пакета асинхронных запросов (всего 700 запросов) на сервер. Все прошло нормально, пока я не включил функцию надежного сеанса сервиса. После включения служба неожиданно стала работать очень медленно, и моей машине потребовалась почти минута для выполнения 700 запросов. Я пытался настроить параметры Concurrency и Throttling (см. Ниже), но это не помогло.

Кто-нибудь знает, почему это происходит? Есть ли способ избежать этого?

Замедление не происходило, если я отключил функцию надежного сеанса или сделал синхронный вызов службы. Поэтому я думаю, что это может относиться к тому, как WCF обрабатывает ожидающие запросы в режиме WS-ReliableMessaging.

РЕДАКТИРОВАТЬ: Также это не произошло, когда я изменил netTcpBinding к wsHttpBinding. Это очень странно, потому что в этом случае wsHttpBinding намного быстрее, чем netTcpBinding.

РЕДАКТИРОВАТЬ: Запуск Perfmon.exe на стороне сервера показывает, что "счетчик потоков" постепенно увеличивается от 8 до более 100 в вышеуказанном случае.

РЕДАКТИРОВАТЬ: Некоторые измерили пропускную способность на моем ПК (локальная сеть). Обратите внимание, что производительность в случае 1 очень вялая и практически бесполезная.

  1. Async + NetTcpBinding / Надежная пропускная способность -> прибл. 14 вызовов / с (70 мс / вызов)
  2. Async + WsHttp / Надежная пропускная способность -> 7957 вызовов / с (0,12 мс / вызов)
  3. Sync + NetTcpBinding / Надежная пропускная способность -> 3986 вызовов / с (0,25 мс / вызов)

Ниже приведены коды и настройки для сервера и клиента, которые я использовал в экспериментах.

Сервер:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.ServiceModel;
using System.ServiceModel.Description;

[ServiceContract]
public interface IHelloService
{
    [OperationContract(IsOneWay=false)]
    string SayHello(string name);
}

[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple, InstanceContextMode=InstanceContextMode.PerSession)]
public class HelloService : IHelloService
{
    public string SayHello(string name) {
        String s = string.Format("Hello {0}", name); 
        return s; 
    }
}

namespace WcfServer
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri baseAddress = new Uri("net.tcp://localhost:8080/hello");
            using (ServiceHost host = new ServiceHost(typeof(HelloService), baseAddress)){
                // Open and listen
                host.Open();
                Console.WriteLine("The service is ready at {0}", baseAddress);
                Console.WriteLine("Press <Enter> to stop the service.");
                Console.ReadLine();
                // Close the ServiceHost.
                host.Close();
            }
        }
    }
}

Клиент:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.ServiceModel;
using WcfClient.WcfServer;

namespace WcfClient
{
    class Program
    {
        static async Task PrintNameAsync(HelloServiceClient client, int cnt) {
            string s = await client.SayHelloAsync(string.Format("-- {0} --", cnt));
            Console.WriteLine(s);
        }

        static void Main(string[] args)
        {
            HelloServiceClient client = new HelloServiceClient("HelloService", "net.tcp://10.20.61.13:8080/hello");
            List<Task> tasks = new List<Task>();
            for(int i=0; i < 700; i++){
                Task t = PrintNameAsync(client, i);
                tasks.Add(t);
            }
            Task.WhenAll(tasks).Wait();
            client.Close();
        }
    }
}

App.config сервера:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <system.serviceModel>
        <bindings>
            <netTcpBinding>
                <binding name="HelloServiceBinding">
                    <reliableSession ordered="true" enabled="true" />
                    <security mode="None" />
                </binding>
            </netTcpBinding>
        </bindings>
        <behaviors>
            <serviceBehaviors>
                <behavior name="HelloServiceBehavior">
                    <serviceMetadata policyVersion="Policy15" />
                    <serviceDebug includeExceptionDetailInFaults="true" />
                    <serviceThrottling maxConcurrentCalls="1000" maxConcurrentSessions="1000"
                        maxConcurrentInstances="1000" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <services>
            <service behaviorConfiguration="HelloServiceBehavior" name="HelloService">
                <endpoint address="net.tcp://localhost:8080/hello" binding="netTcpBinding"
                    bindingConfiguration="HelloServiceBinding" name="HelloService" contract="IHelloService" />
                <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

App.config клиента:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <system.serviceModel>
        <bindings>
            <netTcpBinding>
                <binding name="HelloServiceBinding" sendTimeout="00:01:00">
                    <reliableSession enabled="true" />
                    <security mode="None" />
                </binding>
            </netTcpBinding>
        </bindings>
        <client>
            <endpoint address="net.tcp://localhost:8080/hello" binding="netTcpBinding"
                bindingConfiguration="HelloServiceBinding" contract="WcfServer.IHelloService"
                name="HelloService">
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>

3 ответа

Решение

Нашел частичное решение проблемы по ссылкам ниже:

С помощью обходного пути (используя WorkerThreadPoolBehavior) измеренные пропускные способности следующие:

  1. Async + NetTcpBinding / Надежная пропускная способность -> 474 вызовов / с (2,1 мс / вызов) ... улучшено, но не удовлетворительно
  2. Async + WsHttp/ Надежная пропускная способность -> 7856 вызовов / с (0,13 мс / вызов) ... без изменений
  3. Sync + NetTcpBinding/ Надежная пропускная способность -> 2110 вызовов / с 0,47 мс / вызов) ... ухудшено

Обратите внимание, что случай 1 выше значительно улучшен с 70 мс / вызов. Однако это все еще отстает от случая 2. А для случая 3 введение поведения WorkerThreadPool приводит к снижению производительности с 0,25 мс / вызов до 0,47 мс / вызов.

Посмотри на это...

"Установка MaxTransferWindowSize

Надежные сеансы в Windows Communication Foundation (WCF) используют окно передачи для хранения сообщений на клиенте и службе. Настраиваемое свойство MaxTransferWindowSize указывает, сколько сообщений может содержать окно передачи.

Для отправителя это указывает, сколько сообщений может удерживать окно передачи в ожидании подтверждений; на приемнике указывается, сколько сообщений нужно буферизовать для службы..."

Источник "MSDN: лучшие практики для надежных сеансов": http://msdn.microsoft.com/en-us/library/ms733795.aspx

Вы отправляете сообщения Async, но вы заказали ="true" для ReliableSessionBindingElement. Это не имеет смысла. Установите значение order в false, так как это имеет больше смысла для вашего сценария. ReliableMessaging вызовет снижение производительности, поскольку добавляет к каждому ответному сообщению подтверждение последовательности. Он также добавляет накладные расходы на CreateSequence/CreateSequenceResponse и CloseSequence/CloseSequenceResponse, а затем обменивается сообщениями TerminateSequence/TerminateSequenceResponse в начале и конце сеанса.

Другие вопросы по тегам