MQTT'de üretimde başınızı ağrıtan şey QoS seviyeleri değil, oturum yönetimi ve hata teşhisi. MQTT 3.1.1 ile yazılmış bir istemci bağlantı kurulamadığında size yalnızca "bağlanamadı" diyor; MQTT 5.0 0x87 Not authorized ile 0x9F Connection rate exceeded arasındaki farkı söylüyor ve bu fark, saatlerce süren bir hata avını beş dakikaya indiriyor. Bu yazı MQTT 5.0'ın gerçekten değiştirdiği şeyleri, filo ölçeğinde konu (topic) tasarımını ve Sparkplug B'nin ne zaman gerekli olduğunu anlatıyor. Referans: OASIS Standard "MQTT Version 5.0", onay tarihi 7 Mart 2019.
Hâlâ 3.1.1 kullanıyorsanız kaybettiğiniz şey teşhis yeteneği
MQTT 5.0'ın spesifikasyonundaki Appendix C, 3.1.1'e göre eklenenleri sayıyor. Operasyonel değeri en yüksek olanlar:
- Tüm ACK'lerde Reason Code. CONNACK, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, UNSUBACK, DISCONNECT ve AUTH artık bir nedenle geliyor. Spesifikasyondaki tanım: Reason Code tek baytlık işaretsiz bir değer; 0x80'in altı başarı, 0x80 ve üzeri başarısızlık, normal başarı kodu 0.
- Sunucu da DISCONNECT gönderebiliyor. 3.1.1'de sunucu bağlantıyı sessizce kesiyordu; şimdi sebebini söylüyor.
- Session Expiry Interval. "Clean Session" bayrağı yerine Clean Start + oturum ömrü. 3.1.1'deki
Clean Session=1davranışının karşılığı,Clean Start=1ve Session Expiry Interval=0. - Topic Alias. Uzun konu adlarını iki baytlık takma adlarla değiştiriyor. Hücresel bağlantıdaki cihazlarda ölçülebilir bir bant genişliği kazancı.
- Shared Subscriptions. Tüketici tarafında yatay ölçekleme; aşağıda ayrı başlık.
- Will Delay Interval, Message Expiry, Request/Response (Response Topic + Correlation Data), Flow control, User properties, Maximum Packet Size, gelişmiş kimlik doğrulama (challenge/response ve yeniden kimlik doğrulama).
Teşhis için ezberlenecek CONNACK kodları:
| Kod | Ad | Sahada ne demek |
|---|---|---|
0x84 | Unsupported Protocol Version | İstemci 5.0 istiyor, broker 3.1.1 modunda |
0x85 | Client Identifier not valid | ClientId üretme mantığınız broker kuralına uymuyor |
0x86 | Bad User Name or Password | Kimlik bilgisi yanlış |
0x87 | Not authorized | Kimlik doğru, yetki yok. ACL kuralına bakın. |
0x95 | Packet too large | Payload broker sınırını aşıyor |
0x97 | Quota exceeded | Broker kotası doldu |
0x9A / 0x9B | Retain not supported / QoS not supported | Broker yapılandırması istemci beklentisini karşılamıyor |
0x9F | Connection rate exceeded | Bağlantı fırtınası. Yeniden bağlanmada jitter yok. |
Son satır, saha kurulumlarında en sık görülen ve en yanlış teşhis edilen durum. Bir kesinti sonrası tüm cihazlar aynı anda yeniden bağlanmaya çalışıyor, broker kapasitesi dolduğu için reddediyor, cihazlar hemen tekrar deniyor ve döngü kapanmıyor. Çözüm broker'ı büyütmek değil, istemci tarafında üstel geri çekilme ve rastgele gecikme.
QoS seçimi: spesifikasyonun tanımları ve gerçek maliyet
Spesifikasyondaki tanımlar kısa ve net. QoS 0, "at most once": mesajlar işletim ortamının en iyi çabasıyla teslim ediliyor, kayıp olabilir. QoS 1, "at least once": mesajın varması garanti ama kopya oluşabilir. QoS 2, "exactly once": tam olarak bir kez teslim garantisi (bkz. OASIS MQTT Version 5.0).
Pratik karar kuralı:
- QoS 0 — yüksek frekanslı telemetri (saniyede bir sıcaklık okuması). Bir örneğin kaybolması önemsiz; iki katı ağ trafiği ödemeye değmez.
- QoS 1 — durum değişiklikleri, alarmlar, komut sonuçları. Varsayılan seçiminiz bu olsun.
- QoS 2 — yalnızca kopyanın gerçekten zarar verdiği yerlerde (fatura üreten sayaç okuması, ödeme tetikleyen olay). Dört aşamalı el sıkışma pahalı.
QoS 1 kullanıyorsanız tüketici tarafı idempotent olmak zorunda. Bu, spesifikasyonun size bıraktığı iş: mesajın içine bir olay kimliği koyun ve tüketici aynı kimliği ikinci kez gördüğünde işlemi tekrarlamasın. QoS 2'ye geçerek bu sorumluluktan kurtulmaya çalışmak, pahalı ve kırılgan bir çözüm.
Konu hiyerarşisi: sonradan değiştirilemeyen tek karar
Konu şeması, kurulduktan sonra binlerce cihazın bellenimine gömülmüş oluyor. Üç kural:
- Genelden özele.
fabrika/hat3/pres02/sicaklikdoğru;sicaklik/pres02/hat3yanlış. Abonelik joker karakterleri soldan sağa çalışıyor; yanlış sıra, "üçüncü hattın tümü" gibi doğal bir aboneliği imkânsız kılıyor. - Kimlik konuda, veri yükte. Cihaz kimliği ve ölçüm tipi konuda olsun; değer, birim ve zaman damgası yükte. Yükün içine cihaz kimliği koymak zorunda kalıyorsanız konu şemanız eksik.
- Komut ve telemetri ayrı ağaçlarda.
.../telemetri/...ve.../komut/...ayrımı, ACL kurallarını tek satırda yazılabilir kılıyor: cihaz kendi telemetri dalına yazar, kendi komut dalını okur. Bu ayrım yoksa yetkilendirme kuralları cihaz sayısıyla birlikte büyüyor.
Retained mesajı durum için kullanın, olay için değil. "Pres 02 şu anda çalışıyor" retained olmalı; yeni bağlanan bir panel anında güncel durumu görsün. "Pres 02 alarm verdi" retained olmamalı; aksi hâlde her yeni abone, haftalar önce kapanmış bir alarmı yeni gibi görüyor.
Shared Subscriptions: tüketici tarafını ölçeklemenin doğru yolu
Tek bir tüketici süreç, on binlerce cihazın telemetrisini işleyemiyor. Spesifikasyonun §4.8.2'si bunun için paylaşımlı abonelik tanımlıyor. Biçim: $share/{ShareName}/{filter}.
Normatif kurallar net: konu filtresi $share/ ile başlamak zorunda ve en az bir karakter uzunluğunda bir ShareName içermeli. ShareName /, + veya # karakterlerini içeremez ve ardından bir / gelmeli; onu da bir konu filtresi izlemeli. Eşleşen her mesaj gruptaki tek bir oturuma gidiyor (bkz. OASIS MQTT Version 5.0, §4.8.2 Shared Subscriptions).
Bir davranış ayrıntısı üretimde şaşırtıyor: paylaşımlı aboneliğe ilk abone olan oturuma retained mesaj gönderilmiyor, sonradan katılanlara da gönderilmiyor. Yani tüketici havuzunuz başlarken mevcut durumu retained mesajlardan öğrenemiyor; durumu ayrı bir yerden okumanız gerekiyor.
# Python, paho-mqtt ile MQTT 5.0 ve paylaşımlı abonelik
import paho.mqtt.client as mqtt
from paho.mqtt.client import CallbackAPIVersion
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code != 0:
print(f"CONNACK reason code: {int(reason_code)} - {reason_code}")
return
# Grup adı "isleyiciler"; aynı adla baglanan her surec yuku paylasir
client.subscribe("$share/isleyiciler/fabrika/+/+/telemetri", qos=1)
def on_message(client, userdata, msg):
# QoS 1: kopya gelebilir, tuketici idempotent olmali
handle(msg.topic, msg.payload)
client = mqtt.Client(CallbackAPIVersion.VERSION2, protocol=mqtt.MQTTv5)
client.on_connect = on_connect
client.on_message = on_message
client.tls_set() # 8883 uzerinden TLS
client.username_pw_set("isleyici", "…")
client.connect("broker.ornek.local", 8883, keepalive=60)
client.loop_forever()
Sparkplug B: ne zaman gerekli, ne zaman fazla
MQTT bir taşıma protokolü; yükün içinde ne olduğunu tanımlamıyor. Endüstriyel kurulumlarda bu boşluğu Eclipse Sparkplug dolduruyor. Güncel sürüm Sparkplug 3.0.0 (16 Kasım 2022). Amacı spesifikasyonun kendi ifadesiyle, genel olarak endüstriyel IoT sektörüne uygulanabilen ama özellikle gerçek zamanlı SCADA ve HMI çözümlerinin gereksinimlerini karşılayan bir MQTT konu ad alanı, yük biçimi ve oturum durumu yönetimi tanımlamak.
Konu yapısı normatif olarak sabit: namespace/group_id/message_type/edge_node_id/[device_id]. Sparkplug B için ilk token spBv1.0 olmak zorunda. Mesaj tipleri de sabit: NBIRTH, NDEATH, DBIRTH, DDEATH, NDATA, DDATA, NCMD, DCMD ve STATE. Yük kodlaması zorunlu olarak Protocol Buffers (bkz. Eclipse Sparkplug Specification 3.0.0).
Doğum ve ölüm sertifikası mekanizması, Sparkplug'ı kendi başınıza kuramayacağınız şeyi veriyor: bir düğüm bağlandığında yayımladığı NBIRTH mesajı, o düğümün sunduğu tüm metrikleri ve tiplerini ilan ediyor. Yani SCADA tarafı, cihaz listesini elle yapılandırmadan keşfedebiliyor.
Gerekli olduğu yer: birden fazla üretici kaynaklı PLC ve gateway'in aynı brokera bağlandığı, SCADA tarafının otomatik keşif beklediği fabrika kurulumları. Fazla olduğu yer: tek bir ürünün sahaya dağıttığı kendi cihazları. Orada yükünüzü kendiniz tanımlamak, Protobuf şeması ve Sparkplug durum makinesi bakımından daha ucuz.
Güvenlik: kimlik cihaz başına olmalı
MQTT güvenliğinde en pahalı hata, tüm filoya tek bir kullanıcı adı ve parola dağıtmak. Bir cihaz ele geçirildiğinde tüm filo tehlikeye giriyor ve tek bir cihazı iptal edemiyorsunuz.
Bulut sağlayıcılarının modelleri de bu yönde. AWS IoT Core'un dokümanı üç kimlik türü sayıyor: X.509 istemci sertifikaları, IAM kullanıcı/grup/rolleri ve Amazon Cognito kimlikleri; tipik olarak cihazlar X.509 sertifikası kullanıyor (bkz. AWS IoT Core, Client authentication). Azure IoT Hub tarafında paylaşımlı erişim imzası (SAS) yolu kullanıldığında cihaz kaydı sırasında iki simetrik anahtar üretiliyor ve MQTT CONNECT paketinde ClientId cihaz kimliği, Username {iothubhostname}/{deviceId}, Password ise SAS token oluyor.
Kendi brokerınızı işletiyorsanız izlenecek yol: cihaz başına X.509 sertifikası, karşılıklı TLS (mTLS), ve sertifika Common Name'ine bağlı ACL kuralları. Sertifika iptal listesini işletmek ek bir yük ama tek cihazı iptal edebilmenin başka yolu yok. Güncel açık kaynak broker sürümleri: Mosquitto 2.1.2, EMQX 6.3.1, HiveMQ Community Edition 2026.5.
Sırada ne var
Mevcut kurulumunuzda iki şeye bakın. Birincisi: istemciler MQTT 5.0 ile mi bağlanıyor? Değilse, yalnızca protokol sürümünü yükseltmek bile hata teşhisini dönüştürüyor.
İkincisi: yeniden bağlanma mantığınızda rastgele gecikme var mı? Yoksa bir sonraki kesintide 0x9F ile karşılaşacaksınız.
IoT tarafındaki güvenlik başlıkları için IoT güvenliği yazısına, fabrika kurulumları için IoT ve akıllı sistemler sayfasına bakabilirsiniz. Üretim tarafındaki sektörel yaklaşımımız üretim ve imalat sayfasında; kapsam çıkarmak için proje fiyat hesaplama aracını kullanabilirsiniz.
Protokol ayrıntıları 25 Eylül 2026 tarihinde OASIS Standard "MQTT Version 5.0" (onay 07.03.2019) metninden doğrulanmıştır: QoS tanımları, Reason Code tablosu, Last Will alanları, §4.8.2 Shared Subscriptions ve Appendix C. Sparkplug bilgileri Eclipse Sparkplug Specification 3.0.0 (16.11.2022) metnindendir. Broker sürümleri mosquitto.org, EMQX ve HiveMQ Community Edition yayın sayfalarından; kimlik doğrulama modelleri AWS IoT Core ve Azure IoT Hub dokümanlarından alınmıştır. Kod örneği kendi ortamınıza uyarlanmadan üretime alınmamalıdır.