Предотвращение перегрузок
𝔯𝔱_𝔣𝔞𝔫В жизни любого маршрутизатора наступает момент, когда очередь переполняется. Куда положить пакет, если положить его решительно некуда — всё, буфер кончился, совсем, и не будет, даже если хорошо поискать, даже если доплатить.
Тут есть два путя: отбросить либо этот пакет, либо те, что уже очередь забили.
Если те, что уже в очереди, то считай, что пропалодё.
А если этот, то считай, что и не приходил он.
Эти два подхода называются Tail Drop и Head Drop.
Другие механизмы AQM, которые подходят к вопросу более интеллектуально:
- RED - Random Early Detection
- WRED - Weighted Random Early Detection
Tail Drop и Head Drop.
Tail Drop — наиболее простой механизм управления очередью — отбрасываем все вновь пришедшие пакеты, не помещающиеся в буфер.

Head Drop отбрасывает пакеты, которые стоят в очереди уже очень долго. Их уже лучше выбросить, чем пытаться спасти, потому что они, скорее всего, бесполезны. Зато у более актуальных пакетов, пришедших в конец очереди, будет больше шансов прийти вовремя. Плюс к этому Head Drop позволит не загружать сеть ненужными пакетами. Естественным образом самыми старыми пакетами оказываются те, что в самой голове очереди, откуда и название подхода.

У Head Drop есть и ещё одно неочевидное преимущество — если пакет отбросить в начале очереди, то получатель быстрее узнает о перегрузке на сети и сообщит отправителю. В случае Tail Drop информация об отброшенном пакете дойдёт, возможно, на сотни миллисекунд позже — пока будет добираться с хвоста очереди до её головы.
Оба механизма работают с дифференциацией по очередям. То есть на самом деле не обязательно, чтобы весь буфер переполнился. Если 2-ая очередь пустая, а нулевая под завязку, то отбрасываться буду только пакеты из нулевой.

Tail Drop и Head Drop могут работать одновременно.

