Сети

123

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

Сетевые протоколы

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

Конвейерный режим

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

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

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

Примером протокола с поддержкой конвейерной обработки может служить протокол HTTP 1.1, но она часто отключена по умолчанию на большинстве серверов и в веб-браузерах из-за проблем совместимости. Протокол Google SPDY (экспериментальный HTTP-подобный протокол, поддерживаемый веб-браузерами Chrome и Firefox), а также некоторые HTTP-серверы и готовящийся к выходу протокол HTTP 2.0, включают конвейерную обработку по умолчанию.

Потоковый режим

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

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

Объединение сообщений

Отправка данных маленькими фрагментами является напрасной тратой пропускной способности сети. Заголовки протоколов Ethernet, IP и TCP/UDP имеют довольно большие размеры, из-за чего объем полезных данных в пакете оказывается невелик, поэтому, даже при максимальном использовании пропускной способности сети, большая ее часть может отводиться на передачу заголовков, а не полезной информации.

Кроме того, сама ОС Windows вносит свои накладные расходы, не зависящие или почти не зависящие от размера фрагмента данных. Протокол может смягчать этот недостаток, поддерживая возможность объединения нескольких запросов. Например, протокол службы доменных имен (Domain Name Service, DNS) дает возможность клиенту разрешать несколько доменных имен в одном запросе.

Многословные протоколы

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

Представьте сеанс использования «многословного» протокола (chatty protocol). Когда вы пытаетесь открыть веб-страницу, браузер подключается к веб-серверу по протоколу TCP, отправляет запрос HTTP GET и принимает HTML-страницу. Затем браузер анализирует полученную страницу, выявляет в ней ссылки на сценарии JavaScript, таблицы стилей CSS и изображения, и загружает их отдельно. Далее он выполняет сценарии JavaScript, которые могут инициировать загрузку дополнительных данных. В общем случае клиент не может заранее знать, какое содержимое еще потребуется для отображения страницы.

Чтобы ослабить отрицательное влияние этой проблемы, сервер может подсказывать клиенту адреса URL, откуда потребуется загрузить дополнительную информацию, необходимую для отображения страницы, и даже отправить эту информацию, не дожидаясь, пока клиент запросит ее.

Кодирование и избыточность сообщений

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

Сетевые сокеты

Прикладной интерфейс сокетов является стандартным инструментом для работы с сетевыми протоколами, такими как TCP и UDP. Первоначально интерфейс сокетов был реализован в операционной системе BSD UNIX и со временем превратился в стандарт практически для всех операционных систем, иногда дополненный патентованными расширениями, такими как Microsoft WinSock.

В Windows поддерживается несколько способов организации ввода/вывода через сокеты: с блокировкой, без блокировки с опросом и асинхронно. Выбор наилучшей модели ввода/вывода и параметров сокетов позволяют добиться максимальной пропускной способности, минимальных задержек и наилучшей масштабируемости. В этом разделе рассказывается о некоторых способах оптимизации операций с использованием механизма Windows Sockets.

Асинхронные сокеты

Асинхронный ввод/вывод в .NET поддерживается посредством класса Socket. Всего существует две группы асинхронных методов: Begin... и ...Async, где под ... подразумеваются Accept, Connect, Receive, Send и другие операции. Первая группа использует механизм .NET ThreadPool для организации ожидания завершения асинхронных операций ввода/вывода, а вторая - механизм порта завершения ввода/вывода, более производительный и масштабируемый. Вторая группа методов впервые была реализована в версии .NET Framework 2.0 SP1.

Буферы сокетов

Объекты сокетов имеют свойства ReceiveBufferSize и SendBufferSize, позволяющие определять размеры буферов, выделяемых стеком протоколов TCP/IP (в пространстве памяти операционной системы). По умолчанию оба получают значение 8192 байт. Приемный буфер используется для хранения принятых данных, пока не прочитанных приложением. Выходной буфер используется для хранения данных, отправленных приложением, но получение которых еще не было подтверждено принимающей стороной. Если возникнет необходимость повторно отправить данные, они будут отправлены из выходного буфера.

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

Когда приложение выводит данные в сокет, они записываются в выходной буфер, при этом приложение не блокируется, пока выходной буфер не заполнится или пока не заполнится приемный буфер на принимающей стороне. С каждым подтверждением приема, принимающая сторона сообщает объем свободного пространства в приемном буфере.

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

В высоконадежных сетях идеальным является размер буфера, являющийся произведением пропускной способности на задержку. Например, для соединения с пропускной способностью 100 Мбит/сек с 5-миллисекундной задержкой, идеальным будет размер буфера (100 000 000 / 8) x 0.005 = 62 500 байт. При наличии потерь пакетов это значение следует уменьшить.

Алгоритм Нейгла

Как уже упоминалось выше, чем меньше размер пакетов, тем выше накладные расходы на их транспортировку, потому что объем заголовков может оказаться больше объема полезных данных. Алгоритм Нейгла (Nagle) позволяет увеличить производительность сокетов TCP посредством объединения операций записи в полноценные пакеты данных. Однако за эту услугу приходится платить задержками в отправке данных. Приложения, чувствительные к задержкам, должны отключать алгоритм Нейгла установкой свойства Socket.NoDelay в значение true. Хорошо продуманные приложения обычно посылают данные большими блоками и не получат выгод от использования алгоритма Нейгла.

Зарегистрированный ввод/вывод

Зарегистрированный ввод/вывод (Registered I/O, RIO) - это новое расширение механизма WinSock, доступное в Windows Server 2012, реализующее весьма эффективный механизм регистрации буферов и извещений. RIO устраняет наиболее существенные источники накладных расходов в подсистеме ввода/вывода Windows:

Это налог, который приходится платить за изоляцию приложения от операционной системы и других приложений, гарантирующую безопасность и надежность. Без механизма RIO вам придется платить налог за каждый вызов, который при большом объеме ввода/вывода становится существенным. Напротив, используя RIO, вы платите налог только один раз, в процессе инициализации.

Механизм RIO требует регистрации буферов, которые запираются в физической памяти, пока не будут освобождены приложением (в процессе завершения приложения или подсистемы). Поскольку выделенные буферы постоянно находятся в ОЗУ, Windows может пропустить процедуру опробования, блокировки и разблокировки в каждом вызове.

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

RIO поддерживает три механизма извещений:

  1. по опросу: имеет самую низкую задержку, но требует выделения логического процессора для опроса сетевых буферов;

  2. порты завершения ввода/вывода;

  3. события Windows.

На момент написания этих строк фреймворк .NET Framework не обеспечивал доступ к механизму RIO, но его можно использовать с применением стандартных механизмов взаимодействий .NET с низкоуровневым кодом.

Пройди тесты
Лучший чат для C# программистов