Советы / методы для высокопроизводительных серверных сокетов C#

У меня есть сервер .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" - он в лучшем случае выглядит отрывочно.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
33
0
27 228
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Ответ принят как подходящий

Во многом это связано с тем, что в вашей системе работает множество потоков, и ядро ​​дает каждому из них отрезок времени. Дизайн простой, но плохо масштабируется.

Вероятно, вам стоит взглянуть на использование Socket.BeginReceive, которое будет выполняться в пулах потоков .net (вы можете каким-то образом указать количество используемых потоков), а затем нажать на очередь из асинхронного обратного вызова (который может выполняться в любом из .NET потоки). Это должно дать вам гораздо более высокую производительность.

Согласен, хотя я мог бы добавить, что даже если вы «исключили» проблемы с сетью, я бы подумал о замене различных частей (особенно серверного nic) и удостоверился, что у вас установлены все последние версии прошивки и драйверов.

Jason Hernandez 26.11.2008 08:26

У меня нет ответа, но для получения дополнительной информации я бы посоветовал добавить в ваш код таймеры и регистрировать среднее и максимальное время, затраченное на подозрительные операции, такие как добавление в очередь или открытие сокета.

По крайней мере, так у вас будет представление о том, на что смотреть и с чего начать.

Поток на клиента кажется излишним, особенно с учетом низкой общей загрузки ЦП. Обычно вам нужен небольшой пул потоков для обслуживания всех клиентов, используя BeginReceive для ожидания асинхронной работы, а затем просто отправьте обработку одному из рабочих (возможно, просто добавив работу в синхронизированную очередь, в которой все рабочие ждут ).

Определенно полезны Socket.BeginConnect и Socket.BeginAccept. Я считаю, что они используют вызовы ConnectEx и AcceptEx в своей реализации. Эти вызовы заключают первоначальное согласование соединения и передачу данных в один переход между пользователем и ядром. Поскольку начальный буфер отправки / получения уже готов, ядро ​​может просто отправить его - либо на удаленный хост, либо в пользовательское пространство.

У них также есть готовая очередь слушателей / коннекторов, что, вероятно, дает небольшой импульс, избегая задержки, связанной с принятием / получением соединения в пользовательском пространстве и его передачей (и всеми переключениями между пользователем и ядром).

Чтобы использовать BeginConnect с буфером, оказывается, что вам нужно записать исходные данные в сокет перед подключением.

Я ни в коем случае не фанат C#, но для высокопроизводительных серверов сокетов наиболее масштабируемым решением является использование Порты завершения ввода / вывода с количеством активных потоков, подходящих для ЦП, на котором выполняется процесс, вместо использования одного- модель "поток на соединение".

В вашем случае с 8-ядерной машиной вам понадобится 16 общих потоков, из которых 8 работают одновременно. (Остальные 8 в основном находятся в резерве.)

CLR уже использует порты завершения ввода-вывода для сокетов. Таким образом, вы получаете это преимущество по умолчанию в .NET.

feroze 29.11.2009 18:53

WCF также будет использовать порты завершения ввода-вывода для ответа на каждый из ваших вызовов службы. Но стоит отметить, что облегченные порты ввода-вывода специально разработаны для этой задачи.

Spence 26.08.2010 15:15

В среде .NET 3.5 производительность ввода-вывода сокета улучшилась. Вы можете использовать ReceiveAsync / SendAsync вместо BeginReceive / BeginSend для повышения производительности. Проверьте это:

http://msdn.microsoft.com/en-us/library/bb968780.aspx

Спасибо за ссылку. Мы, вероятно, не будем использовать 3.5 какое-то время (по множеству причин), но когда мы все же переключимся, я еще раз взгляну на эти новые методы.

McKenzieG1 09.01.2009 20:37

Как предлагали другие, лучший способ реализовать это - сделать код, обращенный к клиенту, полностью асинхронным. Используйте BeginAccept () в TcpServer (), чтобы не создавать поток вручную. Затем используйте BeginRead () / BeginWrite () в базовом сетевом потоке, который вы получаете от принятого TcpClient.

Однако есть одна вещь, которую я здесь не понимаю. Вы сказали, что это долгие связи и большое количество клиентов. Предполагая, что система достигла устойчивого состояния, когда у вас подключено максимальное количество клиентов (скажем, 70). У вас есть 70 потоков, ожидающих клиентских пакетов. Тогда система по-прежнему должна реагировать. Если ваше приложение не имеет утечек памяти / дескрипторов и у вас не хватает ресурсов, поэтому ваш сервер выполняет подкачку. Я бы поставил таймер вокруг вызова Accept (), где вы запускаете клиентский поток и смотрите, сколько времени это займет. Кроме того, я бы запустил диспетчер задач и PerfMon, и отслеживал «невыгружаемый пул», «виртуальную память», «количество обработчиков» для приложения и смотрел, находится ли приложение в состоянии дефицита ресурсов.

Хотя это правда, что переход на асинхронный режим - правильный путь, я не уверен, действительно ли он решит основную проблему. Я бы следил за приложением, как я предлагал, и удостоверился, что нет внутренних проблем, связанных с утечкой памяти и дескрипторов. В этом отношении BigBlackMan был прав - вам нужно больше инструментов, чтобы продолжить. Не знаю, почему ему отказали.

Случайные прерывистые задержки ~ 250 мсек могут быть связаны с алгоритмом Нэгла, используемым TCP. Попробуйте отключить это и посмотрите, что произойдет.

Одна вещь, которую я хотел бы исключить, - это то, что это не что-то такое простое, как запущенный сборщик мусора. Если все ваши сообщения находятся в куче, вы генерируете 10000 объектов в секунду.

Прочтите Сборка мусора каждые 100 секунд

Единственное решение - не хранить сообщения в куче.

У меня была такая же проблема 7 или 8 лет назад и паузы от 100 мс до 1 секунды, проблема заключалась в сборке мусора. Было использовано около 400 мегабайт из 4 гигабайт, НО было много объектов.

В итоге я хранил сообщения на C++, но вы могли использовать кеш ASP.NET (который раньше использовал COM и перемещал их из кучи)

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