Defecțiunea JAR-urilor noastre De ce am încetat să construim JAR-uri grase

Serviciile de backend HubSpot sunt scrise aproape toate în Java. Avem peste 1.000 de microservicii în mod constant construite și implementate. Când vine timpul să implementăm și să rulăm una dintre aplicațiile noastre Java, dependențele sale trebuie să fie prezente pe calea clasei pentru ca aceasta să funcționeze. Anterior, am gestionat acest lucru folosind plugin-ul maven-shade pentru a construi un JAR gras. Aceasta ia aplicația și toate dependențele sale și le grupează într-un JAR masiv. Acest JAR este imuabil și nu are dependențe externe, ceea ce îl face ușor de implementat și de rulat. De ani de zile, astfel am împachetat toate aplicațiile noastre Java și a funcționat destul de bine, dar a avut unele dezavantaje grave.

noastre

Primul număr pe care l-am lovit este că JAR-urile nu trebuie să fie agregate astfel. Pot exista fișiere cu aceeași cale prezentă în mai multe JAR-uri și în mod implicit pluginul de umbră include primul fișier din JAR-ul gras și aruncă restul. Acest lucru a provocat unele erori cu adevărat frustrante până când ne-am dat seama ce se întâmplă (de exemplu, Jersey folosește fișiere META-INF/servicii pentru a descoperi automat furnizorii și acest lucru a făcut ca unii furnizori să nu fie înregistrați). Din fericire, pluginul pentru umbră acceptă transformatoare de resurse care vă permit să definiți o strategie de îmbinare atunci când întâlnește fișiere duplicate, astfel încât am putut rezolva această problemă. Cu toate acestea, este încă un „gotcha” suplimentar de care trebuie să fie conștienți toți dezvoltatorii noștri.

Cealaltă problemă, mai mare, cu care ne-am confruntat este că acest proces este lent și ineficient. Folosind una dintre aplicațiile noastre ca exemplu, conține 70 de fișiere de clasă în valoare totală de 210 KB atunci când sunt ambalate ca JAR. Dar după ce rulăm pluginul de umbră pentru a-și grupa dependențele, ajungem cu un JAR gras care conține 101.481 de fișiere și cântărește la 158 MB. Combinarea a 100.000 de fișiere mici într-o singură arhivă este lentă. Încărcarea acestui JAR pe S3 la sfârșitul construcției este lentă. Descărcarea acestui JAR la momentul implementării este lentă (și poate satura plăcile de rețea de pe serverele noastre de aplicații dacă avem o mulțime de implementări simultane).

Cu peste 100 de ingineri care se angajează constant, avem de obicei până la 1.000-2.000 de versiuni pe zi. Cu fiecare dintre aceste versiuni încărcând un JAR gras, am generat 50-100 GB de artefacte de construcție pe zi . Și cea mai dureroasă parte este cât de multe duplicări există între fiecare dintre aceste artefacte. Aplicațiile noastre au o mulțime de suprapuneri în ceea ce privește bibliotecile terță parte, de exemplu, toate folosesc Guice, Jackson, Guava, Logback etc. Imaginați-vă câte exemplare ale acestor biblioteci avem în S3!