Sorunun arka planı:
9. ayın 1. günü, saat 17:12 civarında saha uygulama personelinden bazı cihazların düzgün yükseltilemediği, konsoldan gönderilen komutları alamadığı gibi sorunlara ilişkin şikâyet aldık. Bir çalışma arkadaşımız control projesinin(bundan sonra control olarak anılacak)günlüklerini inceledi ve control'ün cihazların bildirdiği gölge bilgilerini almadığını, bu nedenle komut göndermediğini gördü. control projesi cihazlarla doğrudan iletişim kurar; cihazların bildirdiği bilgilere göre yükseltme, günlükleri gönderme ve benzeri işlemlere ilişkin komutlar ve yapılandırma bilgileri gönderir. IoT devreye alınmadan önce control, cihazın güncel bilgilerini almak için kalp atışı bildirimlerine dayanıyordu; IoT devreye alındıktan sonra ise cihaz gölge bilgilerine dayanıyor
cihazın güncel bilgilerini almak için control, cihazın gölge bilgilerine abone olur; ancak bu bilgiler gölge servisi (kısaca IoT) tarafından iletilir. control, cihazın gölge bildirimlerini doğrudan almaz. Akışın ayrıntıları için aşağıdaki iki şekle bakın:
Uygulama(control)cihaz durumunu alır

Uygulama(control)cihaza komut gönderir

control'ün gölge mesajlarını alamadığını öğrenir öğrenmez, mesaj olup olmadığını görmek için rabbitmq konsoluna gittim ve iki şeyi kontrol ettim: 1. Cihaz mesaj bildirmiş miydi 2. rabbitmq normal çalışıyor muydu, aşağıdaki şekil 1 ve şekil 2, o sırada rabbitmq konsolundan alınan iki ekran görüntüsüdür,Şekil1 Cihazın mesaj gönderdiği açıkça doğrulanabiliyor, ancak birçok mesaj unacked durumunda (onaylanmamış)(Bu, mesajın tüketiciye zaten teslim edildiğini, yalnızca tüketicinin ack göndermediğini gösterir; teorik olarak bir süre beklendikten sonra normale); unacked iletilerin tam olarak hangi kuyrukta biriktiğini görmek için bkz. Şekil 2,“spacebridgeiot-shadow” Tam da cihazın bildirdiği gölge bilgilerini almak için kullandığımız yerdi; mesajların hepsi kuyrukta biriktiğinden control'e iletilmemeleri de anlaşılırdı. Bir süre gözlemledikten sonra unacked sayısının 0 olduğunu, ancak total sayısının pek değişmediğini gördük. Sanki unacked mesajlar teslim edilmeyi beklemek üzere mesaj kuyruğuna geri dönmüş gibiydi. Gerçekten de birkaç dakika sonra yine çok sayıda unacked mesaj gördük; birkaç dakika daha geçince bu unacked mesajlar yeniden kuyruğa döndü. control tarafına hâlâ mesaj ulaşmıyordu. O sırada IoT tarafındaki günlükleri kontrol edince hiç gölge mesajı gelmediğini fark ettik. rabbitmq konsolunda “spacebridgeiot-shadow” Bu kuyruğun artık hiç tüketicisi kalmamıştı,örneğin Şekil 3 şeklinde gösterilmiştir. Bu sırada rabbitmq günlüklerine bakıldığında gerçekten hata mesajları olduğu görülür, örneğin Görsel 4 görüldüğü üzere, rabbitmq bağlantıyı kendisi kapattı.
Şekil 1:rabbitmq genel görünümü

Şekil 2:rabbitmq kuyruk istatistikleri grafiği

Şekil 3:spacebridgeiot-shadow genel bakış

Şekil 4:rabbitmq hata bilgisi

Geçici çözüm:
O sırada çok sayıda şikâyet gelmiş olduğundan, oldukça sert bir çözüm olan “biriken mesajları silme” yöntemini kullandık; silince sistem gerçekten normale döndü(Not:Canlı ortamdaki sorunlar mümkün olan en kısa sürede çözülmelidir,günlükleri analiz edip sonra düzenli adımlarla çözüm üretmeye zaman yoktur,hızlı olmak gerekir)。

Sorunu yerel ortamda yeniden oluşturma:
1.10.200.41.166 ortamındaki rabbitmq'nun “mirrorTestQueue” kuyruğunda çok sayıda mesaj biriktirin(en az on binlerce)
2.mirrorTestQueue tüketicilerini durdurun, mesajlar biriktikten sonra yeniden başlatın
3.Birikme tamamlandıktan sonra tüketiciyi yeniden başlatın
Beklediğimiz gibi, birkaç saniye içinde tüketiciye binlerce mesaj gönderildi. Bu durum birkaç dakika sürdükten sonra rabbitmq, tüketiciyle arasındaki bağlantıyı kendiliğinden kapattı; o anda konsolda kuyruğun tüketicisi görünmüyordu. Tüketicimizde otomatik kurtarma ayarlı olduğundan, bir süre sonra yeniden otomatik olarak bağlanıyordu,
Ancak kısa süre sonra bağlantı yine kesiliyordu; üretim ortamında karşılaştığımız sorunla hemen hemen aynıydı. Peki bu soruna tam olarak ne yol açıyordu? Açıkçası o sırada aklıma pek bir şey gelmiyordu; internette epey aradım ama beni gerçekten tatmin eden bir yanıt da bulamadım(O sırada sorunun özünü kavrayamamıştım; aradığım anahtar kelimeler fazla geneldi),daha sonra
TCP katmanında bir sorun olabileceğini düşündük,bu yüzden bir ipucu bulabilir miyiz diye paket yakalamaya karar verdik。Gerçekten de,şans yüzümüze güldü,sözü uzatmadan görsele geçelim。


