Короткий ответ — чтобы вы не скроллили зря
Берите MQTT, если ваше устройство отправляет данные чаще, чем раз в минуту, работает от батарейки, или вам нужна обратная связь с устройством (команды, OTA, алерты). MQTT экономит трафик в 5–10 раз, держит постоянное соединение и тратит на порядок меньше энергии на каждое сообщение.
Берите HTTP, если устройство отправляет данные раз в час и реже, у него стабильное питание от сети, а на стороне облака уже стоит REST API, который не хочется переписывать. HTTP проще отладить, проще защитить стандартными средствами, проще объяснить бэкенд-команде.
Теперь разберём, откуда эти цифры.
Два протокола — две философии
HTTP создавался для веба. Запрос — ответ. Stateless. Каждый раз заново: TCP-соединение, TLS-хэндшейк, заголовки, тело, закрытие. Это надёжно и понятно, но для IoT это как ездить на фуре за хлебом.
MQTT создавался для телеметрии. Publish/subscribe. Stateful. Устройство подключается к брокеру один раз, и дальше обменивается сообщениями по открытому каналу. Заголовок фиксированный — 2 байта. Да, два байта. Вся логика маршрутизации — на стороне брокера.
Разница в философии определяет всё остальное: трафик, латентность, батарейку, масштаб.
Критерий 1: размер пакетов и трафик
Мы замерили на реальном сценарии: ESP32 отправляет JSON с показаниями датчика температуры и влажности. Полезная нагрузка одинаковая — 42 байта.
HTTP POST (с TLS 1.3)
TCP SYN/SYN-ACK/ACK: ~120 байт
TLS 1.3 handshake: ~500 байт (session resumption)
HTTP headers: ~350 байт
Payload: 42 байта
HTTP response headers: ~250 байт
TCP FIN: ~40 байт
─────────────────────────────────────
Итого на одно сообщение: ~1 302 байт
MQTT PUBLISH (QoS 0, поверх TLS)
MQTT fixed header: 2 байта
Topic (sensors/t1/temp): 14 байт
Payload: 42 байта
─────────────────────────────────────
Итого на одно сообщение: 58 байт
TLS-хэндшейк и TCP-соединение при MQTT оплачиваются один раз — при подключении. После этого — чистые 58 байт на каждый publish.
Разница: 22.4x на одно сообщение. При 1 000 устройств, каждое из которых отправляет данные раз в 10 секунд, HTTP генерирует ~11.2 ГБ трафика в сутки. MQTT — ~500 МБ. Когда у вас сотовый канал и платите за мегабайты — это разница между «работает» и «разоряет».
Если устройство передаёт данные через LPWAN-канал, каждый байт вдвойне на счету — полоса пропускания LoRaWAN ограничена десятками килобит.
Критерий 2: латентность
Мы тестировали на ESP32-S3 (ESP-IDF v5.3) с брокером Mosquitto и nginx с REST API, оба в одном дата-центре.
| Сценарий | HTTP (мс) | MQTT QoS 0 (мс) | MQTT QoS 1 (мс) |
|---|---|---|---|
| Первое сообщение (cold start) | 340 | 280 | 310 |
| Повторное сообщение (warm) | 180 | 3.2 | 8.5 |
| Под нагрузкой (1000 msg/s) | 450 | 5.1 | 14 |
| С потерями 5% пакетов | 1 200 | 8.3 | 45 |
Первое подключение — примерно одинаково: и там, и там TLS-хэндшейк. Но на «тёплых» сообщениях MQTT быстрее HTTP в 56 раз. Под нагрузкой — в 88 раз.
180 мс для HTTP — это не плохо для веба. Но когда у вас IoT-система, которая управляет клапаном или сигнализацией, 180 мс на каждое срабатывание — это ощутимо. 3.2 мс — нет.
Критерий 3: энергопотребление и батарейка
Здесь MQTT побеждает с разгромным счётом. Причина простая: TCP+TLS хэндшейк — это самая энергоёмкая часть радиообмена. HTTP делает его на каждое сообщение. MQTT — один раз.
Замеры на ESP32 (Wi-Fi, 3.3 В, MQTT keepalive = 60 с):
| Операция | HTTP (мАч) | MQTT (мАч) |
|---|---|---|
| Одиночный publish | 0.085 | 0.004 |
| 100 publish за час | 8.5 | 0.4 + 0.15 (keepalive) |
| 1 000 publish за час | 85 | 4.0 + 0.15 |
Батарейка 2 000 мАч (типичный литий 18650):
- HTTP, 1 msg/min → ~16 дней
- MQTT QoS 0, 1 msg/min → ~310 дней
Двадцатикратная разница. Если ваше устройство живёт от батарейки — выбор очевиден. Если питается от сети — этот критерий неважен.
(Оговорка: keepalive-пинги MQTT тоже тратят энергию. При keepalive = 60 с это ~0.15 мАч/час. Если устройство отправляет данные раз в час, соотношение сжимается до 5x. Но оно всё равно в пользу MQTT.)
Для устройств на RTOS с глубоким сном этот критерий вообще определяющий: пробудились, отправили 58 байт по MQTT, уснули. Никакого хэндшейка.
Критерий 4: двусторонняя связь
Вот здесь — принципиальная разница в архитектуре.
HTTP — это запрос-ответ. Устройство спрашивает — сервер отвечает. Если сервер хочет отправить команду устройству, у него три варианта:
- Polling: устройство опрашивает сервер каждые N секунд. Работает, но убивает батарейку и загружает сеть.
- Long polling: устройство держит открытый HTTP-запрос. Работает, но ломает все таймауты и прокси.
- WebSocket: по сути, TCP-соединение поверх HTTP. Работает хорошо, но вы уже переизобрели MQTT (только без QoS, retained messages и wildcard-подписок).
MQTT — это pub/sub. Устройство подписывается на топик devices/device-42/commands. Сервер публикует команду — устройство получает её мгновенно. Никакого polling. Никаких костылей.
Для OTA-обновлений прошивок этот канал обратной связи критичен: вы должны иметь возможность в любой момент отправить устройству команду «проверь обновление» и получить подтверждение. MQTT для этого идеален.
QoS: гарантии доставки
MQTT предлагает три уровня:
- QoS 0 — at most once. Отправил и забыл. Потерялось — ладно.
- QoS 1 — at least once. Брокер подтверждает получение. Возможны дубликаты.
- QoS 2 — exactly once. Четырёхступенчатый хэндшейк. Гарантия, но дорого по трафику.
Для телеметрии обычно достаточно QoS 0 или QoS 1. Для команд управления — QoS 1 с идемпотентностью на стороне устройства.
HTTP тоже не гарантирует доставку «из коробки» — если запрос упал на полпути, клиент должен повторить его сам. Но здесь хотя бы есть HTTP-статусы (200, 500, 503), которые понятны всем.
Критерий 5: масштабируемость
При 1 000 устройств — оба протокола работают одинаково. При 100 000 — начинаются различия.
HTTP: каждое устройство создаёт новое TCP-соединение на каждое сообщение. При 100 000 устройств, каждое с частотой 1 msg/s, ваш сервер обрабатывает 100 000 TCP-хэндшейков в секунду. nginx справится, но TLS-хэндшейки сожрут CPU.
MQTT: 100 000 постоянных TCP-соединений. Да, это 100 000 открытых сокетов — но они легковесные. Брокер (EMQX, VerneMQ, HiveMQ) держит миллионы подключений на одном узле. EMQX заявляет 100M concurrent connections на кластере.
Ловушка MQTT: retained messages и wildcard-подписки. Если у каждого устройства свой топик и вы подписались на sensors/#, брокер будет рассылать каждое сообщение каждому подписчику. При неправильном проектировании топиков это взрывает трафик.
Проектирование IoT-платформ, способных обрабатывать такой масштаб — одна из задач, которую решают команды enterprise IoT-разработки.
Критерий 6: безопасность
HTTP — зрелый, проверенный протокол с огромной экосистемой безопасности. TLS, OAuth 2.0, API keys, JWT, rate limiting, WAF — всё это работает «из коробки» с любым HTTP-сервером.
MQTT тоже поддерживает TLS, но экосистема тоньше:
| Аспект | HTTP | MQTT |
|---|---|---|
| Шифрование транспорта | TLS 1.3 (стандарт) | TLS 1.3 (поддерживается) |
| Аутентификация | OAuth 2.0, JWT, mTLS | Username/password, mTLS, JWT (брокер-зависимо) |
| Авторизация | Стандартные middleware | ACL на уровне топиков |
| WAF / API Gateway | Nginx, Cloudflare, AWS ALB | Нет стандартного решения |
| Аудит | Access logs повсюду | Зависит от брокера |
Для корпоративных проектов с требованиями compliance (PCI DSS, 152-ФЗ) HTTP проще «закрыть» — есть готовые инструменты и практики. MQTT требует более ручной настройки безопасности.
Сначала я думал, что это минус MQTT. Потом пришло понимание: в IoT у вас два канала — устройство → облако (MQTT) и облако → пользователь (HTTP/REST). Безопасность HTTP применяется на втором канале. А на первом канале mTLS с клиентскими сертификатами для каждого устройства — надёжнее любого OAuth.
Критерий 7: отладка и мониторинг
HTTP выигрывает чисто практически. curl, Postman, браузер, tcpdump — любой инструмент понимает HTTP. Логи читаемые. Ошибки очевидные.
# Отладка HTTP — одна строка
curl -X POST https://api.example.com/telemetry \
-H "Content-Type: application/json" \
-d '{"temp": 23.5, "humidity": 61}'
С MQTT нужен клиент:
# Подписаться на все сообщения устройства
mosquitto_sub -h broker.example.com -t 'sensors/#' -v
# Отправить тестовое сообщение
mosquitto_pub -h broker.example.com -t 'sensors/test/temp' \
-m '{"temp": 23.5}'
Не сложно. Но порог входа для бэкенд-разработчика, который никогда не работал с MQTT — реальный. Если ваша команда пишет на Java/Kotlin и привыкла к REST, переход на MQTT потребует обучения.
На стороне сервера для MQTT нужен бэкенд-слой обработки сообщений, который конвертирует pub/sub в бизнес-логику. Spring Boot + Eclipse Paho или Spring Integration MQTT — рабочий вариант.
Критерий 8: поддержка на микроконтроллерах
Оба протокола хорошо поддерживаются в ESP-IDF — фреймворке для ESP32. Посмотрим на реальный код.
HTTP POST — отправка телеметрии (ESP-IDF)
#include "esp_http_client.h"
#include "cJSON.h"
esp_err_t send_telemetry_http(float temp, float humidity)
{
cJSON *root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "temp", temp);
cJSON_AddNumberToObject(root, "humidity", humidity);
char *json = cJSON_PrintUnformatted(root);
esp_http_client_config_t config = {
.url = "https://api.example.com/telemetry",
.method = HTTP_METHOD_POST,
.cert_pem = server_ca_pem, // CA-сертификат
.transport_type = HTTP_TRANSPORT_OVER_SSL,
.timeout_ms = 10000,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_http_client_set_header(client, "Content-Type", "application/json");
esp_http_client_set_header(client, "Authorization", "Bearer <token>");
esp_http_client_set_post_field(client, json, strlen(json));
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
int status = esp_http_client_get_status_code(client);
if (status != 200) {
ESP_LOGW("HTTP", "Server returned %d", status);
}
} else {
ESP_LOGE("HTTP", "Request failed: %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
cJSON_Delete(root);
free(json);
return err;
}
Каждый вызов — полный цикл: создание клиента, TLS-хэндшейк, отправка, разбор ответа, очистка. ~35 строк.
MQTT PUBLISH — отправка телеметрии (ESP-IDF)
#include "mqtt_client.h"
#include "cJSON.h"
static esp_mqtt_client_handle_t mqtt_client;
// Вызывается один раз при старте
void mqtt_init(void)
{
esp_mqtt_client_config_t config = {
.broker.address.uri = "mqtts://broker.example.com:8883",
.broker.verification.certificate = server_ca_pem,
.credentials.username = "device-42",
.credentials.authentication.password = "secret",
};
mqtt_client = esp_mqtt_client_init(&config);
esp_mqtt_client_start(mqtt_client);
}
// Вызывается на каждое измерение
esp_err_t send_telemetry_mqtt(float temp, float humidity)
{
cJSON *root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "temp", temp);
cJSON_AddNumberToObject(root, "humidity", humidity);
char *json = cJSON_PrintUnformatted(root);
int msg_id = esp_mqtt_client_publish(
mqtt_client,
"sensors/device-42/telemetry", // топик
json, 0, // данные
1, // QoS 1
0 // не retained
);
cJSON_Delete(root);
free(json);
return (msg_id >= 0) ? ESP_OK : ESP_FAIL;
}
Инициализация — отдельно, отправка — 10 строк. Никакого создания/уничтожения клиента. Никакого хэндшейка. Один вызов esp_mqtt_client_publish — и данные ушли.
Подписка на команды (только MQTT)
// Обработчик событий — подписка на команды
static void mqtt_event_handler(void *arg, esp_event_base_t base,
int32_t event_id, void *event_data)
{
esp_mqtt_event_handle_t event = event_data;
switch (event->event_id) {
case MQTT_EVENT_CONNECTED:
esp_mqtt_client_subscribe(mqtt_client,
"devices/device-42/commands", 1);
break;
case MQTT_EVENT_DATA:
ESP_LOGI("MQTT", "Topic: %.*s", event->topic_len, event->topic);
ESP_LOGI("MQTT", "Data: %.*s", event->data_len, event->data);
// Парсим команду и выполняем
handle_command(event->data, event->data_len);
break;
default:
break;
}
}
На HTTP для аналогичной функции вам нужен отдельный polling-цикл с таймером, обработкой ошибок, повторными запросами. Или WebSocket — а это по сути отдельный протокол.
Сводная таблица: MQTT vs HTTP для IoT
| Критерий | MQTT | HTTP | Победитель |
|---|---|---|---|
| Размер пакета (42 байта payload) | 58 байт | 1 302 байт | MQTT (22x) |
| Латентность (warm) | 3.2 мс | 180 мс | MQTT (56x) |
| Энергопотребление | 0.004 мАч/msg | 0.085 мАч/msg | MQTT (21x) |
| Двусторонняя связь | Нативная (pub/sub) | Polling/WebSocket | MQTT |
| Масштабируемость (100K+) | Постоянные соединения | Новые соединения | MQTT |
| Безопасность (экосистема) | mTLS, ACL | OAuth, JWT, WAF | HTTP |
| Простота отладки | mosquitto_sub/pub | curl, Postman | HTTP |
| Порог входа для команды | Средний | Низкий | HTTP |
| Поддержка в ESP-IDF | Нативная | Нативная | Ничья |
| Работа через прокси/firewall | Порт 8883 (может быть закрыт) | Порт 443 (всегда открыт) | HTTP |
Когда HTTP всё-таки лучше
Не каждый IoT-проект — это 100 000 датчиков на батарейках. Бывают сценарии, где HTTP — правильный выбор:
1. Редкая отправка данных (раз в час и реже). Если устройство просыпается раз в час, отправляет показания и засыпает — оверхед TCP+TLS хэндшейка амортизируется. MQTT keepalive в этом случае тратит больше энергии, чем экономит.
2. Стабильное питание + существующий REST API. У вас промышленный шлюз с питанием от сети, а на бэкенде — готовый REST API на Spring Boot. Переписывать его на MQTT-подписчик ради 10 шлюзов — бессмысленно.
3. Корпоративные firewall-ограничения. Порт 443 (HTTPS) открыт везде. Порт 8883 (MQTTS) — часто заблокирован. Да, MQTT можно пустить через WebSocket (порт 443), но это добавляет слой сложности.
4. Команда не знает MQTT. Если ваши бэкенд-разработчики никогда не работали с pub/sub и брокерами — время на обучение может перевесить технические преимущества MQTT. Особенно на проекте с дедлайном.
5. Одностороннее взаимодействие. Устройство только отправляет данные, сервер никогда не отправляет команды. Pub/sub не нужен — REST хватает.
Когда MQTT — единственный вариант
А вот сценарии, где HTTP просто не работает:
1. Батарейное питание + частая отправка. Если устройство живёт от батарейки и отправляет данные чаще раза в минуту — HTTP убьёт батарейку за дни.
2. Обратная связь с устройством. Команды, обновления прошивок, изменение конфигурации — всё это требует канала «облако → устройство». MQTT даёт его бесплатно.
3. Нестабильный канал. Мобильная сеть, спутник, LoRa-шлюз. MQTT с QoS 1/2 переживает обрывы и доставляет сообщения при восстановлении связи. HTTP — нет (без ручной реализации retry-логики).
4. Большой флот устройств (10 000+). При масштабе MQTT-брокер эффективнее, чем HTTP-сервер: меньше хэндшейков, меньше CPU на TLS, меньше трафика.
5. Real-time мониторинг. Дашборд, который показывает данные с датчиков в реальном времени. MQTT → WebSocket bridge → браузер. С HTTP пришлось бы опрашивать сервер.
Гибридный подход: MQTT + HTTP
На практике в большинстве IoT-платформ работают оба протокола. Типичная архитектура:
┌──────────────┐ MQTT ┌──────────┐ HTTP/REST ┌──────────┐
│ Устройства │─────────────────────▶│ Брокер │◀─────────────────────│ API │
│ (ESP32, │◀─────────────────────│ (EMQX) │─────────────────────▶│ Gateway │
│ STM32) │ telemetry + │ │ bridge / webhook │ (Spring │
│ │ commands │ │ │ Boot) │
└──────────────┘ └──────────┘ └──────────┘
│
▼
┌──────────┐
│ Web UI │
│ Mobile │
│ App │
└──────────┘
- Устройства ↔ Брокер: MQTT. Минимальный трафик, двусторонняя связь, обработка обрывов.
- Брокер → API: MQTT-to-HTTP bridge или webhook. Брокер пересылает сообщения в REST API.
- API ↔ Пользователь: HTTP/REST. Стандартные веб-инструменты, авторизация, документация.
Это не компромисс — это оптимальная архитектура. Каждый протокол используется там, где он силён.
Проектирование таких гибридных IoT-архитектур — задача, требующая опыта и в embedded-слое, и в бэкенд-части на Java/Kotlin. Ошибки в проектировании топиков, QoS, retention и bridge дорого обходятся на масштабе.
MQTT 5.0: что изменилось
MQTT 5.0 (финализирован в 2019, широко поддерживается с 2023) закрыл многие исторические проблемы:
- Reason codes — теперь брокер возвращает код ошибки (аналог HTTP-статусов). Раньше соединение просто рвалось без объяснений.
- Shared subscriptions — несколько подписчиков на один топик с балансировкой нагрузки. Раньше каждый получал копию.
- Message expiry — TTL для сообщений. Устройство, которое было офлайн сутки, не получит тысячу устаревших команд.
- Request/Response — паттерн запрос-ответ поверх pub/sub. Для RPC-подобных вызовов.
- Topic aliases — сжатие повторяющихся топиков до числового ID. Ещё меньше трафика.
ESP-IDF поддерживает MQTT 5.0 начиная с версии 5.1. Если вы начинаете новый проект — используйте только MQTT 5.0.
Дерево решений
Ответьте на пять вопросов:
1. Устройство на батарейке?
- Да → MQTT (если отправка чаще 1 раз/час) или HTTP (если реже)
- Нет → переходите к вопросу 2
2. Нужна обратная связь (команды, OTA)?
- Да → MQTT
- Нет → переходите к вопросу 3
3. Частота отправки данных?
- Чаще 1 раз/мин → MQTT
- Раз в минуту — раз в час → оба подходят, зависит от масштаба
- Реже 1 раз/час → HTTP
4. Количество устройств?
- Больше 10 000 → MQTT
- Меньше 1 000 → оба подходят
- 1 000–10 000 → зависит от частоты и трафика
5. У команды есть опыт с MQTT?
- Да → MQTT
- Нет, но проект долгосрочный → MQTT (окупится)
- Нет, дедлайн через месяц → HTTP
Если вы попали в «оба подходят» — берите MQTT. Потому что требования к IoT-системам всегда растут: сегодня 100 устройств, завтра 10 000; сегодня только телеметрия, завтра нужны команды. С MQTT вы готовы к росту.
Что мы используем в своих проектах
На наших IoT-проектах типичная конфигурация:
- Устройства: MQTT 5.0 (QoS 1 для телеметрии, QoS 1 для команд)
- Брокер: EMQX (Erlang, проверен на 5M+ подключений) или Mosquitto (для малых проектов до 10 000 устройств)
- Бэкенд: Spring Boot + Eclipse Paho + Kafka. MQTT-сообщения попадают в Kafka, оттуда — в микросервисы обработки
- API для клиентов: REST (Spring WebFlux) + WebSocket для real-time дашбордов
- Мониторинг: Prometheus + Grafana, метрики брокера + кастомные метрики устройств
Для промышленных проектов добавляем mTLS с индивидуальным клиентским сертификатом на каждое устройство, автоматический provisioning через REST API и безопасные OTA-обновления.
Чеклист перед выбором протокола
Прежде чем фиксировать архитектуру — пройдитесь по этому списку:
- Определите частоту отправки данных (раз/сек, раз/мин, раз/час)
- Определите размер полезной нагрузки (байты, килобайты)
- Ответьте: нужна ли обратная связь cloud → device?
- Оцените масштаб: сколько устройств через год? Через три года?
- Проверьте: есть ли ограничения по портам на целевых площадках?
- Оцените компетенции команды: кто будет поддерживать брокер?
- Рассчитайте стоимость трафика (особенно для сотовых каналов)
- Определите требования по безопасности и compliance
- Спланируйте мониторинг: как вы узнаете, что устройство отвалилось?
- Прототипируйте оба варианта на одном устройстве — замерьте сами
Последний пункт — самый важный. Чужие бенчмарки (включая наши) дают ориентир, но ваша сеть, ваш payload, ваш сервер — уникальны. Потратьте день на прототип, и вы будете уверены в решении.