У меня есть сервер .NET 2.0, который, похоже, сталкивается с проблемами масштабирования, вероятно, из-за плохой разработки кода обработки сокетов, и я ищу руководство о том, как я могу изменить его для повышения производительности.
Сценарий использования: 50 - 150 клиентов, высокая скорость (до 100 с / сек) небольших сообщений (по 10 байтов каждое) каждому клиенту / от каждого клиента. Клиентские соединения долговечны - обычно часы. (Сервер является частью торговой системы. Сообщения клиентов объединяются в группы для отправки на биржу через меньшее количество «исходящих» соединений сокетов, а сообщения подтверждения отправляются обратно клиентам по мере того, как каждая группа обрабатывается биржей. .) ОС - Windows Server 2003, оборудование - 2 х 4-х ядерных X5355.
Текущий дизайн клиентского сокета:TcpListener порождает поток для чтения каждого клиентского сокета при подключении клиентов. Потоки блокируются на Socket.Receive, анализируя входящие сообщения и вставляя их в набор очередей для обработки логикой основного сервера. Сообщения подтверждения отправляются обратно через клиентские сокеты с использованием асинхронных вызовов Socket.BeginSend из потоков, которые общаются со стороной обмена.
Наблюдаемые проблемы: По мере того, как количество клиентов увеличивалось (теперь 60-70), мы начали видеть прерывистые задержки до 100 миллисекунд при отправке и получении данных от клиентов. (Мы регистрируем временные метки для каждого подтверждающего сообщения, и мы можем видеть случайные длинные промежутки в последовательности временных меток для пакетов подтверждений из одной и той же группы, которые обычно уходят в течение нескольких миллисекунд.)
Общая загрузка ЦП системы низкая (<10%), имеется много свободной оперативной памяти, а основная логика и исходящая (обращенная к обмену) сторона работают нормально, поэтому проблема, похоже, изолирована от кода сокета, обращенного к клиенту. . Между сервером и клиентами имеется достаточная пропускная способность сети (гигабитная локальная сеть), и мы исключили проблемы на сетевом или аппаратном уровне.
Будем очень признательны за любые предложения или указатели на полезные ресурсы. Если у кого-то есть какие-либо советы по диагностике или отладке, чтобы точно выяснить, что происходит не так, это тоже было бы здорово.
Примечание. У меня есть статья Winsock: станьте ближе к сети с высокопроизводительными сокетами в .NET в журнале MSDN, и я взглянул на компонент Kodart "XF.Server" - он в лучшем случае выглядит отрывочно.





