Fan-in, IoT'nin gündelik biçimidir: binlerce cihazın her biri biraz telemetri publish eder ve tek bir backend hepsini saklamak veya işlemek için devices/+/telemetry topic'ine abone olur. Tek tek bakıldığında cihazlar sessizdir; birlikte ise her şeyi tek bir abonelik, tek bir TCP bağlantısı ve tek bir süreç üzerinden gönderirler. Bir fan-in testi, bu tüketicinin bir backlog oluşmadan önce kaç cihazı kaldırabildiğini ve kaldıramadığında nasıl ölçekleneceğini ortaya çıkarır.
Bu rehber emqtt-bench ile bir filo simüle eder, tüketici tarafında backlog ve gecikmeyi bir Python betiğiyle ölçer ve shared subscription'ların yükü nasıl dağıttığını gösterir. Ters desen fan-out testi yazısında ele alınır.
Fan-in darboğazları nerede saklanır
Broker genellikle fan-in girişini rahatça kaldırır; çok sayıda küçük mesajı tek bir subscriber'a yönlendirmek ucuzdur. Sorun daha ileridedir:
| Darboğaz | Belirti | Tipik çözüm |
|---|---|---|
| Tek tüketici bağlantısı | Cihazlar publish etmeye devam ederken alım hızı sabitleniyor | Birden fazla tüketici arasında shared subscription |
| MQTT callback'i içinde işleme | Alım hızı = işleme hızı; ağ thread'i bloke | Süreç içi kuyruk ve worker thread'leri |
| Yavaş hedef (veritabanı, HTTP API) | Backlog sürekli büyüyor | Toplu yazma, daha fazla worker veya Kafka gibi bir buffer |
| Broker'ın istemci başına kuyruk sınırı | Mesajlar düşürülüyor veya tüketici kopuyor | Daha hızlı tüketici; sınırı yalnızca tampon olarak artırın |
| Inflight penceresi (QoS 1) | Throughput gidiş-dönüşlerle sınırlı | Daha büyük Receive Maximum veya daha fazla bağlantı |
Son ikisi broker'ın tek bir oturum için koyduğu sınırlardır; örneğin Mosquitto'daki max_queued_messages ve max_inflight_messages. Tüketici geride kaldığında broker onun için mesajları kuyruğa alır; kuyruk dolduğunda broker'a bağlı olarak mesaj düşürmeye veya bağlantıyı kesmeye başlar. Farklı broker'ların nasıl davrandığı için yavaş tüketiciler yazısına bakın.
Adım 1: filoyu simüle edin
emqtt-bench tek bir makineden binlerce cihazı canlandırabilir. Topic yapısının üretimle aynı olması için her istemciye %i (istemci indeksi) ile kendi topic'ini verin. -I aralığı istemci başına ve milisaniye cinsindendir; yani -I 5000 ile 5.000 cihazın her biri 5 saniyede bir mesaj gönderir, toplamda 1.000 msg/s:
docker run --rm -it --ulimit nofile=65535:65535 emqx/emqtt-bench pub \
-h broker.connectmq.com -p 1883 \
-c 5000 -i 5 -I 5000 \
-t 'testmqtt/fanin/%i/telemetry' -s 200 -q 1 \
-u "$MQTT_USER" -P "$MQTT_PASS"Yükü artırmak için bağlantı eklemek yerine -I değerini düşürün (5000 → 2000 → 1000 ms, 2.500 ve 5.000 msg/s verir): test ettiğiniz şey bağlantı sayısı değil, tüketicidir. Başlamadan önce wildcard'ınızın cihaz topic'leriyle gerçekten eşleştiğini topic matcher ile kontrol edin; filtredeki bir yazım hatası, hiçbir şey almayan bir tüketiciyle tamamen aynı görünür.
Adım 2: backlog'unu gösteren bir tüketici
Üretim tarzı bir tüketici on_message içinde asla yavaş iş yapmamalıdır: bu, ağ döngüsünü bloke eder; döngü socket'i okumayı bırakır ve backlog'u göremeyeceğiniz broker'a iter. Bu paho-mqtt 2.x tüketicisi her mesajı bir kuyruğa verir, mesaj başına simüle edilmiş bir maliyetle worker thread'lerinde işler ve her saniye alım hızını, işleme hızını ve backlog'u yazdırır:
import json, queue, sys, threading, time, uuid
import paho.mqtt.client as mqtt
HOST, PORT = "broker.connectmq.com", 1883
FILTER = "testmqtt/fanin/+/telemetry"
SHARED = "--shared" in sys.argv
TOPIC = f"$share/ingest/{FILTER}" if SHARED else FILTER
WORK_MS = 0.5 # mesaj basina simule edilen maliyet (parse + DB insert)
WORKERS = 4
inbox = queue.Queue(maxsize=100_000)
counts = {"rx": 0, "done": 0}
lags = []
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(TOPIC, qos=1)
def on_message(client, userdata, msg):
counts["rx"] += 1
inbox.put(msg.payload) # ag thread'inde asla yavas is yapmayin
def worker():
while True:
payload = inbox.get()
if payload[:1] == b"{": # zaman damgali sonda mesajlari
try:
lags.append((time.time() - json.loads(payload)["t"]) * 1000)
except (ValueError, KeyError):
pass
time.sleep(WORK_MS / 1000)
counts["done"] += 1
for _ in range(WORKERS):
threading.Thread(target=worker, daemon=True).start()
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"ingest-{uuid.uuid4().hex[:6]}", protocol=mqtt.MQTTv5)
c.on_connect, c.on_message = on_connect, on_message
c.connect(HOST, PORT)
c.loop_start()
while True:
time.sleep(1)
l = sorted(lags); lags.clear()
p99 = f"{l[int(len(l) * 0.99) - 1]:.0f} ms" if l else "-"
print(f"rx {counts['rx']:>6}/s done {counts['done']:>6}/s "
f"backlog {inbox.qsize():>6} probe lag p99 {p99}")
counts.update(rx=0, done=0)emqtt-bench payload'ları rastgele baytlardan oluşur, dolayısıyla zaman damgası taşıyamaz. Uçtan uca gecikmeyi ölçmek için, tüketiciyle aynı makinede (ikisi de aynı saati kullansın diye) aynı topic ağacına zaman damgalı JSON publish eden küçük bir sonda çalıştırın:
import json, time
import paho.mqtt.client as mqtt
p = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
p.connect("broker.connectmq.com", 1883)
p.loop_start()
while True:
p.publish("testmqtt/fanin/probe/telemetry", json.dumps({"t": time.time()}), qos=1)
time.sleep(0.1)Sayıları okumak
Filoyu her basamakta çalıştırın ve tüketici çıktısını birkaç dakika izleyin:
- rx ≈ done, backlog sıfıra yakın, gecikme sabit: tüketici yetişiyor. Sonraki basamağa geçin.
- rx ≈ giriş ama done < rx, backlog büyüyor: ağ tarafı iyi, darboğaz işleme. Worker ekleyin, yazmaları toplu yapın veya hedefi hızlandırın.
- rx < giriş, backlog küçük: sınır tek bağlantı veya broker tarafındaki oturum (inflight penceresi, istemci başına kuyruk). Shared subscription tam burada işe yarar.
- Gecikme sürekli artıyor: henüz hiçbir şey kaybolmasa bile sürdürülebilir hızın üzerindesiniz; kuyruk yalnızca uzuyor.
- Tüketici yük altında kopuyor: broker istemci başına sınırına ulaştı ya da ağ thread'i bloke olduğu için keep-alive süresi doldu.
Adım 3: shared subscription ile ölçeklendirin
Sınır tek bir tüketici bağlantısıysa, aynı shared subscription grubunda birden fazla tüketici başlatın. Broker bu durumda her mesajı $share/ingest/... grubunun yalnızca bir üyesine teslim eder:
python ingest.py --shared # terminal 1
python ingest.py --shared # terminal 2 (ve devami)Daha önce başarısız olan basamağı tekrarlayın. Doğrulanacaklar:
- Tüketicilerdeki
donetoplamı filonun girişine eşit: hiçbir mesaj kaybolmuyor. - Kopya yok:
$shareolmadan her tüketici her mesajı alır ve her şeyi iki kez işlersiniz. Payload'ınız benzersiz ID taşıyorsa bunları sayın. - Dengeli dağılım: broker'lar farklı stratejiler kullanır (round robin, rastgele, istemci başına sticky). Yükün kabaca dengeli olduğunu kontrol edin.
- Failover: çalışma sırasında bir tüketiciyi sonlandırın; diğerleri onun payını üstlenmelidir. Sonlandırılan tüketiciye yolda olan mesajlar QoS'a ve broker'a bağlı olarak yeniden teslim edilebilir veya kaybolabilir.
- Sıra: cihaz başına sıra grup genelinde garanti edilmez. Önemliyse tasarımınızı buna göre yapın.
Semantik için shared subscriptions yazısına, dağılım ve failover testlerinin ayrıntıları için shared subscription testi yazısına bakın.
Tek sürecin ötesinde
Darboğaz hedefse ve ani yükler kaçınılmazsa (bir kesintiden sonra yeniden bağlanan filo, biriktirdiği çok sayıda veriyi bir anda gönderir), MQTT ile depolama arasına kalıcı bir buffer koyun. MQTT'yi Kafka'ya köprülemek yaygın bir desendir: MQTT'den Kafka'ya yazısına bakın. Ardından ani yük senaryosunu açıkça test edin: filo çalışırken tüketiciyi beş dakika durdurun, yeniden başlatın ve backlog'un erimesinin ne kadar sürdüğünü ölçün. emqtt-bench'in sunduğundan daha gerçekçi cihaz davranışı için gerçek payload'larınızla bir cihaz simülatörü kullanın.
Büyük bir çalıştırmadan önce online MQTT istemcisinde yapacağınız hızlı bir kontrol (testmqtt/fanin/# topic'ine abone olup mesajların gelişini izlemek), filonun düşündüğünüz yere publish ettiğini doğrular.
Fan-in nadiren broker'ı bozar; bozduğu şey tüketicidir. Ham alım hızları yerine backlog'u ve gecikmeyi ölçün, yavaş işleri ağ thread'inden uzak tutun ve tek bağlantı yetmediğinde shared subscription'larla ölçeklendirin.
Sıkça sorulan sorular
MQTT'de fan-in nedir?
Fan-in, çok sayıda publisher'ın tek bir tüketicinin (veya küçük bir grubun) abone olduğu topic'lere gönderim yapmasıdır; tipik olarak binlerce cihaz device/+/telemetry topic'lerine telemetri publish eder ve tek bir backend servisi hepsini wildcard aboneliğiyle okur.
Yetişemeyen bir MQTT tüketicisini nasıl ölçeklendiririm?
Önce süreç içi bir kuyruk ve worker thread'leriyle alma ile işlemeyi birbirinden ayırın. Tek bağlantı yine yetmiyorsa, broker'ın mesajları aralarında dağıtması için birden fazla tüketici örneğini bir shared subscription ($share/grup/filtre) ile çalıştırın.
Shared subscription'lar mesaj sırasını korur mu?
Grup genelinde korumaz. Bir cihazın mesajları farklı tüketiciler tarafından işlenebilir; sıra yalnızca bağlantı başına garanti edilir. Cihaz başına sıra önemliyse, bir publisher'ı tek bir tüketiciye sabitleyen bir broker stratejisi kullanın veya sonraki aşamada sıra numarasına göre yeniden sıralayın.


