From: Alexey Remizov <alexey@remizov.pp.ru.>
Date: Mon, 25 May 2006 18:21:07 +0000 (UTC)
Subject: Дисциплина обработки очереди HTB. Руководство по использованию.
* Дисциплина обработки очереди HTB. Руководство по использованию.
+ 1. Введение
+ 2. Распределение канала
+ 3. Иерархическое распределение
+ 4. Ограничение максимальной скорости
+ 5. Ускорения
+ 6. Распределение с учётом приоритетов
+ 7. Разбор статистики
+ 8. Сборка, отладка и отчёты об ошибках
Дисциплина обработки очереди HTB. Руководство по использованию.
Оригинальный документ опубликован на сайте дисциплины HTB.
Martin Devera aka devik (devik@cdi.cz)
Авторы руководства: devik и Don Cohen
Последнее обновление: 5/5/2002
1. Введение
HTB задумывалась как более понятная, прозрачная и быстрая замена
дисциплины CBQ для Linux. CBQ и HTB помогают контролировать исходящий
трафик на заданном интерфейсе. Обе дисциплины позволяют разделить
физический канал на несколько логических и распределить по ним трафик
разных типов. Для этого необходимо определить как именно распределять
физическое соединение и классифицировать исходящий трафик.
В этом документе описано использование HTB. Большинство глав содержат
примеры, диаграммы с графиками поведения потока данных и обсуждения
частных проблем.
HTB в текущей реализации стала ещё более масштабируемой. На сайте HTB
опубликованы результаты сравнения CBQ и HTB.
Утилита tc (не только для HTB) использует следующие сокращения для
обозначения пропускной способности: kbps означает килобайт в секунду,
а kbit - килобит в секунду. Это один из наиболее часто встречающихся
вопросов.
2. Распределение канала
Рассмотрим задачу. Трафик клиентов A и B отправляется в сеть через
интерфейс eth0. Нужно гарантировать скорость 60 kbps для клиента B и
40 kpbs для A. Полосу А нужно в свою очередь разделить на 30 kbps для
веб-трафика и 10 kbps для всего остального. Свободную часть канала
нужно делить между клиентами в заданной пропорции.
Согласно правилам HTB скорость передачи данных каждого класса будет
равна минимуму из запрошенной скорости и скорости, гарантированной
определением класса. Если класс запрашивает скорость меньше
гарантированной, остаток полосы распределяется между остальными.
Прочтите также документ о внутреннем строении HTB, где приведённая
выше задача рассматривается более детально.
В литературе использование излишков пропускной способности канала
описывается как "займ" или "одалживание". В этом документе мы будем
стараться придерживаться сложившейся терминологии, хотя нам она
кажется неудачной, так как при "займах" трафика отсутствуют какие-либо
гарантии.
В HTB различные типы трафика описываются классами. Простейшая
классификация трафика для нашей задачи показана на рисунке справа.
Эта команда присоединяет к устройству eth0 дисциплину очереди HTB и
назначает ей управляющий дескриптор 1:. Это просто идентификатор, на
который мы будем ссылаться ниже. Параметр default 12 указывает на то,
что трафик, не попавший ни в одну классификацию, будет отнесён к
классу 1:12.
В общем виде (не только для HTB, но и для всех дисциплин и классов tc)
управляющие дескрипторы (handles) выглядят как x:y где x - целое
число, определяющее дисциплину, а y - целое число, определяющее класс,
присоединённый к дисциплине. В дескрипторе дисциплины y должен быть
равен 0. Дескриптор класса должен содержать ненулевое значение y.
Запись 1: эквивалентна записи 1:0.
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:11.
Класс называется корневым, если он является непосредственным потомком
дисциплины HTB. Как и другие классы в дереве наследования HTB,
корневые классы могут предоставлять часть своего канала соседним
классам, но сами заимствовать каналы не могут. В этом их основная
особенность. Можно было бы объявить все три класса трафика потомками
корневой дисциплины, но тогда они не смогли бы использовать излишки
пропускной способности соседей. В нашей задаче необходимо обеспечить
возможность заимствования каналов, поэтому мы создали один корневой
класс, а для непосредственного управления трафиком - несколько
дочерних. Потомки корневого класса описаны в последних трёх строчках.
Параметр ceil обсудим позже.
Иногда возникает вопрос, зачем устанавливать параметр dev eth0 для
потомков, если он уже определён для родительского класса. Причина в
том, что каждому интерфейсу выделяется локальное пространство имён
дескрипторов, поэтому, к примеру, eth0 и eth1 могут иметь разные
классы с дескриптором 1:1.
Теперь определим критерии, по которым пакеты будут относиться к
определённому классу. Описание методов классификации не входит в рамки
обсуждения дисциплины 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.)
В классификаторе U32 есть недокументированная ошибка, которая приводит
к тому, что команда tc filter show дублирует строчки для фильтров,
содержащих классификаторы U32 c разными величинами prio.
Обратите внимание, что для класса 1:12 фильтр не создан. Возможно,
дополнительный фильтр внёс бы большую ясность, но сейчас мы хотим
подчеркнуть, что класс 1:12 используется по умолчанию. Любой пакет,
который не удовлетворяет условиям приведённых фильтров, будет отнесён
к классу 1:12.
Теперь можно назначить дисциплины очередей для краевых классов. По
умолчанию устанавливается дисциплина pfifo.
Посмотрим что происходит, когда идёт поток пакетов каждого класса со
скоростью 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. Если он не использует полосу, выделенную для
веб-трафика, остаток канала в первую очередь отводится трафику,
относящемуся к другим классам клиента А.
Трафик, относящийся к внутренним классам, тоже может быть
классифицирован путём присоединения к ним фильтров. В итоге трафик
будет отнесён к одному из краевых классов или к классу 1:0. Скорость,
гарантированная родительскому классу, должна равняться сумме
скоростей, гарантированных классам-потомкам.
Теперь список команд выглядит так:
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.
Величина ceil должна быть не меньше rate. Кроме того величина ceil для
родительского класса должна быть не меньше, чем у любого дочернего
класса.
5. Ускорения
Примечание переводчика. В этой главе предполагается, что читатель
знаком с технологией регулировки скорости отсылки пакетов при помощи
токенов.
Сетевое устройство может передавать только один пакет в один момент
времени и только с определённой устройством скоростью. Программам
разделения канала остаётся только использовать эти параматры для
расчётов деления канала на полосы требуемого размера. Таким образом
текущая и максимальная скорости передачи данных не могут быть
урегулированы мгновенно. Они рассчитываются на основании усреднённых
данных о скорости отсылки пакетов. В действительности передача данных
с учётом заданных свойств классов происходит следующим образом:
устройству передаётся некоторое количество пакетов одного класса
(которые отправляются в сеть со скоростью, определённой устройством
т.е. максимально возможной), после чего некоторое время обрабатываются
пакеты остальных классов. Параметры класса burst и cburst определяют
количество данных, которое можно передать на максимальной скорости
(скорости оборудования), после чего переключиться на обслуживание
других классов.
Чем меньше параметр cburst (в идеале он должен быть равен размеру
одного пакета), тем лучше он сглаживает ускорение передачи данных,
чтобы скорость передачи не превысила величину ceil. Так же действует
параметр peakrate' дисциплины TBF.
Если параметр burst родительского класса меньше чем у какого-нибудь из
потомков, можно ожидать, что передача данных этого класса иногда будет
останавливаться (так как дочерний класс пытается создать большее
ускорение, чем позволено родительскому). HTB помнит о неудачной
попытке ускорения в течение минуты.
Возникает вопрос: для чего нужно регулировать ускорение? Дело в том,
что это дешёвый и простой способ улучшить время отклика на
переполненных каналах. К резким колебания склонен, к примеру,
веб-трафик. Вы запрашиваете страницу, создавая ускорение, потом
читаете её. За время чтения "накапливается энергия" для следующего
ускорения.
Параметры burst и cburst всегда должны быть не меньше, чем такие же
параметры дочерних классов.
На диаграмме можно увидеть что происходит, если в предыдущем примере
установить параметр 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 и cburst для всех классов.
Интервал срабатывания таймера равен 10ms для архитектуры i386 и 1ms
для Alpha. Минимальное эффективное ускорение рассчитывается по формуле
(максимальная скорость)*(интервал таймера). Например чтобы обеспечить
скорость 10 mbit на компьютере i386 параметр burst должен быть равен 12kb.
Заниженная величина burst приведёт к тому, что данные будут
передаваться на скорости ниже запрошенной параметром rate. Последние
версии tc по умолчанию самостоятельно рассчитывают и устанавливают
минимальное значение burst.
6. Распределение с учётом приоритетов
Рассмотрим два аспекта распределения каналов с учётом приоритетов.
Первый касается распределения излишков канала между потомками одного
класса. Как было сказано выше, излишки канала распределяются
пропорционально гарантированным классам скоростям. Возьмём
конфигурацию из главы 3 (иерархические классы без ограничения
ускорений и максимальной скорости), установим приоритет 0 (высший) для
класса SMTP (зелёный график) и приоритет 1 для остальных. Можно
увидеть, что класс SMTP получает все излишки канала. Действует правило
согласно которому при распределении излишков канала класс с наивысшим
приоритетом обслуживается в первую очередь. Тем не менее установки
гарантированной (rate) и максимальной (seil) скоростей продолжают
действовать.
Второй аспект касается общей задержки пакета. Скорость передачи данных
ethernet-устройству слишком высока, чтобы заметить и оценить задержку
передачи пакета. Есть простое решение задачи создания наблюдаемой
задержки. Создадим корневой класс HTB с гарантированной скоростью
меньше 100 kbps, к нему присоединим ещё одну дисциплину 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
Создание дисциплины-потомка HTB означает не то же самое, что создание
дочерних классов HTB. При наличии единственной дисциплины,
присоединённой к устройству, классифицированный трафик отправляется в
сеть со скоростью и задержками, определёнными оборудованием (грубо
говоря, "со скоростью света"). При "вложении" дисциплин корневая
дисциплина фактически является моделью устройства, к которому
присоединена дочерняя, таким образом условия отправки данных дочерней
дисциплиной регулируются программно со всеми вытекающими
последствиями.
Симулятор будет генерировать трафик обоих классов со скоростью 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)
Первые три дисциплины относятся к краевым классам. Их мы рассматривать
не будем, поскольку статистика 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
Для экономии места статистика классов 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' при вызове tc qdisc add
... htb. Это прведёт к выводу большого количества отладочной
информации класса kern с приоритетом debug. Возможно в этом случае
лучше добавить строчку
kern.debug -/var/log/debug
в файл /etc/syslog.conf. После чего вышлите по электронной почте лог,
сжатый bzip2 (не больше 10MB) вместе с описанием проблемы и моментом
её возникновения.