Во многом это связано с тем, что в вашей системе работает множество потоков, и ядро дает каждому из них отрезок времени. Дизайн простой, но плохо масштабируется.
Вероятно, вам стоит взглянуть на использование Socket.BeginReceive, которое будет выполняться в пулах потоков .net (вы можете каким-то образом указать количество используемых потоков), а затем нажать на очередь из асинхронного обратного вызова (который может выполняться в любом из .NET потоки). Это должно дать вам гораздо более высокую производительность.
У меня нет ответа, но для получения дополнительной информации я бы посоветовал добавить в ваш код таймеры и регистрировать среднее и максимальное время, затраченное на подозрительные операции, такие как добавление в очередь или открытие сокета.
По крайней мере, так у вас будет представление о том, на что смотреть и с чего начать.
Поток на клиента кажется излишним, особенно с учетом низкой общей загрузки ЦП. Обычно вам нужен небольшой пул потоков для обслуживания всех клиентов, используя BeginReceive для ожидания асинхронной работы, а затем просто отправьте обработку одному из рабочих (возможно, просто добавив работу в синхронизированную очередь, в которой все рабочие ждут ).
Определенно полезны Socket.BeginConnect и Socket.BeginAccept. Я считаю, что они используют вызовы ConnectEx и AcceptEx в своей реализации. Эти вызовы заключают первоначальное согласование соединения и передачу данных в один переход между пользователем и ядром. Поскольку начальный буфер отправки / получения уже готов, ядро может просто отправить его - либо на удаленный хост, либо в пользовательское пространство.
У них также есть готовая очередь слушателей / коннекторов, что, вероятно, дает небольшой импульс, избегая задержки, связанной с принятием / получением соединения в пользовательском пространстве и его передачей (и всеми переключениями между пользователем и ядром).
Чтобы использовать BeginConnect с буфером, оказывается, что вам нужно записать исходные данные в сокет перед подключением.
Я ни в коем случае не фанат C#, но для высокопроизводительных серверов сокетов наиболее масштабируемым решением является использование Порты завершения ввода / вывода с количеством активных потоков, подходящих для ЦП, на котором выполняется процесс, вместо использования одного- модель "поток на соединение".
В вашем случае с 8-ядерной машиной вам понадобится 16 общих потоков, из которых 8 работают одновременно. (Остальные 8 в основном находятся в резерве.)
CLR уже использует порты завершения ввода-вывода для сокетов. Таким образом, вы получаете это преимущество по умолчанию в .NET.
WCF также будет использовать порты завершения ввода-вывода для ответа на каждый из ваших вызовов службы. Но стоит отметить, что облегченные порты ввода-вывода специально разработаны для этой задачи.
В среде .NET 3.5 производительность ввода-вывода сокета улучшилась. Вы можете использовать ReceiveAsync / SendAsync вместо BeginReceive / BeginSend для повышения производительности. Проверьте это:
http://msdn.microsoft.com/en-us/library/bb968780.aspx
Спасибо за ссылку. Мы, вероятно, не будем использовать 3.5 какое-то время (по множеству причин), но когда мы все же переключимся, я еще раз взгляну на эти новые методы.
Как предлагали другие, лучший способ реализовать это - сделать код, обращенный к клиенту, полностью асинхронным. Используйте BeginAccept () в TcpServer (), чтобы не создавать поток вручную. Затем используйте BeginRead () / BeginWrite () в базовом сетевом потоке, который вы получаете от принятого TcpClient.
Однако есть одна вещь, которую я здесь не понимаю. Вы сказали, что это долгие связи и большое количество клиентов. Предполагая, что система достигла устойчивого состояния, когда у вас подключено максимальное количество клиентов (скажем, 70). У вас есть 70 потоков, ожидающих клиентских пакетов. Тогда система по-прежнему должна реагировать. Если ваше приложение не имеет утечек памяти / дескрипторов и у вас не хватает ресурсов, поэтому ваш сервер выполняет подкачку. Я бы поставил таймер вокруг вызова Accept (), где вы запускаете клиентский поток и смотрите, сколько времени это займет. Кроме того, я бы запустил диспетчер задач и PerfMon, и отслеживал «невыгружаемый пул», «виртуальную память», «количество обработчиков» для приложения и смотрел, находится ли приложение в состоянии дефицита ресурсов.
Хотя это правда, что переход на асинхронный режим - правильный путь, я не уверен, действительно ли он решит основную проблему. Я бы следил за приложением, как я предлагал, и удостоверился, что нет внутренних проблем, связанных с утечкой памяти и дескрипторов. В этом отношении BigBlackMan был прав - вам нужно больше инструментов, чтобы продолжить. Не знаю, почему ему отказали.
Случайные прерывистые задержки ~ 250 мсек могут быть связаны с алгоритмом Нэгла, используемым TCP. Попробуйте отключить это и посмотрите, что произойдет.
Одна вещь, которую я хотел бы исключить, - это то, что это не что-то такое простое, как запущенный сборщик мусора. Если все ваши сообщения находятся в куче, вы генерируете 10000 объектов в секунду.
Прочтите Сборка мусора каждые 100 секунд
Единственное решение - не хранить сообщения в куче.
У меня была такая же проблема 7 или 8 лет назад и паузы от 100 мс до 1 секунды, проблема заключалась в сборке мусора. Было использовано около 400 мегабайт из 4 гигабайт, НО было много объектов.
В итоге я хранил сообщения на C++, но вы могли использовать кеш ASP.NET (который раньше использовал COM и перемещал их из кучи)
Согласен, хотя я мог бы добавить, что даже если вы «исключили» проблемы с сетью, я бы подумал о замене различных частей (особенно серверного nic) и удостоверился, что у вас установлены все последние версии прошивки и драйверов.