Загрузка файлов через WCF медленнее, чем через IIS
Следующий метод (в котором я, надеюсь, не допустил ошибок при значительном упрощении этого поста) работает правильно и настроен с использованием режима передачи в потоковом режиме по протоколу net.tcp. Проблема в том, что производительность значительно ниже, чем при загрузке одного и того же файла через IIS через http. Почему это так, и что я могу изменить, чтобы улучшить производительность?
Stream WebSiteStreamedServiceContract.DownloadFile( string filePath ) {
return File.OpenRead( filePath );
}
Наконец, принимает ли WCF ответственность за правильное удаление моего потока, и делает ли это хорошую работу в этом? Если нет, что я должен делать вместо этого?
Спасибо.
5 ответов
После 8 месяцев работы над этой проблемой, 3 из них с Microsoft, вот решение. Краткий ответ: на стороне сервера (сторона, отправляющая большой файл) необходимо использовать следующее для привязки:
<customBinding>
<binding name="custom_tcp">
<binaryMessageEncoding />
<tcpTransport connectionBufferSize="256192" maxOutputDelay="00:00:30" transferMode="Streamed">
</tcpTransport>
</binding>
</customBinding>
Ключ здесь является атрибутом connectionBufferSize. Возможно, потребуется установить несколько других атрибутов (maxReceivedMessageSize и т. Д.), Но виновником было connectionBufferSize.
Никакой код не должен был быть изменен на стороне сервера.
Никакой код не должен быть изменен на стороне клиента.
Никакая конфигурация не должна была быть изменена на стороне клиента.
Вот длинный ответ:
Я все время подозревал, что причина net.tcp по WCF была медленной, потому что он очень часто отправлял небольшие порции информации, а не реже большие порции информации, и это заставляло его работать плохо в сетях с высокой задержкой (Интернет). Это оказалось правдой, но это был долгий путь, чтобы добраться туда.
В netTcpBinding есть несколько атрибутов, которые звучат многообещающе: maxBufferSize является наиболее очевидным, а maxBytesPerRead и другие тоже звучат обнадеживающе. В дополнение к этому, можно создавать более сложные потоки, чем в исходном вопросе - вы также можете указать размер буфера там - как на стороне клиента, так и на стороне сервера. Проблема в том, что ничего из этого не имеет никакого влияния. Как только вы используете netTcpBinding, вы попадаете.
Причина этого заключается в том, что настройка maxBufferSize на netTcpBinding корректирует буфер на уровне протокола. Но ничего, что вы можете сделать с netTcpBinding, никогда не отрегулирует базовый транспортный уровень. Вот почему мы так долго не могли добиться прогресса.
Настраиваемая привязка решает проблему, потому что увеличение connectionBufferSize на транспортном уровне увеличивает объем передаваемой информации за один раз, и, следовательно, передача гораздо менее подвержена задержке.
Решая эту проблему, я заметил, что maxBufferSize и maxBytesPerRead оказали влияние на производительность в сетях с низкой задержкой (и локально). Microsoft сообщает мне, что maxBufferSize и connectionBufferSize являются независимыми и что все комбинации их значений (равные друг другу, maxBufferSize больше, чем connectionBufferSize, maxBufferSize меньше, чем connectionBufferSize), являются действительными. Мы добились успеха с maxBufferSize и maxBytesPerRead в 65536 байтов. Опять же, однако, это оказало очень небольшое влияние на производительность сети с высокой задержкой (исходная проблема).
If you are wondering what maxOutputDelay is for, it is the amount of time that is allotted to fill the connection buffer before the framework throws an IO exception. Because we increased the buffer size, we also increased the amount of time allotted to fill the buffer.
With this solution, our performance increased about 400% and is now slightly better than IIS. There are several other factors that affect the relative and absolute performance over IIS over HTTP and WCF over net.tcp (and WCF over http, for that matter), but this was our experience.
Я не знаю ответа на ваш первый вопрос (я думаю, что вам нужно предоставить больше кода, чтобы показать, что вы делаете для обоих ваших тестов), но на ваш второй вопрос, касающийся утилизации потока, ответ заключается в том, что вам нужно сделай это сам.
Вот отличная запись в блоге, в которой есть отличный код для этой цели.
Я принял @Greg-Smalter совет и изменения ConnectionBufferSize
на NetTcpBinding
используя отражение, и это решило мою проблему. Потоковая передача больших документов сейчас очень быстро. Вот код
var transport = binding.GetType().GetField("transport", BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(binding);
transport?.GetType().GetProperty("ConnectionBufferSize", BindingFlags.Public | BindingFlags.Instance)?.SetValue(transport, 256192);
Вот еще один способ сделать это, не имея дело с отражением. Просто заверните NetTcpBinding
в CustomBinding
,
var binding = new CustomBinding(new NetTcpBinding
{
MaxReceivedMessageSize = 2147483647,
MaxBufferSize = 2147483647,
MaxBufferPoolSize = 2147483647,
ReceiveTimeout = new TimeSpan(4, 1, 0),
OpenTimeout = new TimeSpan(4, 1, 0),
SendTimeout = new TimeSpan(4, 1, 0),
CloseTimeout = new TimeSpan(4, 1, 0),
ReaderQuotas = XmlDictionaryReaderQuotas.Max,
Security =
{
Mode = SecurityMode.None,
Transport = {ClientCredentialType = TcpClientCredentialType.None}
},
TransferMode = TransferMode.Streamed,
HostNameComparisonMode = HostNameComparisonMode.StrongWildcard
});
binding.Elements.Find<TcpTransportBindingElement>().ConnectionBufferSize = 665600;
Ответ, основанный на размышлениях, действительно работает для нас. Если вам нужно сделать это через размещенный сервис IIS/WCF; Вы можете использовать волшебное объявление функции Configure, чтобы получить доступ к Binding, чтобы сделать это:
public static void Configure(ServiceConfiguration config)
{
NetTcpBinding tcpBinding = new NetTcpBinding { /* Configure here */ };
var transport = tcpBinding.GetType().GetField("transport", BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(tcpBinding);
transport?.GetType().GetProperty("ConnectionBufferSize", BindingFlags.Public | BindingFlags.Instance)?.SetValue(transport, 256192);
ServiceEndpoint se = new ServiceEndpoint(ContractDescription.GetContract(typeof(IService)), tcpBinding , new EndpointAddress("net.tcp://uri.foo/bar.svc"))
{
ListenUri = new Uri("net.tcp://uri.foo/bar.svc")
};
config.AddServiceEndpoint(se);
config.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
config.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true });
}
Спасибо за информацию в этом посте. Там не так много информации об этой проблеме. Я хочу добавить некоторые дополнительные детали, которые могут быть полезны для других.
Кажется, что большинство ответов здесь указывают на то, что люди используют одну конечную точку NetTcp или они не размещают WCF внутри IIS.
Если вы используете несколько конечных точек netTcp в одной и той же службе wcf, размещенной в IIS, или контейнер, использующий WAS, вы можете столкнуться с этими проблемами.
- Если вы измените ConnectionBufferSize для одной конечной точки NetTcp, все они должны быть изменены, и они должны иметь одинаковое значение. Очевидно, это требование при размещении в WAS (именно это использует IIS). ( https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-host-a-wcf-service-in-was) Очевидно, это не проблема, если вы сами -хостинг веб-службы, но я не проверил это. Поэтому, если вы добавили настраиваемую привязку для одного NetTcp, но получаете исключение активации из-за отсутствия TransportManager, это ваша проблема.
- Я не мог заставить это работать, используя магический метод Configure. Даже с базовой связкой NetTcp без особых изменений я получал нулевой объект ответа. Я уверен, что есть способ это исправить, но у меня не было времени копаться в этом.
- Способ, который работал для меня, состоял в том, чтобы использовать customBinding. Если вы используете безопасность транспорта с аутентификацией Windows, вы можете просто добавить
<windowsStreamSecurity />
Таким образом, привязка выглядит примерно так:
<binding name="CustomTcpBinding_ServerModelStreamed">
<windowsStreamSecurity />
<binaryMessageEncoding />
<tcpTransport connectionBufferSize="5242880" maxReceivedMessageSize="2147483647" maxBufferSize ="2147483647" transferMode="Streamed" />
</binding>
Вам не нужно менять настройки клиента NetTcp, конечно, если вы не используете какие-либо дополнительные функции, которые здесь не отражены. Для customBinding важен порядок разделов. https://docs.microsoft.com/en-us/dotnet/framework/wcf/extending/custom-bindings