Ne eliberăm de Monolit

Și cum am evoluat cu Microservicii

(19 iulie 2018)

Un monolit care stăpânește codul maimuțelor

La Conio totul a început cu ceea ce în mod obișnuit se numește „Monolit”. Adică, o singură bază de cod care conține toate aplicațiile complete: componentele frontend, componentele backend, serviciile API, sarcinile de fundal; la naiba, chiar devopsesc scripturi. Și a funcționat foarte bine la început. Doar câțiva ingineri software, care lucrează pe zone separate ale bazei de cod (deci foarte puține șanse de conflicte de modificare a codului), sunt ușor de implementat. Concentrați-vă pe scrierea funcționalităților aplicațiilor, fără să vă faceți griji cu privire la multe altele. Cum am abordat implementarea? Cu doar câțiva clienți beta conștienți de progresele continue, nu a fost o problemă reală închiderea serviciilor pentru o vreme, lansarea bazei de cod complete (indiferent cât de mici sau mari au fost modificările generale și dacă acestea includeau migrațiile bazei de date), și apoi aduceți din nou serviciile la dispoziție.

A fost cu siguranță satisfăcător să văd un produs care se conturează de la bază și să primească aprecieri de la clienții finali. Cu toate acestea, știam foarte bine că această abordare nu se potrivește unei companii Fintech moderne.

Ce atunci?

Cu majoritatea aplicațiilor software, clienții sunt destul de toleranți. Da, Whatsapp s-ar putea să nu mai funcționeze și să aibă întreruperi de câteva ore: cu siguranță o pacoste, dar nu o problemă percepută. Același lucru este valabil și pentru Pokemon Go sau aplicația ta de joc preferată. Cu toate acestea, nu este cazul atunci când sunt implicați bani : starea de spirit se schimbă dacă nu vă puteți conecta la contul dvs. bancar sau nu puteți face operațiuni comerciale. Acest lucru este și mai rău în cazul aplicațiilor de criptomonede: majoritatea oamenilor își amintesc de gafele infame din trecut și, ori de câte ori nu pot accesa fondurile lor de criptomonede, chiar și pentru o perioadă scurtă de timp, apar speculații. Este corect. Sunt banii dvs. și ar trebui să aveți puține sau deloc probleme atunci când doriți să-i utilizați.

Monolitul de mai sus nu este potrivit pentru un astfel de scenariu: orice modificare a bazei de cod în producție ar necesita o implementare completă, cu perioade de inactivitate asociate. În fiecare zi, lucrăm pentru a ne îmbunătăți serviciile prin remedierea erorilor, făcând interfața noastră și mai prietenoasă, eliminând funcționalitățile vechi și adăugând altele noi care au o utilizare mai bună. De multe ori lansăm aceste actualizări zilnic, astfel încât clienții noștri să poată beneficia imediat și să ne străduim să nu avem niciun impact asupra experienței clientului. Adică, orice formulă inventăm în culise, trebuie să fie invizibilă pentru lumea exterioară (cel puțin, de cele mai multe ori). Deci, ne-am îndepărtat de Monolit și am ales ceea ce se numește în mod obișnuit „arhitectură Microservices”.

Evoluție prin Microservices

Baza de cod masivă strâns lipită este acum descompusă în părți mai mici, fiecare dintre ele reprezentând un anumit serviciu. Ori de câte ori sunt executate, serviciile comunică între ele sincron prin protocol HTTP standard și asincron prin cozi (tratate de exemplu de RabbitMQ și Apache Kafka).

Interacțiuni într-o arhitectură Microservices

Este destul de dificil să începi să sparg monolitul în componente mai mici, dar merită foarte bine efortul. În termeni militari, este foarte asemănător cu ceea ce a făcut Iulius Cezar pentru a guverna constant marele teritoriu al Galliei: „ împarte și cucerește ”.

1) Produsul poate fi desfășurat continuu. O actualizare de cod se aplică acum doar unui microserviciu: în majoritatea cazurilor poate fi implementată imediat în producție și lansată fără impact asupra clientului

2) Codul este mai ușor de gestionat. Din perspectiva organizației companiei, lucrurile se schimbă atunci când o echipă de 2 ingineri software devine o echipă de 10 ingineri software. Este mai eficient și are mai puține conflicte de cod, atunci când fiecare membru al echipei este responsabil pentru propriul său microserviciu.

