Дисциплина обработки очереди HTB. Руководство по использованию

htb networking shaping

Оригинальный документ опубликован на сайте дисциплины HTB.

Martin Devera aka devik

Авторы руководства: devik и Don Cohen

Последнее обновление: 5/5/2002

1. Введение

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

В этом документе описано использование HTB. Большинство глав содержат примеры, диаграммы с графиками поведения потока данных и обсуждения частных проблем.

HTB в текущей реализации стала ещё более масштабируемой. На сайте HTB опубликованы результаты сравнения CBQ и HTB.

2. Распределение канала

Рассмотрим задачу. Трафик клиентов A и B отправляется в сеть через интерфейс eth0. Нужно гарантировать скорость 60 kbps для клиента B и 40 kpbs для A. Полосу А нужно в свою очередь разделить на 30 kbps для веб-трафика и 10 kbps для всего остального. Свободную часть канала нужно делить между клиентами в заданной пропорции.

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

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

Классификация трафика

В HTB различные типы трафика описываются классами. Простейшая классификация трафика для нашей задачи показана на рисунке справа.

Рассмотрим команды, которые нам понадобятся.

tc qdisc add dev eth0 root handle 1: htb default 12

Эта команда присоединяет к устройству eth0 дисциплину очереди HTB и назначает ей управляющий дескриптор 1:. Это просто идентификатор, на который мы будем ссылаться ниже. Параметр default 12 указывает на то, что трафик, не попавший ни в одну классификацию, будет отнесён к классу 1:12.

tc class add dev eth0 parent 1: classid 1:1 htb rate 100kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 30kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 10kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 60kbps ceil 100kbps

Первая строка присоединяет к дисциплине 1: корневой класс 1:1.

Класс называется корневым, если он является непосредственным потомком дисциплины HTB, корневые классы могут предоставлять часть своего канала соседним классам, но сами заимствовать каналы не могут. В этом их основная особенность. Можно было бы объявить все три класса трафика потомками корневой дисциплины, но тогда они не смогли бы использовать излишки пропускной способности соседей. В нашей задаче необходимо обеспечить возможность заимствования каналов, поэтому мы создали один корневой класс, а для непосредственного управления трафиком - несколько дочерних. Потомки корневого класса описаны в последних трёх строчках. Параметр ceil обсудим позже.

Теперь определим критерии, по которым пакеты будут относиться к определённому классу. Описание методов классификации не входит в рамки обсуждения дисциплины HTB. За подробностями обратитесь к документации по команде tc filter. Нужные нам команды выглядят примерно так:

tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip src 1.2.3.4 match ip dport 80 0xffff flowid 1:10
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
match ip src 1.2.3.4 flowid 1:11

(Предполагается, что клиент А имеет IP-адрес 1.2.3.4.)

Обратите внимание, что для класса 1:12 фильтр не создан. Возможно, дополнительный фильтр внёс бы большую ясность, но сейчас мы хотим подчеркнуть, что класс 1:12 используется по умолчанию. Любой пакет, который не удовлетворяет условиям приведённых фильтров, будет отнесён к классу 1:12.

Теперь можно назначить дисциплины очередей для краевых классов. По умолчанию устанавливается дисциплина pfifo.

tc qdisc add dev eth0 parent 1:10 handle 20: pfifo limit 5
tc qdisc add dev eth0 parent 1:11 handle 30: pfifo limit 5
tc qdisc add dev eth0 parent 1:12 handle 40: sfq perturb 10

Итак, все необходимые команды выполнены.

Графики скоростей передачи

Посмотрим что происходит, когда идёт поток пакетов каждого класса со скоростью 90 kbps, и время от времени передача пакетов одного из классов прекращается. Красные метки под горизонтальной осью показывают моменты, когда скорость передачи данных изменяется. Также под диаграммой есть обозначения типа 0:90k. Перед двоеточием записан условный номер класса (0 для класса 1:10 (веб-трафик клиента A), 1 для 1:11 (прочий трафик клиента A), 2 для 1:12 (трафик клиента B)), а после двоеточия количество передающихся данных. К примеру, объём данных класса 0 в момент 0 равен 90k, в момент 3 равен 0, а в момент 6 снова равен 90k.

