Kas yra RabbitMQ ir kodėl jis reikalingas
Kai kuriate sudėtingas sistemas, anksčiau ar vėliau susidursite su problema – kaip efektyviai sujungti skirtingas aplikacijas, kad jos galėtų keistis informacija neblokuodamos viena kitos? Čia ir ateina į pagalbą žinučių brokeriai, o RabbitMQ yra vienas populiariausių pasirinkimų.
Įsivaizduokite situaciją: jūsų e-komercijos platformoje vartotojas užsako prekę. Sistema turi išsiųsti patvirtinimo el. laišką, atnaujinti inventorių, inicijuoti mokėjimo procesą, informuoti sandėlį apie siuntą ir dar kelias kitas operacijas. Jei visa tai vykdytumėte sinchroniškai, vartotojas turėtų laukti, kol visos šios operacijos bus baigtos. Su RabbitMQ galite paprasčiausiai „išmesti” žinutę į eilę ir leisti kitoms sistemoms ją apdoroti savo tempu.
RabbitMQ veikia kaip tarpininkas tarp aplikacijų – jis priima žinutes iš siuntėjų (producers) ir pristato jas gavėjams (consumers). Tai leidžia sistemoms būti atskirtoms viena nuo kitos, o tai reiškia didesnį lankstumą, lengvesnį skalabilumą ir atsparumą gedimams.
Pagrindinės RabbitMQ koncepcijos, kurias privalote suprasti
Prieš pradedant dirbti su RabbitMQ, reikia susipažinti su keliais pagrindiniais terminais. Neišsigąskite – tai paprasčiau nei atrodo.
Producer – tai jūsų aplikacijos dalis, kuri siunčia žinutes. Pavyzdžiui, tai gali būti API endpoint’as, kuris priima užsakymą ir siunčia žinutę apie jį į RabbitMQ.
Queue (eilė) – tai vieta, kur saugomos žinutės, kol kas nors jas paims apdoroti. Galite turėti daugybę skirtingų eilių skirtingiems tikslams – vieną el. laiškams, kitą mokėjimams ir t.t.
Consumer – tai aplikacija ar servisas, kuris klauso eilės ir apdoroja žinutes. Vienas consumer gali klausyti kelių eilių, o viena eilė gali turėti kelis consumers.
Exchange – tai maršrutizatorius, kuris nusprendžia, į kurią eilę nukreipti gautą žinutę. Yra keletas exchange tipų: direct, topic, fanout ir headers. Kiekvienas iš jų veikia skirtingai ir tinka skirtingoms situacijoms.
Binding – tai ryšys tarp exchange ir eilės. Jis apibrėžia taisykles, pagal kurias žinutės keliauja iš exchange į konkrečią eilę.
Praktiškai tai atrodo taip: producer siunčia žinutę į exchange su tam tikru routing key. Exchange pagal bindings nusprendžia, į kurią eilę (ar eiles) nukreipti žinutę. Consumer’iai, kurie klauso tų eilių, paima žinutes ir jas apdoroja.
Kaip įdiegti ir paleisti RabbitMQ savo kompiuteryje
Pradėti dirbti su RabbitMQ yra gana paprasta. Paprasčiausias būdas – naudoti Docker. Jei turite įdiegtą Docker, tiesiog paleiskite:
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
Šis komanda parsisiunčia RabbitMQ image su management plugin’u ir paleidžia jį. Portas 5672 yra skirtas pačiam RabbitMQ, o 15672 – web valdymo sąsajai.
Po kelių sekundžių galite atidaryti naršyklę ir eiti į http://localhost:15672. Prisijungimo duomenys pagal nutylėjimą: username guest, password guest. Čia rasite grafinę sąsają, kur galite stebėti eiles, exchange’us, connections ir daug kitų dalykų.
Jei nenorite naudoti Docker, galite įdiegti RabbitMQ tiesiogiai į savo operacinę sistemą. Linux sistemose tai paprastai daroma per package manager’į, pavyzdžiui Ubuntu:
sudo apt-get install rabbitmq-server
Windows ir macOS naudotojams rekomenduoju atsisiųsti oficialų installer’į iš RabbitMQ svetainės.
Pirmasis praktinis pavyzdys su Python
Dabar pereikime prie kodo. Naudosiu Python, nes jis paprastas ir aiškus, bet RabbitMQ turi bibliotekų beveik visoms populiarioms programavimo kalboms.
Pirmiausia įdiekite pika biblioteką:
pip install pika
Sukurkime paprastą producer’į, kuris siunčia žinutes:
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
channel.basic_publish(exchange='',
routing_key='hello',
body='Labas, RabbitMQ!')
print(" [x] Išsiųsta 'Labas, RabbitMQ!'")
connection.close()
Šis kodas prisijungia prie RabbitMQ, sukuria eilę pavadinimu „hello” (jei jos dar nėra) ir išsiunčia žinutę. Atkreipkite dėmesį, kad naudojame tuščią exchange – tai reiškia, kad naudojame default exchange, kuris tiesiogiai siunčia žinutes į eilę pagal routing_key.
Dabar sukurkime consumer’į:
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
print(f" [x] Gauta {body.decode()}")
channel.basic_consume(queue='hello',
auto_ack=True,
on_message_callback=callback)
print(' [*] Laukiama žinučių. Spauskite CTRL+C išėjimui')
channel.start_consuming()
Consumer’is prisijungia prie tos pačios eilės ir laukia žinučių. Kai gauna žinutę, iškviečiama callback funkcija, kuri ją apdoroja.
Exchange tipai ir kada juos naudoti
Vienas iš svarbiausių dalykų dirbant su RabbitMQ – suprasti, kada naudoti kokį exchange tipą. Kiekvienas iš jų turi savo nišą.
Direct Exchange veikia pagal tikslų routing key atitikimą. Jei žinutė siunčiama su routing key „payment.processed”, ji bus nukreipta tik į tas eiles, kurios susietos su būtent tokiu routing key. Tai puikiai tinka, kai žinote tiksliai, kur norite siųsti žinutes. Pavyzdžiui, skirtingi log level’iai gali būti nukreipti į skirtingas eiles.
Topic Exchange leidžia naudoti wildcards routing key. Simbolis * atitinka vieną žodį, o # – nulį ar daugiau žodžių. Pavyzdžiui, routing key user.*.created atitiktų user.admin.created ir user.customer.created. Tai neįtikėtinai lankstus variantas, kai reikia sudėtingesnės maršrutizacijos logikos.
Fanout Exchange yra paprasčiausias – jis tiesiog siunčia žinutę į visas susietas eiles, ignoruodamas routing key. Tai idealu, kai reikia broadcast funkcionalumo. Pavyzdžiui, kai vartotojas atnaujina savo profilį, galite norėti informuoti kelis skirtingus servisus apie tai.
Headers Exchange maršrutizuoja žinutes pagal header’ius, o ne routing key. Naudojamas retai, bet gali būti naudingas specifinėse situacijose, kai routing logika yra sudėtinga ir negali būti išreikšta per routing key.
Praktiškai, daugeliu atvejų naudosite Direct arba Topic exchange. Fanout tinka event broadcasting, o Headers exchange – tik labai specifinėms situacijoms.
Patikimumas ir žinučių patvirtinimas
Vienas iš svarbiausių klausimų dirbant su žinučių brokeriais – kaip užtikrinti, kad žinutės nedingtų? RabbitMQ turi kelis mechanizmus tam.
Message acknowledgments – tai būdas consumer’iui pasakyti RabbitMQ, kad žinutė buvo sėkmingai apdorota. Ankstesniame pavyzdyje naudojome auto_ack=True, kas reiškia, kad žinutė laikoma apdorota iš karto, kai tik consumer’is ją gauna. Tai pavojinga – jei consumer’is užstringa ar nutrūksta ryšys, žinutė prarandama.
Geriau naudoti manual acknowledgments:
def callback(ch, method, properties, body):
print(f" [x] Apdorojama {body.decode()}")
# Čia vyksta žinutės apdorojimas
time.sleep(2) # Simuliuojame darbą
ch.basic_ack(delivery_tag=method.delivery_tag)
print(" [x] Baigta")
channel.basic_consume(queue='hello',
on_message_callback=callback)
Dabar žinutė bus ištrinta iš eilės tik tada, kai consumer’is aiškiai patvirtins jos apdorojimą su basic_ack. Jei consumer’is nutrūks prieš tai, RabbitMQ automatiškai persiųs žinutę kitam consumer’iui.
Message durability – pagal nutylėjimą, jei RabbitMQ serveris nutrūksta, visos žinutės prarandamos. Norėdami to išvengti, turite padaryti dvi dalis:
Pirmiausia, eilė turi būti deklaruota kaip durable:
channel.queue_declare(queue='hello', durable=True)
Antra, žinutės turi būti pažymėtos kaip persistent:
channel.basic_publish(exchange='',
routing_key='hello',
body='Svarbi žinutė',
properties=pika.BasicProperties(
delivery_mode=2, # make message persistent
))
Atminkite, kad durability nėra 100% garantija – yra mažas laiko tarpas tarp žinutės gavimo ir jos įrašymo į diską. Jei reikia absoliutaus patikimumo, turėtumėte naudoti publisher confirms.
Apkrovos balansavimas ir skalabilumas
Vienas iš RabbitMQ privalumų – galimybė lengvai skaluoti sistemą. Jei vienas consumer’is nesuspėja apdoroti visų žinučių, tiesiog paleiskite daugiau consumer’ių tai pačiai eilei.
RabbitMQ automatiškai paskirstys žinutes tarp visų consumer’ių naudodamas round-robin algoritmą. Bet yra viena problema – jei kai kurios žinutės reikalauja daugiau laiko apdoroti nei kitos, vienas consumer’is gali būti perkrautas, kol kitas laukia naujų žinučių.
Sprendimas – naudoti prefetch_count:
channel.basic_qos(prefetch_count=1)
Tai nurodo RabbitMQ nesiųsti naujos žinutės consumer’iui, kol jis nepatvirtino ankstesnės. Taip apkrova bus paskirstyta tolygiau.
Jei jūsų sistema auga, galite naudoti RabbitMQ clustering – keli RabbitMQ serveriai dirba kartu kaip vienas loginis vienetas. Tai suteikia ne tik didesnį našumą, bet ir atsparumą gedimams.
Dar vienas svarbus dalykas – priority queues. Jei kai kurios žinutės yra svarbesnės už kitas, galite nustatyti eilės prioritetą:
channel.queue_declare(queue='priority_queue',
arguments={'x-max-priority': 10})
channel.basic_publish(exchange='',
routing_key='priority_queue',
body='Svarbi žinutė',
properties=pika.BasicProperties(priority=9))
Žinutės su didesniu priority bus apdorotos pirma.
Dažniausios klaidos ir kaip jų išvengti
Per metus darbo su RabbitMQ esu matęs daug kartojančių klaidų. Štai kelios, kurias turėtumėte žinoti.
Užmiršti uždaryti connections – kiekvienas connection naudoja resursus. Jei nuolat kuriate naujus connections ir jų neuždarote, greitai išsemsite serverio resursus. Naudokite context managers arba užtikrinkite, kad connections būtų uždaromi finally blokuose.
Neapdoroti klaidų – kas nutiks, jei žinutės apdorojimas nepavyks? Jei tiesiog išmesit exception, žinutė bus grąžinta į eilę ir bandoma apdoroti iš naujo. Be ribų. Tai gali sukelti begalinį ciklą. Naudokite dead letter exchanges – eiles, į kurias nukreipiamos žinutės, kurių nepavyko apdoroti po N bandymų.
Per didelės žinutės – RabbitMQ nėra skirtas dideliems failams persiųsti. Jei reikia perduoti didelį failą, geriau išsaugokite jį S3 ar panašioje saugykloje ir per RabbitMQ siųskite tik nuorodą.
Ignoruoti monitoringą – RabbitMQ management UI yra puikus, bet gamyboje reikia tinkamo monitoringo. Stebėkite eilių ilgius, consumer’ių skaičių, message rates. Jei eilė auga neribotai, kažkas negerai.
Naudoti auto_ack gamyboje – jau minėjau tai anksčiau, bet verta pakartoti. Auto acknowledgments yra patogu testuojant, bet gamyboje beveik visada norite manual ack su proper error handling.
Kada RabbitMQ yra geriausias pasirinkimas (ir kada ne)
RabbitMQ yra puikus įrankis, bet ne visoms situacijoms. Jis puikiai tinka, kai reikia patikimo žinučių pristatymo, sudėtingos maršrutizacijos logikos, ir kai žinutės nėra itin didelės. Tai idealus pasirinkimas mikroservisų architektūrose, kur skirtingi servisai turi komunikuoti asinchroniškai.
Tačiau jei jums reikia streaming funkcionalumo su dideliais duomenų srautais ir gebėjimu „atsukti laiką atgal” – geriau žiūrėkite į Apache Kafka. Jei reikia paprastos pub/sub funkcionalumo be daug komplikacijų – Redis Pub/Sub gali būti paprastesnis variantas. Jei dirbate AWS aplinkoje, Amazon SQS gali būti lengviau administruoti.
RabbitMQ stiprybė – lankstumas ir patikimumas. Jis turi daug funkcijų, kurios leidžia tiksliai sukonfigūruoti, kaip žinutės keliauja per sistemą. Bet šis lankstumas ateina su sudėtingumu – reikia laiko išmokti visas koncepcijas ir best practices.
Praktiškai, daugeliui projektų RabbitMQ yra puikus pasirinkimas. Jis brandus, gerai dokumentuotas, turi didelę community ir bibliotekų visoms populiarioms kalboms. Tai ne naujausias ar „šauniausias” įrankis, bet tai įrankis, kuris tiesiog veikia ir veikia gerai. Kartais tai svarbiausia.
