Должно быть, вы подумали: «А что, если поменять местами последовательность действий транспортной подсистемы сервера, чтобы сначала осуществлялась запись, а потом высылалось подтверждение?» Представим, что запись сделана, но сбой произошел до отправки подтверждения. Тогда клиент окажется в состоянии S1 и передаст сегмент повторно, а мы получим дубликат сегмента в выходном потоке.
Таким образом, независимо от того, как запрограммированы клиент и сервер, всегда может возникнуть ситуация, в которой протокол не сможет правильно восстановиться. Сервер можно запрограммировать двумя способами: так, чтобы он в первую очередь высылал подтверждение, или так, чтобы он сначала записывал сегмент. Клиент может быть запрограммирован одним из четырех способов: всегда повторно передавать последний сегмент; никогда не передавать повторно последний сегмент; передавать сегмент повторно только в состоянии S0; передавать сегмент повторно только в состоянии S1. Таким образом, получаем восемь комбинаций, но как будет показано далее, для каждой комбинации имеется набор событий, ведущий к ошибке протокола.
На сервере могут происходить три события: отправка подтверждения (A), запись сегмента в выходной процесс (W) и сбой (C). Они могут произойти в виде шести возможных последовательностей: AC(W), AWC, C(AW), C(WA), WAC и WC(A), где скобки означают, что после C события A или W уже не произойдут (ведь уже случился сбой). На илл. 6.18 показаны все восемь комбинаций стратегий сервера и клиента, каждая со своими последовательностями событий. Обратите внимание, что для каждой комбинации существует последовательность, приводящая к ошибке протокола. Например, если клиент всегда передает повторно неподтвержденный сегмент, событие AWC приведет к появлению неопознанного дубликата, хотя при двух других последовательностях протокол будет работать правильно.
Илл. 6.18. Различные комбинации стратегий сервера и клиента
Усложнение протокола не поможет. Даже если клиент и сервер обменяются несколькими сегментами, прежде чем сервер попытается осуществить запись, и клиент будет точно знать, что происходит, у него нет возможности определить, когда произошел сбой: до или после записи. Отсюда следует неизбежный вывод: при жестком условии отсутствия одновременных событий (то есть если отдельные события происходят друг за другом, а не одновременно) более высокие уровни не могут отследить сбой и восстановление хоста.
Все это можно обобщить следующим образом: восстановление после сбоя уровня N может быть осуществлено только уровнем N + 1 и только при условии, что на более высоком уровне сохраняется информация о процессе, достаточная для возвращения в прежнее состояние. Это согласуется с утверждением о том, что транспортный уровень может обеспечить восстановление от сбоя на сетевом уровне, если каждая сторона соединения отслеживает свое текущее состояние.
Данная проблема подводит нас к вопросу о значении так называемого сквозного подтверждения. По сути, транспортный протокол является сквозным, а не последовательным, как более низкие уровни. Рассмотрим случай обращения пользователя к удаленной базе данных. Допустим, удаленная транспортная подсистема запрограммирована сначала передавать сегмент вышестоящему уровню, а затем отправлять подтверждение.
Даже в этом случае получение подтверждения устройством пользователя не означает, что удаленный хост успел обновить базу данных. Похоже, что настоящее сквозное подтверждение, получение которого означает, что работа сделана (а отсутствие означает обратное), невозможно. Более подробно этот вопрос обсуждается у Зальцера и др. (Saltzer et al., 1984).
6.3. Контроль перегрузки
Если транспортные подсистемы нескольких компьютеров будут слишком быстро отправлять чересчур много пакетов, сеть будет перегружена и ее производительность резко снизится, что приведет к задержке и потере пакетов. Контроль перегрузки, направленный на борьбу с такими ситуациями, требует совместной работы сетевого и транспортного уровней. Так как перегрузки происходят на маршрутизаторах, их обнаружением занимается сетевой уровень. Но изначальной причиной этой проблемы является трафик, переданный транспортным уровнем в сеть. Поэтому единственный эффективный способ контроля перегрузки состоит в более медленной передаче пакетов транспортными протоколами.
В главе 5 мы говорили о механизмах контроля перегрузки на сетевом уровне. В этом разделе мы рассмотрим механизмы, действующие на транспортном уровне. После обсуждения основных задач контроля перегрузки мы изучим методы, позволяющие хостам регулировать скорость передачи пакетов в сеть. Контроль перегрузки в интернете во многом опирается на транспортный уровень; для этого в TCP и другие протоколы встроены специальные алгоритмы.
6.3.1. Выделение требуемой пропускной способности
Перед тем как перейти к описанию методов регулирования трафика, необходимо понять, чего мы хотим от алгоритма контроля перегрузки. Мы должны определить, какое состояние сети он должен поддерживать. В задачи этого алгоритма входит не только предотвращение перегрузок: он должен правильно распределять пропускную способность между транспортными подсистемами, работающими в сети. Правильное распределение пропускной способности должно обеспечивать хорошую производительность (сеть должна работать с использованием всей доступной мощности без перегрузок), соответствовать принципу равноправия конкурирующих транспортных подсистем и быстро отслеживать изменения в запросах на выделение ресурсов. Мы рассмотрим каждый из этих критериев отдельно.
Эффективность и мощность
При эффективном распределении пропускной способности между транспортными подсистемами должны использоваться все возможности сети. Однако это не значит, что в канале со скоростью 100 Мбит/с каждая из пяти подсистем получит по 20 Мбит/с. Для хорошей производительности им необходимо выделить меньшую мощность. Причина в том, что трафик часто бывает неравномерным. В разделе 5.3 мы определили полезную пропускную способность (goodput), скорость, с которой полезные пакеты приходят к получателю, как функцию абонентской нагрузки на сеть. Эта кривая и соответствующая ей кривая задержки (как функция нагрузки) приведены на илл. 6.19.
Илл. 6.19. (а) Полезная пропускная способность. (б) Задержка как функция нагрузки
С повышением нагрузки на сеть полезная пропускная способность увеличивается с постоянной скоростью (илл. 6.19 (а)), но по мере того как их значения сближаются, рост полезной пропускной способности замедляется. Этот спад необходим, чтобы предотвратить переполнение буферов сети и потерю данных в случае всплесков трафика. Если транспортный протокол повторно передает задерживающиеся, но не потерянные пакеты, может произойти отказ сети из-за перегрузки. В таком случае отправители продолжают передавать все больше и больше пакетов, а пользы от этого все меньше и меньше.
График задержки пакетов приведен на илл. 6.19 (б). Сначала она постоянна и соответствует задержке распространения в сети. Когда значение нагрузки приближается к значению пропускной способности, задержка возрастает, сначала медленно, а затем все быстрее. Это также происходит из-за всплесков трафика, которые возрастают при высокой нагрузке. Задержка не уйдет в бесконечность (разве что в модели, где у маршрутизаторов бесконечный буфер). Вместо этого пакеты будут теряться при переполнении буферов.
Что касается полезной пропускной способности и задержки, производительность начинает снижаться в момент возникновения перегрузки. Логично, что мы получим наилучшую производительность сети, если будем выделять пропускную способность, пока задержка не начнет быстро расти. Эта точка находится ниже пропускной способности сети. Чтобы ее определить, Клейнрок (Kleinrock) в 1979 году предложил ввести метрику мощность (power), которая вычисляется по формуле:
.
Пока задержка достаточно мала и практически постоянна, мощность растет с ростом нагрузки на сеть; при резком повышении задержки она достигает максимума и резко снижается. Нагрузка, при которой мощность становится максимальной, и является наиболее эффективной нагрузкой, которую транспортная подсистема может передавать в сеть. Сеть должна стремиться к этому уровню.
Равнодоступность по максиминному критерию
Мы еще не говорили о том, как распределять пропускную способность между несколькими отправителями. Кажется, что ответ очень прост: можно разделить пропускную способность между ними поровну. Но на самом деле все намного сложнее.
Во-первых, какое отношение эта проблема имеет к контролю нагрузки? Ведь если сеть предоставляет отправителю некоторое количество пропускной способности, он должен просто использовать то, что у него есть. Но на практике сети часто не резервируют строго ограниченное количество пропускной способности для потока или соединения. Конечно, они могут это делать для некоторых потоков (если есть поддержка QoS), но как правило, соединения стремятся использовать максимум доступной пропускной способности; также возможен вариант, когда сеть выделяет ресурсы группе соединений для совместного использования. К примеру, дифференцированное обслуживание IETF делит трафик на два класса, и соединения конкурируют друг с другом в пределах каждого из этих классов. Часто все соединения одного IP-маршрутизатора используют общую пропускную способность. В таких случаях распределение ресурсов между конкурирующими соединениями должно осуществляться за счет механизмов контроля нагрузки.
Во-вторых, не совсем ясно, что означает равнодоступность для потоков в сети. Если N потоков используют один канал, то решение довольно простое: каждому выделяется 1/N пропускной способности (в случае импульсного трафика эта величина немного меньше по соображениям эффективности). Но что, если потоки используют разные, но пересекающиеся пути? К примеру, если один поток проходит по трем каналам, а остальные — по одному? Очевидно, что поток, идущий по трем каналам, потребляет больше сетевых ресурсов. Поэтому в некотором смысле справедливее было бы выделить ему меньше пропускной способности, чем остальным. Кроме того, неплохо было бы добавить новые одноканальные потоки за счет уменьшения пропускной способности трехканального. На этом примере видно, что между эффективностью и равнодоступностью всегда существуют противоречия.