Советы / методы для высокопроизводительных серверных сокетов C # - PullRequest
32 голосов
/ 26 ноября 2008

У меня есть сервер .NET 2.0, который, похоже, сталкивается с проблемами масштабирования, возможно, из-за плохого дизайна кода обработки сокетов, и я ищу руководство о том, как я мог бы изменить его для повышения производительности.

Сценарий использования: 50 - 150 клиентов, высокая скорость (до 100 с / сек) небольших сообщений (по 10 с байтов каждое) для каждого клиента. Клиентские соединения долгоживущие - обычно часы. (Сервер является частью торговой системы. Клиентские сообщения объединяются в группы для отправки на биржу через меньшее количество «исходящих» сокетных соединений, а сообщения-подтверждения отправляются обратно клиентам, когда каждая группа обрабатывается биржей. .) ОС Windows Server 2003, аппаратная часть - 2 x 4-ядерных X5355.

Текущий дизайн сокета клиента: A TcpListener порождает поток для чтения каждого сокета клиента при подключении клиентов. Потоки блокируются на Socket.Receive, анализируя входящие сообщения и вставляя их в набор очередей для обработки логикой главного сервера. Сообщения подтверждения отправляются обратно через клиентские сокеты с помощью асинхронных вызовов Socket.BeginSend из потоков, которые общаются со стороной обмена.

Наблюдаемые проблемы: Поскольку число клиентов выросло (теперь 60-70), мы начали видеть периодические задержки до 100 секунд миллисекунд при отправке и получении данных от клиентов. (Мы регистрируем временные метки для каждого подтверждающего сообщения, и мы можем видеть случайные длинные промежутки в последовательности временных меток для пакетов подтверждений из одной и той же группы, которые обычно выходят всего за несколько мс.)

Общее использование ЦП системы низкое (<10%), имеется много свободной ОЗУ, а логика ядра и исходящая (обращенная к обмену) сторона работают нормально, поэтому проблема кажется изолированной для клиента. код сокета Существует достаточная пропускная способность сети между сервером и клиентами (гигабитная локальная сеть), и мы исключили проблемы сетевого или аппаратного уровня. </p>

Будем весьма благодарны за любые предложения или ссылки на полезные ресурсы. Если у кого-нибудь есть какие-либо советы по диагностике или отладке, чтобы точно определить, что идет не так, они тоже подойдут.

Примечание: у меня есть статья в журнале MSDN Magazine Winsock: станьте ближе к проводу с высокопроизводительными сокетами в .NET , и я взглянул на компонент Kodart "XF.Server" - в лучшем случае выглядит схематично.

Ответы [ 10 ]

22 голосов
/ 23 декабря 2008

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

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

18 голосов
/ 26 ноября 2008

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

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

8 голосов
/ 26 ноября 2008

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

6 голосов
/ 26 ноября 2008

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

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

4 голосов
/ 29 ноября 2009

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

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

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

3 голосов
/ 18 января 2010

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

3 голосов
/ 26 ноября 2008

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

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

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

1 голос
/ 03 февраля 2011

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

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

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

0 голосов
/ 03 ноября 2012

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

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

0 голосов
/ 26 ноября 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...