Пропускная способность WCF ниже ожидаемой

Прошу прощения за длину сообщения, но есть много вещей, которые могут вызвать мою ситуацию, и я попытался включить все изменения настроек, которые я сделал на основе других сообщений. Короче говоря, служба My WCF, похоже, ограничена 3 или 4 одновременными клиентскими запросами за раз. Если я установлю максимальное количество рабочих процессов для пула приложений выше (около 10) или установлю для поведения службы ConcurrencyMode значение Multiple, я получу гораздо лучшую пропускную способность (в несколько раз быстрее). Тем не менее, они кажутся обходными путями к реальной проблеме, принося с собой свои собственные проблемы. Я ошибаюсь, или IIS должен иметь возможность запускать множество экземпляров (десятки или более) моей службы WCF внутри одного рабочего процесса для обработки нагрузки? Я просто знаю, что где-то отсутствует настройка, но я не могу ее найти.

Редактировать. Пока я пробовал предложенные варианты, я понял, что мои математические расчеты не соответствуют пропускной способности. С циклом ForEach я получаю расчетную параллельную обработку на сервере в 20 секунд (на продолжительность задачи * количество задач / общее время выполнения). Это все еще кажется низким для фактической выполняемой работы (сон 10 секунд), но уже не смехотворно низким.

Второе редактирование: я отметил комментарий @Pablo как ответ, потому что его ответ и его ссылка дали мне информацию, позволяющую значительно повысить производительность (я думаю, примерно в 3 раза). Однако я хотел бы задать дополнительный вопрос: каковы разумные ожидания для обработки одновременных запросов в WCF/IIS? Предполагая, что ЦП, память и ввод-вывод не являются узкими местами, каков практический предел/ожидание (на ЦП) для обработки запросов? То, что я ищу, — это эмпирическое правило, которое говорит мне, что я, вероятно, не получу больше больших приростов без добавления процессоров (или рабочих процессов). Спасибо еще раз.

(В Windows 2008 Server, размещенном в IIS, 1 процессор)
Конфигурация службы WCF (сокращенно):

<?xml version="1.0"?>
<configuration>
  <configSections>
  <system.serviceModel>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
    <services>
      <service name="FMHIRWCFSvc.IRService" behaviorConfiguration="IRServiceBehavior">
        <endpoint address="" binding="basicHttpBinding" bindingConfiguration="Binding1" contract="FMHIRWCFSvc.IIRService" />
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="Binding1" maxReceivedMessageSize="104857600">
          <readerQuotas maxArrayLength="104857600"/>
          <security mode="Transport">
            <transport clientCredentialType="None"/>
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="IRServiceBehavior">
          <serviceMetadata httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceThrottling
              maxConcurrentCalls = "500"
              maxConcurrentSessions = "500"
              maxConcurrentInstances = "500"
            />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
  <applicationSettings>
    <FMHIRWCFSvc.Properties.Settings>
      <setting name="FMHIRWCFSvc_ir_dev_websvc_IRWebService40" serializeAs="String">
        <value>http://ir-dev-websvc/imageright.webservice/IRWebService40.asmx</value>
      </setting>
    </FMHIRWCFSvc.Properties.Settings>
  </applicationSettings>
  <system.net>
    <connectionManagement>
      <add address="*" maxconnection="500"/>
    </connectionManagement>
  </system.net>
</configuration>

Конфигурация клиента (сокращенно):

<?xml version="1.0"?>
<configuration>
  <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_IIRService" closeTimeout="00:01:00"
          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
          allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
          maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
          messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
          useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Windows" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="https://{myserveraddress}/FMHIRWCFSvc/IRService.svc"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IIRService"
        contract="wcf_local.IIRService" name="BasicHttpBinding_IIRService" />
    </client>
  </system.serviceModel>
  <system.net>
    <connectionManagement>
      <add address="*" maxconnection="500"/>
    </connectionManagement>
  </system.net>
</configuration>

Вызов клиентского метода:

static void WCFTesting()
{
    ConcurrentQueue<Exception> exceptionList = new ConcurrentQueue<Exception>();
    int[] taskList = new int[250];
    Parallel.ForEach(taskList, theTask => 
    {
        try
        {
            // Create the WCF client
            BasicHttpBinding binding = new BasicHttpBinding {
                Security = { Mode = BasicHttpSecurityMode.Transport },
                SendTimeout = TimeSpan.FromSeconds(20)
            };
            EndpointAddress endpointAddress = new EndpointAddress("https://{myserveraddress}/FMHIRWCFSvc/IRService.svc");
            IRServiceClient wcfClient = new IRServiceClient(binding, endpointAddress);

            // Call wcf service method that sleeps 10 seconds and returns
            wcfClient.TestCall();
        }
        catch (Exception exception) {
            // Store off exceptions for later processing
            exceptionList.Enqueue(exception);
        }
    });
}