Изначально генерируется 90kb данных каждого класса. Поскольку для передачи такого объёма требуется скорость, превышающая возможную, каждому классу выделяется минимальная гарантированная в определении (параметром rate) скорость. В момент 3 передача данных класса 0 прекращается, и канал, который использовался этим классом, распределяется между остальными пропорционально указанным гарантированным скоростям: 1 часть классу 1 и 6 частей классу 2 (увеличение скорости для класса 1 трудно заметить, поскольку оно равно всего 4 kbps). В момент 9 прекращается поток данных класса 1, и канал этого класса так же распределяется между оставшимися двумя (теперь незаметно увеличение канала для класса 0). Хорошо видно как в момент 15 от освободившегося канала класса 2 переходят 3 части к классу 0 и 1 часть к классу 1. В момент 18 исчезают потоки классов 1 и 2, поэтому класс 0 целиком получает необходимые 90 kbps.

Сейчас стоит рассмотреть понятие квантов трафика. В процессе конкурентной передачи данных нескольких классов обслуживание каждого из них начинается после того, как обработано некоторое количество байт предыдущего класса. Это количество байт называется квантом (quantum). Когда несколько классов претендуют на канал родительского, они получают части канала пропорциональные их квантам. Важно знать, что расчёт распределения канала тем точнее, чем меньше размер кванта (но квант всё же должен быть не меньше величины MTU). Как правило нет необходимости задавать кванты вручную, поскольку HTB рассчитывает их самостоятельно. Размер кванта класса устанавливается (при создании или изменении класса) равным запрошенной скорости делённой на глобальный параметр r2q. По умолчанию r2q равен 10, что приемлемо для скоростей выше 15 kbps (120 kbit), поскольку обычно MTU равен 1500. Для меньших скоростей величина r2q равна 1; это подходит для скоростей от 12 kbit. При желании можно установить величину кванта вручную. В этом случае параметр r2q игнорируется. Будут выведены предупреждения о том, что установленная величина не верна, но ими можно пренебречь.

Приведённый пример был бы хорошим решением, если бы А и B не были независимыми клиентами. Но клиент А, который оплатил скорость 40 kbps, предпочитает чтобы излишки полосы, предназначенной для его веб-трафика, использовались для передачи остальных его данных, а не переходили к клиенту B. Выполнение этого требования обеспечивается созданием иерархии классов HTB.

3. Иерархическое распределение

Иерархическая классификация трафика

Задача, поставленная в конце предыдущей главы, решается введением иерархии классов, показанной на этом рисунке. Трафик клиента А теперь детализируется потомками независимого класса. Как было сказано выше, скорость передачи данных каждого класса равна минимуму из запрошенной скорости и скорости, гарантированной определением класса. Это применимо к тем классам HTB, которые не имеют потомков. Такие классы мы называем краевыми. Для родительских классов действует правило: скорость передачи данных родительского класса равна минимуму из скорости, гарантированной определением класса, и суммы скоростей, запрошенных дочерними классами. В нашем случае клиенту А гарантирована скорость 40 kbps. Если он не использует полосу, выделенную для веб-трафика, остаток канала в первую очередь отводится трафику, относящемуся к другим классам клиента А.

Теперь список команд выглядит так:

tc class add dev eth0 parent 1: classid 1:1 htb rate 100kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:2 htb rate 40kbps ceil 100kbps
tc class add dev eth0 parent 1:2 classid 1:10 htb rate 30kbps ceil 100kbps
tc class add dev eth0 parent 1:2 classid 1:11 htb rate 10kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 60kbps ceil 100kbps
Графики скоростей передачи

Обратимся к диаграмме с результатами иерархического решения. Когда прекращается веб-трафик клиента А, канал этого класса заимствуется другими классами A. Таким образом клиент А всегда имеет гарантированную ширину канала 40 kbps. Только когда А запрашивает в целом меньше 40 kbps, излишки его канала могут быть отданы клиенту B.

4. Ограничение максимальной скорости

