2008-11-26 2 views
32

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

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

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

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

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

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

Примечание: У меня есть статья MSDN Magazine, статья Winsock: Get Closer to the Wire with High-Performance Sockets in .NET, и я взглянул на компонент Kodart «XF.Server» - он выглядит в лучшем случае отрывочным.

ответ

18

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

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

+0

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

-1

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

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

8

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

3

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

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

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

6

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

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

+6

CLR уже использует порты завершения ввода-вывода для сокетов. Таким образом, вы получаете эту выгоду по умолчанию в .NET. – feroze 2009-11-29 15:53:48

+0

WCF также будет использовать порты ввода-вывода для ответа на каждый ваш вызов службы. Но это хороший момент, чтобы сделать, чтобы облегченные порты ввода-вывода были специально разработаны для этой задачи. – Spence 2010-08-26 11:15:56

22

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

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

4

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

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

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

3

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

1

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

Возьмите чтение из Garbage Collection every 100 seconds

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

0

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

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

Смежные вопросы