3) Codul este mai ușor de întreținut. O arhitectură Microservices necesită, prin natură, definirea unei interfețe pentru a comunica cu lumea externă (fie că este aplicația frontend sau alt serviciu backend) și este complet izolată din orice alt punct de vedere. Acest lucru permite revizuirea, reproiectarea sau chiar rescrierea completă de la zero (chiar și în diferite limbi, dacă este convenabil) a componentelor individuale ale aplicației, fără a afecta restul. Fiecare microserviciu poate folosi acum cel mai potrivit limbaj. Componentele de calcul criptografice grele pot fi, de exemplu, optimizate în C, în timp ce serviciile API în Python și sarcini de lungă durată în Go.

5) Izolarea și securitatea codului îmbunătățite. Fiecare microserviciu poate fi rulat în propriul container Docker, oferind astfel izolarea privilegiilor, segregarea rețelei și a datelor și, de o importanță capitală pentru o fază de creștere, un potențial enorm de scalabilitate.

Sunt Microserviciile răspunsul atunci?

Desigur, nu există un prânz gratuit. O arhitectură Microservices vine, de asemenea, cu propriul set de provocări dificile:

1) Complexitatea operațională. Inginerii DevOps sunt cu siguranță necesari pentru a atenua complexitățile noului proces de implementare.

2) Umflarea hardware-ului. Microserviciile sunt adesea rulate în containere Docker; de îndată ce numărul de microservicii proliferează, devine din ce în ce mai dificil să rulezi aplicația completă pe același hardware ca înainte.

3) Intercomunicații generale: este posibil ca fiecare solicitare să interacționeze cu una sau mai multe microservicii prin rețea. Acest lucru poate determina o latență crescută și poate fi supus unor eșecuri temporare. Pentru a implementa servicii rezistente, precum și pentru a îmbunătăți scalabilitatea întregului sistem, este necesar să mutați interacțiunile către mesaje asincrone (de exemplu, folosind Apache Kafka și / sau RabbitMQ)

4) Coerență eventuală. Aceasta este probabil cea mai grea provocare a unei arhitecturi Microservice. Având în vedere un singur microserviciu, este posibil să creați tranzacții RDBMS în limitele sale . Din păcate, totuși, o problemă obișnuită în arhitecturile distribuite este tratarea mai multor tranzacții care nu se află în aceleași limite. Ca urmare, sistemul poate ajunge într-o stare ilegală și nerecuperabilă. Pentru a atenua astfel de probleme, Conio adoptă diferite strategii:

  1. Urmând practicile de proiectare bazată pe domenii, descompuneți domeniile de nivel superior în subdomenii și limitați-le în contexte limitate ; fiecare context delimitat este implementat ca un microserviciu, unde se aplică limitele tranzacțiilor. Aceasta rezolvă posibilitatea de a avea incoerențe pentru anumite subdomenii.
  2. Implementați interacțiuni asincrone idempotente, care mai devreme sau mai târziu rezolvă neconcordanțe.
  3. Ori de câte ori este posibil, evitați orice acțiune care ar putea implica mai multe subdomenii.

5) Raportare complexă. Întrucât fiecare subdomeniu trăiește într-un context delimitat specific, rapoartele complexe care implică mai multe subdomenii ar putea necesita interogarea datelor din mai multe surse de date: acest lucru poate avea un impact negativ atât asupra expresivității domeniilor, cât și asupra scalabilității sistemului. Aici, în Conio , am adoptat o arhitectură CQRS pentru a sprijini rapoartele de activitate backoffice și analize de afaceri.

6 ) Sistem de înregistrare. Fiecare element dintr-un sistem distribuit contribuie la crearea jurnalului întregului sistem. Cu toate acestea, este necesar să implementați instrumente care pot crea conexiunile necesare între toate aceste jurnale separate pentru a avea un jurnal unificat pentru fiecare interacțiune. Aici în Conio folosim stiva ELK (ElasticSearch, Logstash, Kibana) pentru a stoca și interoga datele jurnalului: fiecare jurnal este îmbogățit cu ID-urile de corelație necesare care permit jurnalul unificat menționat mai sus.

Nu opriți niciodată evoluția

Luarea noastră? Descompunerea bazei de cod unice inițiale trebuie privită ca o sarcină pe termen lung , cu îmbunătățiri permanente. La Conio ne-au luat câțiva ani, când pas cu pas am trecut de la o bază de cod masivă la peste 100 de microservicii. Am ajuns la un punct în care ne simțim mândri de rezultate, dar în același timp continuăm să explorăm. Există mai multe noi optimizări posibile: trecerea de la Docker Swarm la Kubernetes? Migrarea serviciilor backend-for-frontend la funcții lambda fără server? Treceți la un flux complet de operațiuni de implementare continuă? Posibilitățile sunt nelimitate.

Am atins o serie de subiecte și tehnologii aici. În articolele următoare vom împărtăși mai multe detalii despre constatările și progresele noastre. Dacă doriți, nu ezitați să comentați și să ne spuneți experiența dvs.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *