`

СПЕЦИАЛЬНЫЕ
ПАРТНЕРЫ
ПРОЕКТА

Архив номеров

Как изменилось финансирование ИТ-направления в вашей организации?

Best CIO

Определение наиболее профессиональных ИТ-управленцев, лидеров и экспертов в своих отраслях

Человек года

Кто внес наибольший вклад в развитие украинского ИТ-рынка.

Продукт года

Награды «Продукт года» еженедельника «Компьютерное обозрение» за наиболее выдающиеся ИТ-товары

 

Андрей Зубинский

Протокол MQTT (и немного из опыта реализации). Часть 2.

+33
голоса

Основа стандарта протокола MQTT – конечно же, описание форматов пакетов и обмена ими, на которые отображены все механизмы MQTT.

На пакетном уровне протокол прост и отличается сравнительно небольшой «перегрузкой» служебной информацией. Это важное свойство, обусловливающее неоспоримые и доказанные достоинства MQTT (о чём чуть позже).

Общую структуру всех пакетов можно отобразить следующей «схемой» (в дальнейшем буду позволять себе довольно свободное обращение с терминологией MQTT, придерживаясь в переводах терминов правила «лучше понятнее, чем дословно», но для сохранения «синхронности» с текстом стандарта всегда буду приводить оригинальные термины):

Протокол MQTT (и немного из опыта реализации). Часть 2.

Предваряя деталировку, сразу замечу довольно интересную организацию пакетов протокола, заметную на логическом уровне. Во-первых, иногда, казалось бы, обязательные и потому относящиеся к «системным» данные, вынесены разработчиками протокола в «полезную нагрузку», Payload. Во-вторых, без окончательного разбора общего для всех пакетов заголовка (Fixed Header) невозможно определить полный размер пакета, и, соответственно, выделить память для его обработки.

Общий для всех пакетов заголовок (Fixed Header), несмотря на слово «fixed» в стандартном названии, имеет переменную длину от двух до пяти октетов (или, если больше нравится, байтов).

Самый первый байт – фактически селектор типа пакета (всего текущая версия стандарта описывает 14 типов пакетов) и, по совместительству, контейнер для управляющих флагов всего одного ключевого типа пакета – Publish. Этот байт в старшем ниббле содержит код типа пакета, в младшем – флаги для пакетов Publish и, иногда, предустановленные для некоторых типов пакетов биты.

Второй и последующие (до четырёх суммарно) байты содержат не слишком хитро закодированную длину (в байтах) всех прочих элементов пакета. Механизм кодирования длины основан на следующем принципе – если старший бит байта установлен в единицу, 7 битов этого байта записываются в результирующее 32-битовое (точнее, 28-битовое) слово в 7-битовую позицию, соответствующую номеру байта в последовательности. Следовательно, полная длина максимального MQTT-пакета, соответствующего стандарту, будет равна 5 (байтов Fixed Header) + значение 28-битового слова со всеми единичными битами, или 228-1, что в сумме даёт примерно 255 мегабайтов.

Я не случайно обращаю внимание на эту вроде как незначительную деталь стандарта. Потому что есть реальность, с её реальными вычислителями. И в этой реальности с одной стороны есть MQTT-клиент, который может исполняться малым встраиваемым вычислителем (речь идёт не о микроконтроллерах, а о малых одноплатных машинах масштаба Raspberry Pi и меньше), и, с другой стороны, есть MQTT-сервер, обслуживающий массу клиентов. Из-за pub-симметричности протокола инжектированные в общую систему пакеты большого размера могут одновременно создать чудовищную нагрузку на сервер и фактически уничтожить тонкие MQTT-клиенты. Поэтому, хоть стандарт и не устанавливает никаких дополнительных ограничений на размер пакета, при реализации что клиента, что сервера разумно ограничиться максимально возможными двумя байтами кодирования длины пакета, иными словами – в серверной реализации декодера пакетов MQTT строго проверять старший бит третьего байта общего для всех пакетов заголовка (если этот байт, конечно, есть) любого MQTT-пакета, и если в нём появляется единица, немедленно закрывать сетевое соединение с клиентом, от которого поступил pub-пакет такого размера. И, само собой, «бить тревогу». Да, это нарушение стандарта, но оно ограничивает возможную максимальную длину пакетов примерно 16 килобайтами, что для реальных M2M и IoT применений более чем достаточно, даже для неспешных обновлений большого объёма firmware (передачей фрагментов), и спасает систему от потенциальных опасностей (не обязательно умышленных).

Не случайно уделяю внимание Fixed Header. Во-первых, из всего множества возможных значений первого байта (256 значений) стандарт определяет допустимыми всего 24 (до этой цифры можно добраться тщательным изучением стандарта). Во-вторых, разбор Fixed Header любого MQTT-пакета даёт необходимую для дальнейшего разбора полного пакета информацию, критически важную для любой системы с динамическим выделением памяти (оставшуюся длину пакета в октетах/байтах, об этом было выше). В-третьих, стандарт чётко указывает зарезервированный характер некоторых полей Fixed Header, из-за чего реализация даже простого кода разбора всего-то нескольких байтов пакета становится потенциально расширяемой. В-четвёртых, реализация разбора пакетов любого протокола должна быть настолько быстрой, насколько возможно. Вообще, здраво считать разбор пакетов hard realtime задачей (что усложняет кодирование, но в данном случае – на уровне количества касаний клавиатуры), такое требование точно не повредит. Ну и, наконец, в-пятых, при реализации «боевого» MQTT-сервера для больших нагрузок после почти мгновенного разбора Fixed Header можно «отправлять» остаток пакета в отдельный поток исполнения.

Флаги фундаментального для любой реализующей pub-sub системы пакета (или, если больше нравится, команды) Publish критически важны для понимания работы MQTT в целом. Всего их три – флаг признака повторной отправки (DUP), двухбитовый флаг «качества обслуживания» механизмов доставки (QoS, Quality of Service) и флаг признака «актуальной до обновления публикации» (RET, Retain).
QoS, «качество обслуживания» MQTT, определяет степень доверия к работе механизма транспорта. К сожалению, текст стандарта даёт трудно точно переводимые лаконичные определения трёх возможных уровней степени доверия, поэтому придётся прибегнуть к многословию.

Основной уровень степени доверия – QoS 0, «at most onсe delivery», лучше всего объяснить военным термином «выстрелил и забыл». Клиент или сервер «выстреливает» Pub-команду (пакет) и забывает об этом, никаких дополнительных проверок, ничего вообще. Основным уровнем его можно считать потому, что протокол требует канала с гарантированной доставкой данных с сохранением порядка и проч.

Уровень степени доверия «с подтверждением», QoS 1, «at least once delivery» требует подтверждения получения передачей специального пакета каждого получения Pub-команды (пакета).

И, наконец, самый высокий уровень степени доверия – QoS 2, «exactly once delivery», с двойным подтверждением от обеих сторон симметричного pub-канала (ещё раз напоминаю, что и клиент, и сервер, передают/принимают Publish-пакеты).

Кажущаяся избыточность уровней качества обслуживания связана с тем, что в MQTT каждый Pub-пакет имеет собственный идентификатор (назначаемый отправляющей стороной) и за ним скрыт временный ресурс клиента или сервера – собственно данные. Пакет с определённым идентификатором может быть как подтверждённо доставлен, так и подтверждённо могут быть освобождены данные, передаваемые этим пакетом и представляющие собой ресурс, в терминах MQTT это две разные операции.

В некоторых реальных реализациях протокола разработчики ограничиваются самым простым QoS 0, и, как показывает практика, на принципиальную работоспособность систем на основе усечённых реализаций это в большом количестве случаев не влияет.

Флаг DUP, обозначающий повторную отправку пакета, интуитивно понятен, как и понятно, что при QoS 0, «выстрелил и забыл», никаких повторных отправок быть не может. А вот флаг RET, актуальной до обновления публикации, как раз относится к публикуемым данным, как к ресурсу. Если этот флаг установлен в 1, сервер MQTT будет «удерживать» полученные данные и публиковать их любым новым клиентам, подписавшимся на топик этих данных. Для «неудерживаемых» (RET=0) данных новые подписчики не получат ничего вообще до очередной публикации данных.

Из-за развитой системы QoS о MQTT распространены urban legends, утверждающие «перетяжелённость» и «неэффективность» протокола. В реальном мире свойства MQTT прекрасно и тщательно изучены (теоретически и на практике), особенно для мобильных систем с батарейным питанием и очень энергоёмкими каналами связи (например, для смартфонов).  Можно найти большие работы, посвящённые изучению эффективности MQTT, но можно ограничиться и сравнительно короткими результатами практиков, например, профилированием работающего MQTT-клиента на смартфоне под управлением ОС Android. Естественно, это не «ответы на все вопросы», но получить достаточную для оценочного уровня информацию из результатов можно. В этой таблице видны затраты энергии (не в джоулях, а в «расходе заряда батареи»), «пропускную способность», выраженную в количестве отправленных в час pub-пакетов с payload всего 1 байт, и, соответственно, «расход батареи» на отправку одного пакета, всё это для разных уровней качества обслуживания:

Протокол MQTT (и немного из опыта реализации). Часть 2.

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

TBC

Откланиваюсь

+33
голоса

Напечатать Отправить другу

Читайте также

 
 
IDC
Реклама

  •  Home  •  Рынок  •  ИТ-директор  •  CloudComputing  •  Hard  •  Soft  •  Сети  •  Безопасность  •  Наука  •  IoT