Параметр ceil определяет максимальную скорость передачи данных класса. Класс не может получить канал больше указанной величины. По умолчанию ceil' равен rate (в предыдущих примерах мы всегда явно указывали величину ceil именно для того, чтобы разрешить перераспределение всего канала). Теперь вместо ceil=100kbps установим для классов 1:2 (весь трафик клиента А) и 1:11 (не относящийся к WWW трафик клиента A) ceil=60kbps и ceil=20kbps соответственно.

Графики скоростей передачи

Теперь диаграмма отличается от предыдущей, начиная с момента 3 (прекращение передачи веб-трафика), поскольку скорость передачи прочего трафика A не может вырасти выше 20 kbps, клиенту А достаётся только 20 kbps, а остальные 20 kbps отдаются клиенту B. Второе отличие - в моменте 15, когда исчезает трафик клиента B. До введения ограничений клиент А получал канал целиком, сейчас же ему доступно в общем не более 60 kbps, поэтому оставшиеся 40 kbps просто не используются.

Рассмотренное свойство используется ISP, когда нужно ограничить ширину канала клиента независимо от того, используется ли канал остальными клиентами (ISP могут расширить канал за плату). Отметим, что поскольку корневой класс не может заимствовать скорость, нет необходимости определять для него параметр ceil.

5. Ускорения

Сетевое устройство может передавать только один пакет в один момент времени и только с определённой устройством скоростью. Программам разделения канала остаётся только использовать эти параметры для расчётов деления канала на полосы требуемого размера. Таким образом текущая и максимальная скорости передачи данных не могут быть урегулированы мгновенно. Они рассчитываются на основании усреднённых данных о скорости отсылки пакетов. В действительности передача данных с учётом заданных свойств классов происходит следующим образом: устройству передаётся некоторое количество пакетов одного класса (которые отправляются в сеть со скоростью, определённой устройством т.е. максимально возможной), после чего некоторое время обрабатываются пакеты остальных классов. Параметры класса burst и cburst определяют количество данных, которое можно передать на максимальной скорости (скорости оборудования), после чего переключиться на обслуживание других классов.

Чем меньше параметр cburst (в идеале он должен быть равен размеру одного пакета), тем лучше он сглаживает ускорение передачи данных, чтобы скорость передачи не превысила величину ceil. Так же действует параметр peakrate дисциплины TBF.

Если параметр burst родительского класса меньше чем у какого-нибудь из потомков, можно ожидать, что передача данных этого класса иногда будет останавливаться (так как дочерний класс пытается создать большее ускорение, чем позволено родительскому). HTB помнит о неудачной попытке ускорения в течение минуты.

Возникает вопрос: для чего нужно регулировать ускорение? Дело в том, что это дешёвый и простой способ улучшить время отклика на переполненных каналах. К резким колебания склонен, к примеру, веб-трафик. Вы запрашиваете страницу, создавая ускорение, потом читаете её. За время чтения «накапливается энергия» для следующего ускорения.

Графики скоростей передачи

На диаграмме можно увидеть что происходит, если в предыдущем примере установить параметр burst для желтого и красного классов (трафик клиента A) в 20 kb, а cburst оставить прежним (2 kb). Зелёная линия резко поднимается в момент 13 в соответствии с величиной ускорения для класса SMTP клиента А. С момента 9 он был ограничен и накопил 20 kb для мгновеннй передачи. Соответственно скорость возрастает до 20 kbps (не выше ceil, так как cburst приблизительно равен размеру пакета).

Вдумчивый читатель может задать вопрос: почему не было всплесков красного и жёлтого графиков в момент 7? Это потому, что жёлтый класс уже достиг максимальной скорости и не имеет возможности отдать трафик с ускорением. На графике присутствует по крайней мере один нежелательный артефакт: провал фиолетового графика в момент 4. Это вызвано тем, что мы намеренно «забыли» установить burst для класса 1:1. Корневой класс запомнил всплеск в момент 1, и когда синий класс в момент 4 запросил займ канала жёлтого класса, уменьшение жёлтого канала было запрещено, а потребность синего класса была компенсирована за счёт общего канала (ресурса класса 1:1).

Заниженная величина burst приведёт к тому, что данные будут передаваться на скорости ниже запрошенной параметром rate. Последние версии tc по умолчанию самостоятельно рассчитывают и устанавливают минимальное значение burst.