Tail и Head Drop — это Congestion Avoidance «в лоб». Даже можно сказать — это его отсутствие.
Ничего не предпринимаем, пока очередь не заполнится на 100%. А после этого все вновь прибывшие (или надолго задержавшиеся) пакеты начинаем отбрасывать.
Если для достижения цели не нужно ничего делать, значит где-то есть нюанс.
И этот нюанс — TCP.
Вспомним (поглубже и экстремально глубоко), как работает TCP — речь про современные реализации.
Есть Скользящее окно(Sliding Window или rwnd — Reciever's Advertised Window), которым управляет получатель, сообщая отправителю, сколько можно посылать.
А есть окно перегрузки (CWND — Congestion Window), которое реагирует на проблемы в сети и управляется отправителем.
Процесс передачи данных начинается с медленного старта (Slow Start) с экспоненциальным ростом CWND. С каждым подтверждённым сегментом к CWND прибавляется 1 размер MSS, то есть фактически оно удваивается за время, равное RTT (туда данные, обратно ACK) (Речь про Reno/NewReno).
Например,

Экспоненциальный рост продолжается до значения, называемого ssthreshold (Slow Start Threshold), которое указывается в конфигурации TCP на хосте.
Далее начинается линейный рост по 1/CWND на каждый подтверждённый сегмент до тех пор, пока либо не упрётся в RWND, либо не начнутся потери (о потерях свидетельсв повторное подтверждение (Duplicated ACK) или вообще отсутствие подтверждения).
Как только зафиксирована потеря сегмента, происходит TCP Backoff — TCP резко уменьшает окно, фактически снижая скорость отправки, — и запускается механизм Fast Recovery:
- отправляются потерянные сегменты (Fast Retransmission),
- окно скукоживается в два раза,
- значение ssthreshold тоже становится равным половине достигнутого окна,
- снова начинается линейный рост до первой потери,
- Повторить.

Потеря может означать либо полный развал какого-то сегмента сети и тогда считай, что пропало, либо перегрузку на линии (читай переполнение буфера и отбрасывание сегмента данной сессии).
Таков у TCP метод максимальной утилизации доступной полосы и борьбы с перегрузками. И он достаточно эффективен.
Однако к чему приводит Tail Drop?
- Допустим через маршрутизатор лежит путь тысячи TCP-сессий. В какой-то момент трафик сессий достиг 1,1 Гб/с, скорость выходного интерфейса — 1Гб/с.
- Трафик приходит быстрее, чем отправляется, буферы заполняются всклянь.
- Включается Tail Drop, пока диспетчер не выгребет из очереди немного пакетов.
- Одновременно десятки или сотни сессий фиксируют потери и уходят в Fast Recovery (а то и в Slow Start).
- Объём трафика резко падает, буферы прочищаются, Tail Drop выключается.
- Окна затронутых TCP-сессий начинают расти, и вместе с ними скорость поступления трафика на узкое место.
- Буферы переполняются.
- Fast Recovery/Slow Start.
- Повторить.
- Подробнее об изменениях в механизмах TCP в RFC 2001 (TCP Slow Start, Congestion Avoidance, Fast Retransmit, and Fast Recovery Algorithms).
- Это характерная иллюстрация ситуации, называемой глобальной синхронизацией TCP (Global TCP Synchronization):

Глобальная — потому что страдают много сессий, установленных через этот узел.
Синхронизация, потому что страдают они одновременно. И ситуация будет повторяться до тех пор, пока имеет место перегрузка.
TCP — потому что UDP, не имеющий механизмов контроля перегрузки, ей не подвержен.
Ничего плохого в этой ситуации не было бы, если бы она не вызывала неоптимальное использование полосы — промежутки между зубцами пилы — потраченные зря деньги. Вторая проблема — это TCP Starvation — истощение TCP. В то время, как TCP сбавляет свою скорость для снижения нагрузки (не будем лукавить — в первую очередь, чтобы наверняка передать свои данные), UDP эти все моральные страдания вообще по датаграмме — шлёт сколько может.
Итак, количество трафика TCP снижается, а UDP растёт (возможно), следующий цикл Потеря — Fast Recovery случается на более низком пороге. UDP занимает освободившееся место. Общее количество TCP-трафика падает.
Чем решать проблему, лучше её избежать. Давайте попробуем снизить нагрузку до того, как она заполнит очередь, воспользовавшись Fast Recovery/Slow Start, который только что был против нас.
RED — Random Early Detection.
А что если взять и дропы размазать по некоторой части буфера?
Условно говоря, начинать отбрасывать случайные пакеты, когда очередь заполняется на 80%, заставляя некоторые TCP-сессии уменьшить окно и соответственно, скорость.
А если очередь заполнена на 90%, начинаем отбрасывать случайным образом 50% пакетов.
90% — вероятность растёт вплоть до Tail Drop (100% новых пакетов отбрасывается).
Именно так и работает RED — более продвинутый алгоритм AQM.
Early Detection — фиксируем потенциальную перегрузку;
Random — отбрасываем пакеты в случайном порядке.
Иногда расшифровывают RED (на мой взгляд семантически более корректно), как Random Early Discard.
Графически это выглядит так:


До заполнение буфера на 80% пакеты не отбрасываются совсем — вероятность 0%.
От 80 до 100 пакеты начинают отбрасываться, и тем больше, чем выше заполнение очереди. Так процент растёт от 0 до 30.
Побочным эффектом RED является и то, что агрессивные TCP-сессии скорее начнут притормаживать, просто потому что их пакетов много и они с бо́льшей вероятностью будут отброшены. Неэффективность использования полосы RED решает тем, что притупляет гораздо меньшую часть сессий, не вызывая такую серьёзную просадку между зубьями.
Ровно по этой же причине UDP не может оккупировать всё.
WRED — Weighted Random Early Detection.
Но на слуху у всех, наверно, всё-таки WRED. Проницательный читатель linkmeup уже предположил, что это тот же RED, но взвешенный по очередям. И оказался не совсем прав.
RED работает в пределах одной очереди. Нет смысла оглядываться на EF, если переполнилась BE. Соответственно и взвешивание по очередям ничего не принесёт.
Здесь как раз работает Drop Precedence.
И в пределах одной очереди у пакетов с разным приоритетом отбрасывания будут разные кривые. Чем ниже приоритет, тем больше вероятность того, что его прихлопнут.

Здесь есть три кривые:
Красная — менее приоритетный трафик (с точки зрения отбрасывания), жёлтая — более, зелёная — максимально.
Красный трафик начинает отбрасываться уже при заполнении буфера на 20%, с 20 до 40 он дропается вплоть до 20%, далее — Tail Drop.
Жёлтый стартует попозже — с 30 до 50 он отбрасывается вплоть до 10%, далее — Tail Drop.
Зелёный наименее подвержен: с 50 до 100 плавно растёт до 5 %. Далее — Tail Drop.
В случае с DSCP это могли бы быть AF11, AF12 и AF13, соответственно зелёная, жёлтая и красная.

Очень важно здесь то, что это работает с TCP и это абсолютно неприменимо к UDP.
Либо приложение использующее UDP игнорирует потери, как в случае телефонии или потокового видео, и это отрицательно сказывается на том, что видит пользователь. Либо приложение само осуществляет контроль доставки и попросит повторно отправить тот же самый пакет. При этом оно совсем не обязано просить источник снизить скорость передачи. И вместо уменьшения нагрузки получается увеличение из-за ретрансмитов. Именно поэтому для EF применяется только Tail Drop. Для CS6, CS7 тоже применяется Tail Drop, поскольку там не предполагается больших скоростей и WRED ничего не решит. Для AF применяется WRED. AFxy, где x — класс сервиса, то есть очередь, в которую он попадает, а y — приоритет отбрасывания — тот самый цвет. Для BE решение принимается на основе преобладающего в этой очереди трафика.
В пределах одного маршрутизатора используются специальная внутренняя маркировка пакетов, отличная от той, что несут заголовки. Поэтому MPLS и 802.1q, где нет возможности кодировать Drop Precedence, в очередях могут обрабатываться с различными приоритетами отбрасывания.
Например, MPLS пакет пришёл на узел, он не несёт маркировки Drop Precedence, однако по результатам полисинга оказался жёлтым и перед помещением в очередь (которая может определяться полем Traffic Class), может быть отброшен. При этом стоит помнить, что вся эта радуга существует только внутри узла. В линии между соседями нет понятия цвета. Хотя закодировать цвет в Drop Precedence-часть DSCP, конечно, возможно.
Отбрасывания могут появиться и в незагруженной сети, где казалось бы никакого переполнения очередей быть не должно. Как? Причиной этому могут быть кратковременные всплески — бёрсты — трафика. Самый простой пример — 5 приложений одновременно решили передать трафик на один конечный хост. Пример посложнее — отправитель подключен через интерфейс 10 Гб/с, а получатель — 1 Гб/с. Сама среда позволяет крафтить пакеты быстрее на отправителе. Ethernet Flow Control получателя просит ближайший узел притормозить, и пакеты начинают скапливаться в буферах.
Ну а что же делать, когда плохо всё-таки стало?