Ca în
Într-o zi m-am trezit cu un e-mail nemulțumit din cauza întârzierilor mari cu Alvin, pe care plănuiam să-l lansăm în viitorul apropiat. Mai exact, clientul a experimentat o latență de percentila 99 în regiunea de 50 ms, cu mult peste bugetul nostru de latență. Acest lucru a fost surprinzător, deoarece am testat serviciul pe larg, în special în ceea ce privește latența, care este o plângere comună.
Înainte de a-l testa pe Alvin, am efectuat o mulțime de experimente cu 40 de interogări pe secundă (QPS), toate arătând o latență mai mică de 10 ms. Eram gata să declar că nu sunt de acord cu rezultatele lor. Dar aruncând o altă privire la scrisoare, am observat ceva nou: nu testasem exact condițiile menționate de ei, QPS-ul lor era mult mai scăzut decât al meu. Am testat la 40k QPS, dar ei doar la 1k. Am mai făcut un experiment, de data aceasta cu un QPS mai mic, doar pentru a-i potoli.
Din moment ce scriu pe blog despre asta, probabil că v-ați dat deja seama că numerele lor erau corecte. Mi-am testat clientul virtual din nou și din nou, cu același rezultat: un număr mic de cereri nu numai că mărește latența, dar crește numărul de solicitări cu o latență de peste 10 ms. Cu alte cuvinte, dacă la 40k QPS aproximativ 50 de solicitări pe secundă depășeau 50 ms, atunci la 1k QPS au fost 100 de solicitări peste 50 ms în fiecare secundă. Paradox!
Îngustând căutarea
Când se confruntă cu o problemă de latență într-un sistem distribuit cu multe componente, primul pas este crearea unei liste scurte de suspecți. Să pătrundem puțin mai adânc în arhitectura lui Alvin:
Un bun punct de plecare este o listă de tranziții I/O finalizate (apeluri de rețea/căutări pe disc etc.). Să încercăm să ne dăm seama unde este întârzierea. Pe lângă I/O-ul evident cu clientul, Alvin face un pas suplimentar: accesează depozitul de date. Cu toate acestea, această stocare funcționează în același cluster ca și Alvin, astfel încât latența ar trebui să fie mai mică decât la client. Deci, lista suspecților:
- Apel în rețea de la client către Alvin.
- Apel de rețea de la Alvin către depozitul de date.
- Căutați pe disc în depozitul de date.
- Apel de rețea de la depozitul de date către Alvin.
- Apel de rețea de la Alvin către un client.
Să încercăm să tăiem câteva puncte.
Stocarea datelor nu are nimic de-a face cu asta
Primul lucru pe care l-am făcut a fost să convertesc Alvin într-un server ping-ping care nu procesează cererile. Când primește o solicitare, returnează un răspuns gol. Dacă latența scade, atunci o eroare în implementarea Alvin sau a depozitului de date nu este nimic nemaiauzit. În primul experiment obținem următorul grafic:
După cum puteți vedea, nu există nicio îmbunătățire atunci când utilizați serverul ping-ping. Aceasta înseamnă că depozitul de date nu crește latența, iar lista suspecților se reduce la jumătate:
- Apel în rețea de la client către Alvin.
- Apel de rețea de la Alvin către un client.
Grozav! Lista se micșorează rapid. Credeam că aproape îmi dădusem motivul.
gRPC
Acum este momentul să vă prezint un jucător nou: gRPC
bine optimizat și utilizat pe scară largă, aceasta a fost prima dată când îl folosesc pe un sistem de această dimensiune și mă așteptam ca implementarea mea să fie suboptimă - cel puțin.
disponibilitate gRPC
în stivă a dat naștere la o nouă întrebare: poate este implementarea mea sau a mea gRPC
provoacă o problemă de latență? Adăugarea unui nou suspect pe listă:
- Clientul sună la bibliotecă
gRPC
- bibliotecă
gRPC
efectuează un apel de rețea către biblioteca de pe clientgRPC
pe server - bibliotecă
gRPC
contactează Alvin (nicio operațiune în cazul serverului de ping-pong)
Pentru a vă face o idee despre cum arată codul, implementarea mea client/Alvin nu este mult diferită de cea client-server
Notă: Lista de mai sus este puțin simplificată deoarece
gRPC
face posibilă utilizarea propriului model (șablon?) de threading, în care stiva de execuție este împletităgRPC
și implementarea utilizatorului. De dragul simplității, vom rămâne la acest model.
Profilarea va rezolva totul
După ce am tăiat depozitele de date, am crezut că aproape am terminat: „Acum e ușor! Să aplicăm profilul și să aflăm unde apare întârzierea.” eu
Am luat patru profiluri: cu QPS ridicat (latență scăzută) și cu un server de ping-pong cu QPS scăzut (latență ridicată), atât pe partea de client, cât și pe partea de server. Și, pentru orice eventualitate, am luat și un eșantion de profil de procesor. Când compar profilurile, de obicei caut o stivă de apeluri anormală. De exemplu, în partea proastă, cu latență mare, există mult mai multe comutatoare de context (de 10 ori sau mai mult). Dar în cazul meu, numărul de comutări de context a fost aproape același. Spre groaza mea, nu era nimic semnificativ acolo.
Depanare suplimentară
eram disperat. Nu știam ce alte instrumente aș putea folosi, iar următorul meu plan a fost, în esență, să repet experimentele cu diferite variații, mai degrabă decât să diagnosticez clar problema.
Ce-ar fi dacă
De la bun început, am fost îngrijorat de latența specifică de 50 ms. Acesta este un moment foarte mare. Am decis că voi tăia bucăți din cod până îmi voi putea da seama exact care parte provoacă această eroare. Apoi a venit un experiment care a funcționat.
Ca de obicei, retrospectiv se pare că totul era evident. Am plasat clientul pe aceeași mașină cu Alvin - și am trimis o solicitare către localhost
. Și creșterea latenței a dispărut!
A fost ceva în neregulă cu rețeaua.
Învățarea abilităților de inginer de rețea
Trebuie să recunosc: cunoștințele mele despre tehnologiile de rețea sunt groaznice, mai ales având în vedere faptul că lucrez cu ele în fiecare zi. Dar rețeaua era principalul suspect și trebuia să învăț cum să o depanez.
Din fericire, internetul îi iubește pe cei care vor să învețe. Combinația dintre ping și tracert părea un început suficient de bun pentru depanarea problemelor de transport în rețea.
În primul rând, am lansat
Apoi am încercat
Deci nu codul meu, implementarea gRPC sau rețeaua a cauzat întârzierea. Începeam să-mi fac griji că nu voi înțelege niciodată asta.
Acum pe ce sistem de operare suntem
gRPC
folosit pe scară largă pe Linux, dar exotic pe Windows. Am decis să încerc un experiment, care a funcționat: am creat o mașină virtuală Linux, am compilat Alvin pentru Linux și am implementat-o.
Și iată ce s-a întâmplat: serverul de ping-pong Linux nu a avut aceleași întârzieri ca o gazdă similară Windows, deși sursa de date nu a fost diferită. Se pare că problema este în implementarea gRPC pentru Windows.
algoritmul lui Nagle
În tot acest timp am crezut că îmi lipsește un steag gRPC
. Acum înțeleg ce este cu adevărat gRPC
Drapelul Windows lipsește. Am găsit o bibliotecă RPC internă în care eram sigur că va funcționa bine pentru toate indicatoarele setate
aproape Gata: am început să elimin steagurile adăugate pe rând până când regresia a revenit, astfel încât să pot identifica cauza. A fost infam
gRPC
acest flag a fost setat în implementarea Linux pentru socket-urile TCP, dar nu și în Windows. Eu sunt asta
Concluzie
Latența mai mare la QPS scăzut a fost cauzată de optimizarea sistemului de operare. Privind retrospectiv, profilarea nu a detectat latența, deoarece a fost făcută mai degrabă în modul kernel decât în
În ceea ce privește experimentul localhost, probabil că nu a atins codul real de rețea și algoritmul lui Nagle nu a rulat, așa că problemele de latență au dispărut când clientul a ajuns la Alvin prin localhost.
Data viitoare când vedeți o creștere a latenței pe măsură ce numărul de solicitări pe secundă scade, algoritmul lui Nagle ar trebui să fie pe lista dumneavoastră de suspecți!
Sursa: www.habr.com