6. Распределение с учётом приоритетов

Рассмотрим два аспекта распределения каналов с учётом приоритетов.

Графики скоростей передачи

Первый касается распределения излишков канала между потомками одного класса. Как было сказано выше, излишки канала распределяются пропорционально гарантированным классам скоростям. Возьмём конфигурацию из главы 3 (иерархические классы без ограничения ускорений и максимальной скорости), установим приоритет 0 (высший) для класса SMTP (зелёный график) и приоритет 1 для остальных. Можно увидеть, что класс SMTP получает все излишки канала. Действует правило согласно которому при распределении излишков канала класс с наивысшим приоритетом обслуживается в первую очередь. Тем не менее установки гарантированной (rate) и максимальной (ceil) скоростей продолжают действовать.

Второй аспект касается общей задержки пакета. Скорость передачи данных ethernet-устройству слишком высока, чтобы заметить и оценить задержку передачи пакета. Есть простое решение задачи создания наблюдаемой задержки. Создадим корневой класс HTB, а к ней - класс, которому гарантируем скорость 100 kbps. Таким образом мы смоделировали перегруженное соединение с увеличенными задержками.

Рассмотрим простой сценарий с двумя классами трафика:

# дисциплина, симулирующая задержку
tc qdisc add dev eth0 root handle 100: htb
tc class add dev eth0 parent 100: classid 100:1 htb rate 90kbps

# наблюдаемая дисциплина
tc qdisc add dev eth0 parent 100:1 handle 1: htb

AC="tc class add dev eth0 parent"
$AC 1: classid 1:1 htb rate 100kbps
$AC 1:2 classid 1:10 htb rate 50kbps ceil 100kbps prio 1
$AC 1:2 classid 1:11 htb rate 50kbps ceil 100kbps prio 1
tc qdisc add dev eth0 parent 1:10 handle 20: pfifo limit 2
tc qdisc add dev eth0 parent 1:11 handle 21: pfifo limit 2

Симулятор будет генерировать трафик обоих классов со скоростью 50 kbps, а в момент 3 сек. выполнит команду

# Повышение приоритета веб-трафика
tc class change dev eth0 parent 1:2 classid 1:10 htb \
rate 50kbps ceil 100kbps burst 2k prio 0
Графики скоростей передачи

Как видим, с момента повышения приоритета задержка класса WWW падает почти до нуля, в то время как задержка SMTP растёт. Когда приоритет одного класса улучшает задержку, задержка другого класса ухудшается. В момент 7 симулятор начинает генерировать трафик WWW со скоростью 60 kbps, а SMTP со скоростью 40 kbps. Здесь можно наблюдать ещё одно интересное поведение дисциплины. Когда привилегированный класс (WWW) превышает гарантированную скорость, HTB обрабатывает трафик, не превысивший лимит, как приоритетный.

Каким классам резонно назначать высший приоритет? В основном это те классы трафика, которые должны передаваться с минимальными задержками. Например видео- и аудиотрафик (здесь нужно серьёзно обдумать ограничения, чтобы этот трафик не оккупировал канал полностью) или интерактивный (telnet, SSH) трафик, который по своей природе состоит из временных всплесков и не сильно влияет на соседние потоки. Распространённый трюк - увеличивать приоритет ICMP пакетов чтобы показывать хорошее время отклика утилиты ping даже на полностью загруженных каналах (понятно, что с технической точки зрения это не говорит об улучшении соединения).

7. Разбор статистики

Утилита tc позволяет накапливать статистику использования дисциплин очередей Linux. К сожалению данные статистики не разъясняются авторами утилиты, поэтому зачастую выглядят бесполезными. Попытаемся разобраться со статистикой HTB.

Сперва рассмотрим статистику дисциплины HTB. Здесь рассматриваются данные, собранные во время исполнения примера из главы 3.

# tc -s -d qdisc show dev eth0
qdisc pfifo 22: limit 5p
Sent 0 bytes 0 pkts (dropped 0, overlimits 0)

qdisc pfifo 21: limit 5p
Sent 2891500 bytes 5783 pkts (dropped 820, overlimits 0)

