5. ünite / 10
MQTT güvenilirliği: QoS, kalıcı mesaj, son vasiyet
QoS 0/1/2 el sıkışmaları ve yinelenen teslim, kalıcı (retained) mesaj, son vasiyet ve doğum mesajı, keepalive ve kalıcı oturum.
Bu ünitenin sonunda
- QoS 0, 1 ve 2'nin paket dizisini ve teslim garantisini açıklamak
- Kayıplı bağlantıda yinelenen teslimin neden oluştuğunu ve nasıl zararsız hale getirileceğini göstermek
- Kalıcı mesaj, son vasiyet ve doğum mesajıyla cihaz durumunu yayımlamak
- Keepalive ve kalıcı oturum ayarlarını senaryoya göre seçmek
Ders notları
Wi-Fi paket kaybeder, cihaz uyur, broker yeniden başlar. MQTT, mesajın ne kadar "kesin" teslim edileceğini her yayında seçmene izin verir: QoS (hizmet kalitesi). Garanti arttıkça paket sayısı ve gecikme de artar.
Üç düzey
- QoS 0, en çok bir kez: tek PUBLISH paketi, yanıt yok. Kaybolursa kaybolur. Sık gelen sıcaklık ölçümü için çoğu zaman yeterlidir: bir sonraki ölçüm zaten gelir.
- QoS 1, en az bir kez: alıcı PUBACK ile onaylar. Onay gelmezse gönderen paketi DUP bayrağıyla yeniden yollar. PUBACK kaybolmuşsa alıcı mesajı iki kez alır.
- QoS 2, tam bir kez: PUBLISH, PUBREC, PUBREL, PUBCOMP. Alıcı paket kimliğini saklar ve mesajı uygulamaya yalnız bir kez verir. En yavaşı ve en pahalısı.
QoS, yayımlayan ile broker arasında ve broker ile abone arasında ayrı ayrı uygulanır. Aboneye giden QoS, yayın QoS'u ile abonelik QoS'unun küçüğüdür. Kütüphane sınırlarını da bil: PubSubClient yalnız QoS 0 yayımlar, QoS 0 ya da 1 abone olur; umqtt.simple QoS 0 ve 1 destekler.
Kalıcı mesaj
Retain bayrağıyla yayımlanan mesajı broker o konunun "son değeri" olarak saklar. Yeni abone olan pano, bir sonraki yayını beklemeden son değeri hemen alır. Konuya boş yükle kalıcı yayın yapmak saklanan mesajı siler. Komutları kalıcı yayımlamak tehlikelidir: cihaz her yeniden bağlandığında eski komutu tekrar alır.
Son vasiyet ve doğum mesajı
Son vasiyet (LWT), istemcinin CONNECT paketinde broker'a bıraktığı bir mesajdır. Bağlantı DISCONNECT gönderilmeden koparsa (güç gitti, Wi-Fi düştü, keepalive aşıldı) broker vasiyeti yayımlar. Yaygın desen: bağlanınca sera/01/durum konusuna kalıcı online, vasiyet olarak kalıcı offline. Böylece pano her cihazın canlı olup olmadığını her an bilir.
Keepalive ve oturum
İstemci keepalive süresince hiç paket göndermezse PINGREQ yollar. Broker, keepalive'ın 1,5 katı boyunca istemciden ses duymazsa bağlantıyı ölü sayar ve vasiyeti yayımlar. Kalıcı oturumda (clean session = false) broker, aynı kimlikli istemci çevrimdışıyken gelen QoS 1 ve 2 mesajları kuyrukta tutar ve geri bağlanınca teslim eder. Uyuyan cihazlar için kullanışlıdır ama broker belleği sınırlıdır.
Simülasyonlar
QoS 0, 1, 2: kayıplı bağlantıda teslim
Paket kaybı oranını artır ve aynı 20 mesajı üç QoS düzeyiyle gönder. Kaybolan, yinelenen ve teslim edilen mesajları, toplam paket sayısını ve her mesajın el sıkışma dizisini gör.
| QoS | Teslim | Kayıp | Yinelenen | Paket |
|---|---|---|---|---|
| 0 at most once | 16/20 | 4 | 0 | 20 |
| 1 at least once | 20/20 | 0 | 4 | 59 |
| 2 exactly once | 20/20 | 0 | 0 | 106 |
- #1gönderen → broker PUBLISH kayboldu
- #1gönderen → broker PUBLISH DUP kayboldu
- #1gönderen → broker PUBLISH DUP
- #1broker → gönderen PUBACK
- #2gönderen → broker PUBLISH
- #2broker → gönderen PUBACK
- #3gönderen → broker PUBLISH
- #3broker → gönderen PUBACK
- #4gönderen → broker PUBLISH
- #4broker → gönderen PUBACK
- #5gönderen → broker PUBLISH
- #5broker → gönderen PUBACK kayboldu
- #5gönderen → broker PUBLISH DUP
- #5broker → gönderen PUBACK
- #6gönderen → broker PUBLISH kayboldu
- #6gönderen → broker PUBLISH DUP
- #6broker → gönderen PUBACK
Tarayıcıda MQTT broker'ı
Üç simüle cihaz ve bir pano aynı broker'a bağlı. Joker karakterli (+ ve #) abonelikler kur, mesaj yayımla, kalıcı (retained) mesajı ve bir cihazın kablosunu çekince yayımlanan son vasiyeti (LWT) izle.
sera/01/led/set Q1sera/02/fan/set Q1- –Bir şey yayımla ya da cihazlara telemetri gönderttir.
Kod
#include <WiFi.h>
#include <PubSubClient.h>
#include "secrets.h" // MQTT_HOST, MQTT_USER, MQTT_PASS
WiFiClient net;
PubSubClient mqtt(net);
const char* STATUS = "sera/01/durum";
bool mqttConnect() {
mqtt.setKeepAlive(30); // broker 45 s sessizlikten sonra ölü sayar
// id, kullanıcı, parola, vasiyet konusu, QoS, retain, vasiyet, cleanSession
bool ok = mqtt.connect("sera-01", MQTT_USER, MQTT_PASS, STATUS, 1, true, "offline", false);
if (!ok) return false;
mqtt.publish(STATUS, "online", true); // doğum mesajı: kalıcı, vasiyetin tersi
mqtt.subscribe("sera/01/+/set", 1); // PubSubClient: abonelik QoS 0/1, yayın yalnız QoS 0
return true;
}
void goToSleep() {
mqtt.publish(STATUS, "sleeping", true);
mqtt.disconnect(); // DISCONNECT gönderildi: vasiyet yayımlanmaz
}
void setup() {
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(250);
mqtt.setServer(MQTT_HOST, 1883);
while (!mqttConnect()) delay(2000);
}
void loop() { mqtt.loop(); }from umqtt.simple import MQTTClient
from secrets import MQTT_HOST, MQTT_USER, MQTT_PASS
STATUS = b'sera/01/durum'
c = MQTTClient('sera-01', MQTT_HOST, user=MQTT_USER, password=MQTT_PASS, keepalive=30)
c.set_last_will(STATUS, b'offline', retain=True, qos=1) # CONNECT içinde gider
c.set_callback(lambda t, m: print(t, m))
c.connect(clean_session=False) # kalıcı oturum: çevrimdışıyken QoS 1 mesajlar bekletilir
c.publish(STATUS, b'online', retain=True, qos=1) # doğum mesajı
c.subscribe(b'sera/01/+/set', qos=1)
# QoS 1 "en az bir kez" demektir: komut iki kez gelebilir.
# Bu yüzden komutlar mutlak değer taşır (1/0), "çevir" değil.
while True:
c.wait_msg()Kendin dene
QoS simülasyonunda paket kaybını %0'dan %30'a çıkar ve aynı 20 mesajı QoS 0, 1 ve 2 ile gönder. Her düzeyde kaybolan, yinelenen ve teslim edilen mesaj sayısını ve toplam paket sayısını bir tabloya yaz. QoS 1'de yinelenen bir mesajın izini bul: hangi paket kaybolmuştu? Sonra broker simülasyonunda sera/01/durum konusuna kalıcı "online" yayımla, panoyu yeniden abone et ve mesajın hemen geldiğini gör; cihazın kablosunu çek ve vasiyetin geldiğini izle. Wokwi'de vasiyetli kodu çalıştırıp simülasyonu durdurarak aynı şeyi gerçek broker'da dene.
Sık yapılan hatalar
- QoS 1'in "tam bir kez" olduğunu sanmak: PUBACK kaybolunca aynı mesaj iki kez gelir, komutlar idempotent olmalı.
- Komutları retain bayrağıyla yayımlamak: cihaz her yeniden bağlandığında eski komutu yeniden uygular.
- Son vasiyeti kalıcı yapmamak: vasiyetten sonra abone olan pano cihazı hâlâ çevrimiçi sanar.
- Uykudan uyanan cihazda her seferinde farklı istemci kimliği kullanmak: kalıcı oturum ve kuyruk her seferinde kaybolur.
- Keepalive'ı derin uyku aralığından kısa tutmak: broker her uykuda vasiyeti yayımlar ve pano sürekli "offline" gösterir.
Yazılım mühendisliği bağlantısı
Dağıtık sistemlerde "tam bir kez" pahalıdır; pratikte en az bir kez + idempotent alıcı kullanılır.
- Her komuta bir kimlik ver ve alıcı son kimlikleri hatırlasın: yinelenen teslim ikinci kez uygulanmaz. 7. ünitenin eyleyici simülasyonu bunu ölçer.
- Durum konusu (
online/offline/sleeping) bir sağlık sinyalidir: izleme ve uyarı bunun üstüne kurulur. - Simülasyondaki
simulateQostohumlu bir rastgele sayı üreteci kullanır: aynı tohum aynı kayıpları verir, hata yeniden üretilebilir. Ağ testlerinde de belirlenimci tohum kullan.
Kendini sına
QoS 1 ile gönderilen bir mesajın PUBACK paketi kayboldu. Ne olur?
Cihaz QoS 2 ile yayımlıyor, pano aynı konuya QoS 1 ile abone. Panoya hangi QoS ile gelir?
Keepalive 20 s. İstemci tamamen sustu. Broker yaklaşık kaç saniye sonra bağlantıyı ölü sayar?
Bir konudaki kalıcı mesajı nasıl silersin?
Bu üniteden sonra bitirme projesinde
- Konu ağacı ve MQTT sözleşmesi: Konu ağacını, yük biçimlerini, QoS ve retain kararlarını ve son vasiyeti bir sayfalık sözleşme olarak yaz; broker simülasyonunda dene.