13:06:25.643428 öncesinde rabbitmq tüketiciye sürekli mesaj göndermeye devam ediyordu; 13:06:25.643428 anında tüketicinin TCP penceresi tamamen dolmaya başladı. Bu durum yaklaşık 30 saniye sürdükten sonra rabbitmq bağlantıyı kendisi kesti(bir RST paketi gönderdi). Ardından tüketici yeniden bağlandı, ancak pencere tekrar tamamen doldu ve yaklaşık 30 saniye sonra bağlantı yine kesildi。


Oldukça düzenli görünüyor; her seferinde 30s sürüyor. Yapılandırılabilir bir parametre gibi görünüyor. Kabaca şöyle özetlenebilir: “tcp full window, sunucunun bağlantıyı kendiliğinden RST ile sıfırlamasına yol açıyor ve bu düzenli aralıklarla gerçekleşiyor”
Bu kez arama anahtar kelimelerini değiştirince buldum: Yanıt,rabbitmq'da şu adlı bir parametre vardırtcp_listen_options.send_timeout yazma zaman aşımını denetleyen bir parametredir; yazma zaman aşımına uğradığında TCP'nin RST'sini tetikler(https://github.com/rabbitmq/rabbitmq-java-client/issues/341),biraz değiştirip nasıl sonuç verdiğine bakalım:
1. Yazma zaman aşımını 10s olarak değiştirin
tcp_listen_options.send_timeout = 10000
2.Paketleri yakalayıp işe yarayıp yaramadığını kontrol edin


Pencerenin dolmasından bağlantının kapanmasına kadar yaklaşık 10s geçer.
Gözlenen durumun değerlendirilmesi:
rabbitmq tüketicisinde prefetch ayarlanmadığından, rabbitmq tüketiciye tek seferde aşırı sayıda mesaj iletti. Bu da tüketicinin tcp penceresinin tamamen dolmasına ve ardından rabbitmq'nun tcp_listen_options.send_timeout ayarının tetiklenmesine yol açtı. Bu yazma zaman aşımı belirli bir eşiğe ulaştığında rabbitmq, tüketicinin tcp bağlantısını keser.
Nihai çözüm:
Daha önce mesajları silmek mecbur kaldığımız bir çözümdü,sorunu çözmüş olsa da fazla sertti,bununla başa çıkmak için zarif bir çözüm bulmamız gerekiyor,TCP penceresini dolduran şey tüketiciye çok fazla mesaj itilmesiyse,alma hızı üzerinde çalışmalıyız,rabbitmq'ya bağlanırken ona çok fazla mesaj göndermemesini söylememiz yeterli。(Aşağıdaki metin şuradan alınmıştır: https://blog.csdn.net/james_searcher/article/details/70308565)rabbitmq'da prefetch adlı bir özellik vardır; prefetch, tek bir tüketicinin alabileceği en fazla onaylanmamış mesaj sayısını belirtir. Bunu nasıl anlamalıyız? MQ, her consumer için boyutu prefetch olan bir tampon oluşturur. Her mesaj alındığında MQ, mesajı tampona, ardından istemciye iletir. Bir ack mesajı alındığında (consumer baseack komutunu gönderdiğinde) MQ tamponda bir yer boşaltır ve yeni bir mesaj ekler. Ancak tampon doluysa MQ engellenmiş duruma geçer. Daha somut anlatmak gerekirse prefetch değerinin 10 olarak ayarlandığını ve iki consumer bulunduğunu varsayalım. Yani her consumer, her seferinde queue'dan önceden 10 mesaj alıp tüketilmeyi beklemek üzere yerel tamponda tutar. Aynı anda bu channel'ın unacked sayısı 20 olur. Rabbit'in teslim sırası ise önce consumer1'e 10 mesajı tamamlamak, ardından consumer2'ye 10 mesaj teslim etmektir. Bu sırada teslim edilmesi gereken yeni bir mesaj varsa önce channel'ın unacked sayısının 20 olup olmadığı kontrol edilir; öyleyse mesaj consumer'a teslim edilmez ve queue'da kalır. Daha sonra consumer'lardan biri bir mesajı ack ile onayladığında unacked sayısı 19 olur; Rabbit de hangi consumer'ın unacked sayısının 10 değerinden az olduğunu kontrol eder ve mesajı o consumer'a teslim eder.
Kodda bunun somut karşılığı şöyledir

Bu değeri nasıl değerlendirmeli?rabbitmq'nun resmî bir makalesi bunu çok iyi açıklıyor, bu yüzden ayrıntıya girmeyeceğim. Sistemimizde şu anda 20 olarak ayarlanmış durumda.
https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/
Son söz:
rabbitmq kullanımında hâlâ başlangıç seviyesindeyiz,kullanmadan önce rabbitmq API belgelerini mutlaka daha çok okumalı,sık kullanılan API'lerin kullanımına alışmalı,yerel ortamda bol bol deneme yapmalıyız。