Подтвердить что ты не робот

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

Я не являюсь участником С# на любом участке, но для высокопроизводительных серверов сокетов наиболее масштабируемым решением является использование I/O Completion Ports с рядом активных потоков, подходящих для процессора (ов), на котором выполняется этот процесс, вместо использования модели с одним потоком за соединение.

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

Ответ 5

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

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

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

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

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

Ответ 10

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

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