qdisc pfifo 20: limit 5p
Sent 1760000 bytes 3520 pkts (dropped 3320, overlimits 0)

qdisc htb 1: r2q 10 default 1 direct_packets_stat 0
Sent 4651500 bytes 9303 pkts (dropped 4140, overlimits 34251)

Первые три дисциплины относятся к краевым классам. Их мы рассматривать не будем, поскольку статистика PFIFO очевидна.

Дисциплина HTB. Значение overlimits показывает сколько раз дисциплина задерживала пакеты. direct_packets_stat показывает сколько пакетов было послано через прямую очередь. Остальные данные очевидны. Рассмотрим статистику классов:

# tc -s -d class show dev eth0
class htb 1:1 root prio 0 rate 800Kbit ceil 800Kbit burst 2Kb/8 mpu 0b
cburst 2Kb/8 mpu 0b quantum 10240 level 3
Sent 5914000 bytes 11828 pkts (dropped 0, overlimits 0)
rate 70196bps 141pps
lended: 6872 borrowed: 0 giants: 0

class htb 1:2 parent 1:1 prio 0 rate 320Kbit ceil 4000Kbit burst 2Kb/8 mpu 0b
cburst 2Kb/8 mpu 0b quantum 4096 level 2
Sent 5914000 bytes 11828 pkts (dropped 0, overlimits 0)
rate 70196bps 141pps
lended: 1017 borrowed: 6872 giants: 0

class htb 1:10 parent 1:2 leaf 20: prio 1 rate 224Kbit ceil 800Kbit burst 2Kb/8 mpu 0b
cburst 2Kb/8 mpu 0b quantum 2867 level 0
Sent 2269000 bytes 4538 pkts (dropped 4400, overlimits 36358)
rate 14635bps 29pps
lended: 2939 borrowed: 1599 giants: 0

Для экономии места статистика классов 1:11 и 1:12 опущена. В начале показаны параметры класса, установленные при создании. Есть информация об уровне класса и размере кванта. Значение overlimits показывает сколько раз пакеты класса не были отосланы вследствие ограничений rate/cell (в настоящий момент рассчитывается только для краевых классов). Строка rate ... pps показывает реальную (рассчитываемую в среднем за 10 секунд) скорость передачи пакетов класса. lended это количество пакетов других классов, прошедших через канал класса "в долг", а borrowed - количество пакетов этого класса, прошедших через одолженные каналы. Ссуды (lends) считаются отдельно для каждого класса, в то время как займы (borrows) считаются с учётом переходов (если класс 1:10 занимает канал у класса 1:2, а тот для этого занимает канал у класса 1:1, увеличиваются счётчики займов как для 1:10, так и для 1:2). Значение giants показывает количество пакетов, оказавшихся больше MTU, установленного командой tc. Такие пакеты HTB обрабатывает, но скорость регулируется с большей погрешностью. Используйте установку MTU при помощи tc (по умолчанию 1600 байт).

8. Сборка, отладка и отчёты об ошибках

Начиная с версии ядра 2.4.20 дополнительные патчи не нужны. Необходимо только обновить утилиту tc. Скачайте архив HTB 3.6 и используйте утилиту tc из него.

Для более старых ядер необходимо накладывать патч. Скачайте исходные тексты ядра и выполните команду patch -p1 -i htb3_2.X.X.diff. Затем настройте и соберите ядро командами make menuconfig, make bzImage как обычно. Не забудьте включить QoS и HTB. Кроме того понадобится исправленная утилита tc. Можно внести исправления патчем или скачать собранный бинарный пакет.

Если Вы нашли ошибку, я буду благодарен за отчёт о ней. В случае oops'ов, пришлите вывод ksymoops. В случае странного поведения дисциплины добавьте параметр debug 3333333 при вызове <kbd>tc qdisc add ... htb</kbd>. Это прведёт к выводу большого количества отладочной информации класса kern с приоритетом debug. Возможно в этом случае лучше добавить строчку kern.debug -/var/log/debug в файл /etc/syslog.conf. После чего вышлите по электронной почте лог, сжатый bzip2 (не больше 10MB) вместе с описанием проблемы и моментом её возникновения.