Код службы WCF:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
...
public string TestCall()
{
    Thread.Sleep(10000);
    return null;
}

Спасибо за любую информацию или предложения!


person Scott Holmes    schedule 12.07.2012    source источник


Ответы (2)


Используемый вами механизм тестирования может быть не совсем правильным.

Использование Parallel.For() не означает, что будет создано 250 воркеров параллельно. В этом случае кажется, что вы ограничиваете свой тест тем, что было бы оптимальным для конфигурации процессора вашего клиента, и фактически не проверяете, с чем может справиться сервер.

Если вы действительно хотите вызвать 250 параллельных потоков и посмотреть, как они отреагируют, вы можете вручную создать все потоки. Такие как:

        var exceptionList = new ConcurrentQueue<Exception>();
        const int max = 250;
        int numberOfTasks = max;
        var signal = new ManualResetEvent(false);
        for (var i = 0; i < max; i++)
        {
            var thread = new Thread(() =>
            {
                try
                {
                    // Create the WCF client
                    BasicHttpBinding binding = new BasicHttpBinding
                    {
                        Security = { Mode = BasicHttpSecurityMode.Transport },
                        SendTimeout = TimeSpan.FromSeconds(20)
                    };
                    EndpointAddress endpointAddress = new EndpointAddress("https://{myserveraddress}/FMHIRWCFSvc/IRService.svc");
                    IRServiceClient wcfClient = new IRServiceClient(binding, endpointAddress);

                    // Call wcf service method that sleeps 10 seconds and returns
                    wcfClient.TestCall();
                }
                catch (Exception exception)
                {
                    // Store off exceptions for later processing
                    exceptionList.Enqueue(exception);
                }

                if (Interlocked.Decrement(ref numberOfTasks) == 0) signal.Set();
            });
            thread.Start();
        }
        signal.WaitOne();
person Pablo Romeo    schedule 12.07.2012
comment
Спасибо за вашу помощь. Когда я использую ваш код ручного создания потока, я получаю повышение параллелизма (по оценкам, низкие 30 вместо 20), но не скачок, на который я надеялся, например, при разрешении многих рабочих процессов IIS (веб-сад). Я думаю, что если увеличение количества рабочих процессов резко увеличивает производительность, клиент не является узким местом. Если серверное оборудование не облагается налогом (низкое использование процессора, малое использование памяти, низкая активность ввода-вывода), что еще может ограничивать пропускную способность? Действительно ли я максимизирую службу WCF, и мой следующий шаг — больше рабочих процессов? - person Scott Holmes; 13.07.2012
comment
Я думаю, что одна проблема может быть связана с тем, как WCF и IIS обрабатывают ThreadPool. Существует неотъемлемая проблема с длительными запросами (особенно во время всплесков), потому что количество потоков в пуле довольно мало. Здесь есть очень подробный пост анализ поведения потоков WCF, некоторые рекомендации, а также другие рекомендации от Microsoft: support.microsoft. com/kb/2538826, blogs.microsoft.co.il/blogs/idof/archive/2011/05/05/ - person Pablo Romeo; 14.07.2012
comment
Спасибо за ссылки. Я не читал (и не понимал) все ваши ссылки, но мне удалось удвоить производительность с помощью обходной ссылки для создания потока IOCP. Это дает мне в среднем 78 одновременно обрабатываемых запросов с моим тестовым методом. Мой реальный сценарий одновременно составляет около 35. Я думаю, что исчерпал все практические программные решения и должен больше сосредоточиться на решениях для администрирования серверов. - person Scott Holmes; 17.07.2012

Parallel.ForEach и Task.Start заканчиваются примерно в одном и том же коде. Одновременное выполнение задач не гарантируется.

Лучше протестировать с асинхронными вызовами службы WCF ссылка.

person Brett Shearer    schedule 12.07.2012
comment
Спасибо за Ваш ответ. Вы правы, асинхронность позволит клиенту обрабатывать больше, чем Parallel.ForEach. В поисках быстрого способа параллельного процесса я написал несколько тестовых клиентских приложений и обнаружил, что создание потоков вручную дает мне наибольшую пропускную способность, за ними следуют асинхронные вызовы, а затем Parallel.Foreach. Тем не менее, я думаю, что мое текущее узкое место находится на сервере, поскольку добавление рабочих процессов значительно увеличило пропускную способность для моего единственного тестового клиентского приложения, использующего Parallel.ForEach. - person Scott Holmes; 13.07.2012