A háttérinfrastruktúra méretezése a hiper-növekedés kezelésére a DoorDash-nál végzett munka sok izgalmas kihívásának egyike. 2019 közepén jelentős méretezési kihívásokkal és gyakori leállásokkal szembesültünk a zeller és a RabbitMQ, két olyan technológia, amely az aszinkron munkát kezelő rendszert táplálja, lehetővé téve platformunk kritikus funkcióit, beleértve a megrendelés-fizetést és a Dasher-feladatokat.
gyorsan megoldottuk ezt a problémát egy egyszerű, Apache Kafka-alapú aszinkron feladatfeldolgozó rendszerrel, amely megállította a leállásokat, miközben folytattuk az iterációt egy robusztus megoldáson. Kezdeti verziónk a legkisebb funkciókat valósította meg, amelyek a meglévő zeller-feladatok nagy részének elhelyezéséhez szükségesek. A gyártás után továbbra is támogattuk a zeller további funkcióit, miközben foglalkoztunk a Kafka használatakor felmerült új problémákkal.
azok a problémák, amelyekkel a zeller és a RabbitMQ használatával szembesültünk
a RabbitMQ és a zeller infrastruktúránk kritikus részei voltak, amelyek több mint 900 különböző aszinkron feladatot hajtottak végre a DoorDash-nál, beleértve a megrendelés kifizetését, a kereskedői megrendelés továbbítását és a Dasher helyfeldolgozását. A probléma, amellyel DoorDash szembesült, az volt, hogy a RabbitMQ a túlzott terhelés miatt gyakran csökkent. Ha a feladatok feldolgozása leállt, a DoorDash gyakorlatilag leállt, és a megrendeléseket nem lehetett teljesíteni, ami bevételkiesést eredményezett kereskedőink és Dashereink számára, és rossz tapasztalatot jelentett a fogyasztók számára. A következő frontokon szembesültünk problémákkal:
- elérhetőség: a kereslet okozta leállások csökkentett rendelkezésre állás.
- skálázhatóság: a RabbitMQ nem tudott méretezni vállalkozásunk növekedésével.
- megfigyelhetőség: a RabbitMQ korlátozott mutatókat kínált, a zelleres dolgozók pedig átlátszatlanok voltak.
- működési hatékonyság: ezeknek az összetevőknek az újraindítása időigényes, kézi folyamat volt.
miért nem volt elérhető az aszinkron feladatfeldolgozó rendszerünk?
Ez a legnagyobb probléma, amellyel szembesültünk, a leállások voltak, és gyakran akkor jöttek, amikor a kereslet a csúcson volt. RabbitMQ lemegy miatt terhelés, túlzott kapcsolat lemorzsolódás, és egyéb okok miatt. A megbízások leállnának, és újra kellene indítanunk a rendszerünket, vagy néha még egy teljesen új brókert is fel kell hoznunk, és manuálisan kell átvennünk a feladatot, hogy felépüljünk a kiesésből.
a búvárkodás mélyebbre a rendelkezésre álló kérdések, azt találtuk, a következő al-kérdések:
- zeller lehetővé teszi a felhasználók számára, hogy a menetrend feladatokat a jövőben a visszaszámlálás vagy ETA. Ezeknek a visszaszámlálásoknak a súlyos használata észrevehető terhelésnövekedést eredményezett a brókeren. Néhány kiesésünk közvetlenül kapcsolódott a visszaszámlálással járó feladatok növekedéséhez. Végül úgy döntöttünk, hogy korlátozzuk a visszaszámlálás használatát egy másik rendszer javára, amely a jövőben a munka ütemezésére volt érvényben.
- a forgalom hirtelen kitörései a RabbitMQ-t leromlott állapotban hagyják, ahol a feladatfogyasztás a vártnál lényegesen alacsonyabb volt. Tapasztalataink szerint ezt csak egy RabbitMQ ugrálással lehetett megoldani. A RabbitMQ rendelkezik az áramlásszabályozás koncepciójával, ahol csökkenti a túl gyorsan publikáló kapcsolatok sebességét, hogy a sorok lépést tarthassanak. Az áramlásszabályozás gyakran, de nem mindig vett részt ezekben a rendelkezésre állás romlásában. Amikor az áramlásszabályozás beindul, a kiadók hatékonyan hálózati késleltetésnek tekintik. A hálózati késleltetés csökkenti a válaszidőket; ha a késleltetés növekszik a csúcsforgalom során, jelentős lassulások eredményezhetik ezt a kaszkádot, amikor a kérések felfelé halmozódnak.
- python uWSGI webes munkatársainknak volt egy harakiri nevű funkciójuk, amely lehetővé tette az időtúllépést meghaladó folyamatok megölését. Leállások vagy lassulások során a harakiri kapcsolatot eredményezett a RabbitMQ brókerekkel, mivel a folyamatokat többször megölték és újraindították. Több ezer webes dolgozó fut egy adott időpontban, bármilyen lassúság, amely kiváltotta a harakirit, viszont még inkább hozzájárul a lassúsághoz azáltal, hogy extra terhelést ad a RabbitMQ-hoz.
- a gyártás során számos olyan esetet tapasztaltunk, amikor a zellerfogyasztók feladatfeldolgozása leállt, még jelentős terhelés hiányában is. A vizsgálati erőfeszítéseink nem mutattak bizonyítékot olyan erőforrás-korlátozásra, amely leállította volna a feldolgozást, és a munkások folytatták a feldolgozást, miután kidobták őket. Ezt a problémát soha nem a gyökér okozta, bár gyanítjuk, hogy a zeller dolgozói maguk, nem pedig a RabbitMQ.
Összességében ezek a rendelkezésre állási problémák elfogadhatatlanok voltak számunkra, mivel a magas megbízhatóság az egyik legfontosabb prioritásunk. Mivel ezek a kimaradások sokba kerültek nekünk az elmaradt megrendelések és a hitelesség szempontjából, olyan megoldásra volt szükségünk, amely a lehető leghamarabb megoldja ezeket a problémákat.
miért nem méreteztük a régi megoldásunkat
a következő legnagyobb probléma a méretarány volt. A DoorDash gyorsan növekszik, és gyorsan elértük a meglévő megoldásunk határait. Meg kellett találnunk valamit, ami lépést tart a folyamatos növekedéssel, mivel a régi megoldásunknak a következő problémái voltak:
a függőleges méretezési határ elérése
a rendelkezésre álló legnagyobb egycsomópontos RabbitMQ megoldást használtuk. Nem volt út a függőleges méretezéshez tovább, és már elkezdtük ezt a csomópontot a határai felé tolni.
a magas rendelkezésre állási mód korlátozta kapacitásunkat
a replikáció miatt az elsődleges-másodlagos magas rendelkezésre állási (HA) mód csökkentette az áteresztőképességet az egycsomópontos opcióhoz képest, így még kevesebb hely marad számunkra, mint az egycsomópontos megoldás. Nem engedhetjük meg magunknak, hogy a kereskedelem áteresztőképessége a rendelkezésre állás.
másodszor, az elsődleges-másodlagos HA mód a gyakorlatban nem csökkentette a leállások súlyosságát. A feladatátvételek több mint 20 percet vettek igénybe, és gyakran elakadtak kézi beavatkozást igényelve. Az üzenetek gyakran elvesznek a folyamat során is.
gyorsan kifogytunk a belmagasságból, mivel a DoorDash tovább növekedett, és a feladatfeldolgozást a korlátai közé szorította. Olyan megoldásra volt szükségünk, amely vízszintesen méretezhető, ahogy a feldolgozási igényeink növekedtek.
a zeller és a RabbitMQ korlátozott megfigyelhetőséget kínált
annak ismerete, hogy mi történik bármely rendszerben, alapvető fontosságú a rendelkezésre állás, a skálázhatóság és a működési integritás biztosításához.
ahogy navigáltunk a fent vázolt kérdésekben, észrevettük, hogy:
- csak egy kis RabbitMQ mutatóra korlátozódtunk.
- korlátozottan láthattuk magukat a Zellermunkásokat.
rendszerünk minden aspektusának valós idejű mutatóit kellett látnunk, ami azt jelentette, hogy a megfigyelhetőség korlátait is kezelni kellett.
a működési hatékonysági kihívások
a RabbitMQ működtetésével kapcsolatban számos problémával is szembesültünk:
- gyakran át kellett állítanunk a RabbitMQ csomópontot egy újra, hogy megoldjuk a megfigyelt tartós degradációt. Ez a művelet kézi és időigényes volt az érintett mérnökök számára, és gyakran késő este kellett elvégezni, csúcsidőn kívül.
- a DoorDash-nál nem voltak házon belüli zeller vagy RabbitMQ szakértők, akikre támaszkodhatnánk, hogy segítsenek kidolgozni ennek a technológiának a méretezési stratégiáját.
a RabbitMQ üzemeltetésével és karbantartásával töltött mérnöki idő nem volt fenntartható. Szükségünk volt valamire, ami jobban megfelel jelenlegi és jövőbeli igényeinknek.
lehetséges megoldások a zellerrel és a RabbitMQ-val kapcsolatos problémáinkra
a fent vázolt problémákkal a következő megoldásokat vettük figyelembe:
- változtassa meg a zeller brókert RabbitMQ-ról Redis-re vagy Kafka-ra. Ez lehetővé tenné számunkra, hogy továbbra is használjuk a zellert, más és potenciálisan megbízhatóbb háttértárral.
- Add multi-bróker támogatást a Django app, így a fogyasztók közzé N különböző brókerek alapján bármilyen logika akartunk. A feladatok feldolgozása több brókeren keresztül történik, így minden bróker a kezdeti terhelés töredékét fogja tapasztalni.
- frissítsen a zeller és a RabbitMQ újabb verzióira. A zeller és a RabbitMQ újabb verziói várhatóan megoldják a megbízhatósági problémákat, időt nyerve nekünk, mivel már párhuzamosan kinyertük az alkatrészeket a Django monolitból.
- áttérés a Kafka által támogatott egyedi megoldásra. Ez a megoldás több erőfeszítést igényel, mint a többi felsorolt lehetőség, de nagyobb potenciállal rendelkezik minden olyan probléma megoldására is, amely a régi megoldással volt.
minden opciónak megvannak az előnyei és hátrányai:
opció | előnyök | hátrányok |
Redis mint bróker |
|
|
Kafka broker |
|
|
több bróker |
|
|
frissítési verziók |
|
|
egyedi Kafka megoldás |
|
|
stratégiánk a Kafka beszállására
a szükséges rendszer-üzemidő miatt kidolgoztuk a Kafka-t onboarding stratégia alapján a következő elveket, hogy maximalizálja a megbízhatóság előnyeit a legrövidebb idő alatt. Ez a stratégia három lépést tartalmazott:
- futás a földre: Szerettük volna kihasználni az általunk épített megoldás alapjait, miközben annak más részein iteráltunk. Ezt a stratégiát egy versenyautó vezetéséhez hasonlítjuk, miközben új üzemanyag-szivattyút cserélünk.
- tervezési lehetőségek a fejlesztők zökkenőmentes elfogadásához: minimalizálni akartuk az összes Fejlesztő elpazarolt erőfeszítéseit, amelyek egy másik felület meghatározásából származhatnak.
- növekményes bevezetés nulla állásidővel: Ahelyett, hogy egy nagy mutatós kiadást először tesztelnének a vadonban, nagyobb eséllyel a meghibásodásokra, arra összpontosítottunk, hogy kisebb független funkciókat szállítsunk, amelyeket a vadonban külön-külön tesztelhetünk hosszabb ideig.
futás a földön
A Kafka-ra való váltás jelentős technikai változást jelentett a veremben, de nagyon szükséges volt. Nem volt időnk pazarolni, mivel minden héten elvesztettük az üzletet a régi RabbitMQ megoldásunk instabilitása miatt. Elsődleges prioritásunk az volt, hogy létrehozzunk egy minimális életképes terméket (MVP), amely átmeneti stabilitást biztosít számunkra, és megadja a szükséges belmagasságot ahhoz, hogy megismételjük és felkészüljünk egy átfogóbb megoldásra, szélesebb körű elfogadással.
MVP-jünk olyan gyártókból állt, akik közzétették a feladat teljesen minősített nevét (fqn) és pácolt érveket a Kafka számára, miközben fogyasztóink elolvasták ezeket az üzeneteket, importálták a feladatokat az FQN-ből, és szinkronban hajtották végre őket a megadott argumentumokkal.
1. ábra: A minimális életképes termék (MVP) architektúra, amelyet úgy döntöttünk, hogy felépítünk, tartalmazott egy átmeneti állapotot, ahol kölcsönösen kizáró feladatokat teszünk közzé mind a régi (piros szaggatott vonalak), mind az új rendszerek (zöld folytonos vonalak) számára, a végső állapot előtt, ahol abbahagyjuk a feladatok közzétételét a RabbitMQ számára.
tervezési lehetőségek a fejlesztők zökkenőmentes elfogadásához
néha a fejlesztői elfogadás nagyobb kihívást jelent, mint a fejlesztés. Ezt megkönnyítettük a zeller @task annotációjának csomagolásával, amely dinamikusan irányította a feladatok beküldését bármelyik rendszerbe a dinamikusan konfigurálható funkciójelzők alapján. Most ugyanaz a felület használható mindkét rendszer feladatainak írására. Ezekkel a döntésekkel a mérnöki csapatoknak nem kellett további munkát végezniük az új rendszerbe való integráláshoz, kizárva egyetlen jellemző zászló megvalósítását.
szerettük volna bevezetni a rendszerünket, amint az MVP készen áll, de még nem támogatta ugyanazokat a funkciókat, mint a zeller. A zeller lehetővé teszi a felhasználók számára, hogy feladataikat paraméterekkel konfigurálják a feladat kommentárjában, vagy amikor elküldik feladatukat. A gyorsabb indítás érdekében létrehoztunk egy kompatibilis paraméterek engedélyezőlistáját, és úgy döntöttünk, hogy a feladatok többségének támogatásához szükséges legkisebb számú funkciót támogatjuk.
2. ábra: Gyorsan növeltük a feladatok mennyiségét a Kafka-alapú MVP – hez, kezdve az alacsony kockázatú és az alacsony prioritású feladatokkal. Ezek közül néhány olyan feladat volt, amely csúcsidőn kívül futott, ami megmagyarázza a fent ábrázolt metrika tüskéit.
amint az a 2. ábrán látható, a fenti két döntéssel két hét fejlesztés után indítottuk el MVP-t, és a RabbitMQ feladatterhelésének 80% – os csökkenését értük el egy héttel az Indítás után. Elsődleges problémánkat, a leállásokat gyorsan megoldottuk, és a projekt során egyre több ezoterikus funkciót támogattunk, hogy lehetővé tegyük a fennmaradó feladatok végrehajtását.
inkrementális bevezetés, nulla állásidő
a Kafka klaszterek dinamikus váltása és a RabbitMQ és a Kafka közötti váltás üzleti hatás nélkül rendkívül fontos volt számunkra. Ez a képesség számos műveletben is segített nekünk, mint például a klaszter karbantartása, a terheléscsökkentés és a fokozatos migráció. Ennek a bevezetésnek a megvalósításához, dinamikus funkciójelzőket használtunk mind az üzenet beküldési szintjén, mind az üzenetfogyasztási oldalon. A teljes dinamizmus ára itt az volt, hogy a munkásflottánk kettős kapacitással működjön. Ennek a flottának a felét RabbitMQ-nak, a többit Kafkának szentelték. A munkásflotta kettős kapacitással történő működtetése határozottan megterhelte az infrastruktúránkat. Egy ponton még egy teljesen új Kubernetes klasztert is felforgattunk, hogy minden dolgozónkat elhelyezzük.
a fejlesztés kezdeti szakaszában ez a rugalmasság jól szolgált nekünk. Miután jobban bíztunk az új rendszerünkben, megvizsgáltuk az infrastruktúránk terhelésének csökkentésének módjait, például több fogyasztó folyamat futtatását egy dolgozó gépenként. Ahogy átálltunk a különböző témákra, elkezdhettük csökkenteni a RabbitMQ munkavállalói számát, miközben fenntartottunk egy kis tartalékkapacitást.
nincs tökéletes megoldás, szükség szerint iteráljon
a gyártás MVP-jével megvolt a szükséges belmagasság a termék iterálásához és polírozásához. Minden hiányzó zeller funkciót rangsoroltunk azon feladatok száma alapján, amelyek arra használták, hogy eldöntsük, melyiket hajtsuk végre először. A csak néhány feladat által használt funkciókat nem valósítottuk meg egyedi megoldásunkban. Ehelyett újraírtuk ezeket a feladatokat, hogy ne használjuk az adott funkciót. Ezzel a stratégiával végül minden feladatot eltávolítottunk a zellerről.
A Kafka használata új problémákat is bevezetett, amelyekre figyelmet kellett fordítanunk:
- a vonalvezető blokkolása, ami a feladat feldolgozásának késleltetését eredményezte
- a telepítések elindították a partíció egyensúlyozását, ami szintén késéseket eredményezett
A Kafka vonalvezető blokkolási problémája
A Kafka témái úgy vannak felosztva, hogy egyetlen fogyasztó (fogyasztói csoportonként) olvassa az üzeneteket a hozzárendelt partíciókhoz az érkezési sorrendben. Ha egy egyetlen partícióban lévő üzenet feldolgozása túl sokáig tart, akkor az a partícióban mögötte lévő összes üzenet fogyasztását leállítja, amint az az alábbi 3.ábrán látható. Ez a probléma különösen katasztrofális lehet egy kiemelt téma esetén. Szeretnénk folytatni az üzenetek feldolgozását egy partícióban abban az esetben, ha késés történik.
3. ábra: A Kafka head-of-the-line blokkoló probléma, egy lassú üzenet egy partíció (piros) blokkolja az összes üzenetet mögötte a feldolgozás. Más partíciók a várt módon folytatódnak.
míg a párhuzamosság alapvetően Python probléma, ennek a megoldásnak a koncepciói más nyelvekre is alkalmazhatók. Az alábbi 4. ábrán bemutatott megoldásunk az volt, hogy munkavállalónként egy Kafka-fogyasztói folyamatot és több feladat-végrehajtási folyamatot helyeztünk el. A Kafka-consumer folyamat felelős az üzenetek lekéréséért a Kafkától, és elhelyezéséért egy helyi várólistán, amelyet a feladat-végrehajtási folyamatok olvasnak. Addig folytatja a fogyasztást, amíg a helyi sor el nem éri a felhasználó által megadott küszöböt. Ez a megoldás lehetővé teszi a partícióban lévő üzenetek áramlását, és csak egy feladat-végrehajtási folyamatot fog leállítani a lassú üzenet. A küszöb korlátozza a repülés közbeni üzenetek számát is a helyi sorban (amelyek rendszerösszeomlás esetén elveszhetnek).
4. ábra: a nem blokkoló Kafka dolgozónk egy helyi üzenetsorból és kétféle folyamatból áll: Kafka-fogyasztói folyamat és több feladat-végrehajtó folyamat. Míg a kafka-fogyasztó több partícióból is olvashat, az egyszerűség kedvéért csak egyet ábrázolunk. Ez a diagram azt mutatja, hogy egy lassan feldolgozó üzenet (piros színnel) csak egyetlen feladat-végrehajtót blokkol, amíg be nem fejeződik, míg a partíció mögött lévő többi üzenetet továbbra is más feladat-végrehajtók dolgozzák fel.
a telepítések zavaró hatása
naponta többször telepítjük Django alkalmazásunkat. A megoldásunk egyik hátránya, hogy észrevettük, hogy egy telepítés a partíciós hozzárendelések egyensúlyának helyreállítását váltja ki a Kafka-ban. Annak ellenére, hogy témánként más fogyasztói csoportot használtak az egyensúlyi hatókör korlátozására, a telepítések továbbra is pillanatnyi lassulást okoztak az üzenetfeldolgozásban, mivel a feladatfogyasztásnak le kellett állnia az egyensúlyozás során. A lassulás a legtöbb esetben elfogadható lehet, amikor tervezett kiadásokat hajtunk végre, de katasztrofális lehet, ha például vészhelyzeti kiadást hajtunk végre egy hiba gyorsjavításához. Ennek következménye a lépcsőzetes feldolgozási lassulás bevezetése lenne.
A Kafka újabb verziói és az ügyfelek támogatják az inkrementális kooperatív egyensúly helyreállítását, ami jelentősen csökkentené az egyensúly helyreállításának működési hatását. Korszerűsítése ügyfeleink, hogy támogassa az ilyen típusú kiegyensúlyozás lenne a megoldás a választás a jövőben. Sajnos az inkrementális kooperatív kiegyensúlyozás még nem támogatott a választott Kafka kliensünkben.
Key wins
a projekt befejezésével jelentős javulást értünk el az üzemidő, a skálázhatóság, a megfigyelhetőség és a decentralizáció tekintetében. Ezek a győzelmek kulcsfontosságúak voltak üzleti tevékenységünk folyamatos növekedésének biztosításához.
nincs több ismétlődő kimaradás
szinte azonnal leállítottuk az ismétlődő leállásokat, amint elkezdtük bevezetni ezt az egyedi Kafka megközelítést. A leállások rendkívül rossz felhasználói élményt eredményeztek.
- az MVP-ben a leggyakrabban használt zeller funkcióknak csak egy kis részhalmazát valósítottuk meg, két hét alatt képesek voltunk a munka kódját gyártásra szállítani.
- az MVP-vel jelentősen csökkentettük a RabbitMQ és a zeller terhelését, miközben tovább keményítettük a megoldásunkat és új funkciókat vezettünk be.
A Feladatfeldolgozás már nem volt a növekedés korlátozó tényezője
a Kafka architektúránk középpontjában egy olyan feladatfeldolgozó rendszert építettünk ki, amely rendkívül elérhető és vízszintesen skálázható, lehetővé téve a DoorDash és ügyfelei számára a növekedés folytatását.
masszívan kibővített megfigyelhetőség
mivel ez egy egyedi megoldás volt, szinte minden szinten több mutatóban tudtunk sütni. Minden sor, munkás és feladat teljes mértékben megfigyelhető volt nagyon szemcsés szinten a termelési és fejlesztési környezetekben. Ez a megnövekedett megfigyelhetőség hatalmas győzelem volt nemcsak termelési értelemben, hanem a fejlesztői termelékenység szempontjából is.
operatív decentralizáció
a megfigyelhetőség javításával képesek voltunk sablonozni riasztásainkat Terraform modulokként, és explicit módon hozzárendelni a tulajdonosokat minden egyes témához és implicit módon mind a 900 plusz feladathoz.
a feladatfeldolgozó rendszer részletes kezelési útmutatója minden mérnök számára hozzáférhetővé teszi az információkat a témákkal és a munkavállalókkal kapcsolatos operatív problémák hibakereséséhez, valamint a Kafka klaszterkezelési műveleteinek elvégzéséhez. A napi működés önkiszolgáló, és infrastrukturális csapatunk támogatására ritkán van szükség.
következtetés
összefoglalva, elértük a RabbitMQ skálázására való képességünk felső határát, és alternatívákat kellett keresnünk. Az alternatíva, amellyel mentünk, egy egyedi Kafka-alapú megoldás volt. Bár vannak hátrányai a Kafka használatának, számos megoldást találtunk, amelyeket fentebb leírtunk.
amikor a kritikus munkafolyamatok nagymértékben támaszkodnak az aszinkron feladatfeldolgozásra, a skálázhatóság biztosítása rendkívül fontos. Ha hasonló problémákat tapasztal, nyugodtan merítsen ihletet stratégiánkból, amely az eredmény 80% – át adta nekünk az erőfeszítés 20% – ával. Ez a stratégia általában egy taktikai megközelítés, amely gyorsan enyhíti a megbízhatósági problémákat, és időt nyer egy robusztusabb és stratégiai megoldáshoz.
Köszönetnyilvánítás
a szerzők szeretnék megköszönni Clement Fang, Corry Haines, Danial Asif, Jay Weinstein, Luigi Tagliamonte, Matthew Anger, Shaohua Zhou és Yun-Yu Chen közreműködését ebben a projektben.
fotó: tian kuan az Unsplash oldalon