La transiro de monolito al mikroservoj: historio kaj praktiko

En ĉi tiu artikolo, mi parolos pri kiel la projekto, pri kiu mi laboras, transformiĝis de granda monolito al aro de mikroservoj.

La projekto komencis sian historion antaŭ sufiĉe longa tempo, komence de 2000. La unuaj versioj estis verkitaj en Visual Basic 6. Kun la tempo, evidentiĝis, ke evoluo en ĉi tiu lingvo estos malfacile subtenebla en la estonteco, ĉar la IDE. kaj la lingvo mem estas malbone evoluinta. Fine de la 2000-aj jaroj, estis decidite ŝanĝi al la pli promesplena C#. La nova versio estis verkita paralele kun la revizio de la malnova, iom post iom pli kaj pli da kodo estis skribita en .NET. Backend en C# estis komence temigis servan arkitekturon, sed dum evoluo, oftaj bibliotekoj kun logiko estis uzitaj, kaj servoj estis lanĉitaj en ununura procezo. La rezulto estis aplikaĵo, kiun ni nomis "serva monolito".

Unu el la malmultaj avantaĝoj de ĉi tiu kombinaĵo estis la kapablo de servoj voki unu la alian per ekstera API. Ekzistis klaraj antaŭkondiĉoj por la transiro al pli ĝusta servo, kaj en la estonteco, mikroserva arkitekturo.

Ni komencis nian laboron pri putriĝo ĉirkaŭ 2015. Ni ankoraŭ ne atingis idealan staton – ankoraŭ ekzistas partoj de granda projekto, kiujn oni apenaŭ povas nomi monolitoj, sed ili ankaŭ ne aspektas kiel mikroservoj. Tamen, progreso estas grava.
Mi parolos pri ĝi en la artikolo.

La transiro de monolito al mikroservoj: historio kaj praktiko

Enhavo

Arkitekturo kaj problemoj de la ekzistanta solvo


Komence, la arkitekturo aspektis jene: la UI estas aparta aplikaĵo, la monolita parto estas skribita en Visual Basic 6, la .NET-apliko estas aro de rilataj servoj laborantaj kun sufiĉe granda datumbazo.

Malavantaĝoj de la antaŭa solvo

Ununura punkto de fiasko
Ni havis ununuran punkton de fiasko: la aplikaĵo .NET funkciis en ununura procezo. Se iu modulo malsukcesis, la tuta aplikaĵo malsukcesis kaj devis esti rekomencita. Ĉar ni aŭtomatigas grandan nombron da procezoj por malsamaj uzantoj, pro malsukceso en unu el ili, ĉiuj ne povis labori dum iom da tempo. Kaj en kazo de programara eraro, eĉ sekurkopio ne helpis.

Vico de plibonigoj
Ĉi tiu malavantaĝo estas sufiĉe organiza. Nia aplikaĵo havas multajn klientojn, kaj ili ĉiuj volas plibonigi ĝin kiel eble plej baldaŭ. Antaŭe, estis neeble fari tion paralele, kaj ĉiuj klientoj staris en vico. Ĉi tiu procezo estis negativa por entreprenoj ĉar ili devis pruvi, ke ilia tasko estas valora. Kaj la disvolva teamo pasigis tempon organizante ĉi tiun vicon. Ĉi tio prenis multan tempon kaj penon, kaj la produkto finfine ne povis ŝanĝiĝi tiel rapide kiel ili dezirus.

Suboptimuma uzo de rimedoj
Gastigante servojn en ununura procezo, ni ĉiam tute kopiis la agordon de servilo al servilo. Ni volis aparte meti la plej ŝarĝitajn servojn por ne malŝpari resursojn kaj akiri pli flekseblan kontrolon pri nia deplojskemo.

Malfacile efektivigi modernajn teknologiojn
Problemo konata al ĉiuj programistoj: estas deziro enkonduki modernajn teknologiojn en la projekton, sed ne ekzistas ŝanco. Kun granda monolita solvo, ĉiu ĝisdatigo de la nuna biblioteko, sen mencii la transiron al nova, fariĝas sufiĉe ne-triviala tasko. Necesas longa tempo por pruvi al la teamestro, ke tio alportos pli da gratifikoj ol malŝparitaj nervoj.

Malfacilo eldoni ŝanĝojn
Ĉi tio estis la plej grava problemo - ni publikigas eldonojn ĉiujn du monatojn.
Ĉiu eldono fariĝis vera katastrofo por la banko, malgraŭ la provoj kaj klopodoj de la programistoj. La komerco komprenis, ke komence de la semajno iuj el ĝiaj funkcioj ne funkcios. Kaj la programistoj komprenis, ke semajno da seriozaj incidentoj atendas ilin.
Ĉiuj havis deziron ŝanĝi la situacion.

Atendoj de mikroservoj


Eldono de komponantoj kiam preta. Livero de komponentoj kiam prete malkomponante la solvon kaj apartigante malsamajn procezojn.

Malgrandaj produktaj teamoj. Ĉi tio estas grava ĉar granda teamo laboranta pri la malnova monolito estis malfacile administrebla. Tia teamo estis devigita labori laŭ strikta procezo, sed ili volis pli da kreivo kaj sendependeco. Nur malgrandaj teamoj povis pagi ĉi tion.

Izoliĝo de servoj en apartaj procezoj. Ideale, mi volis izoli ĝin en ujoj, sed granda nombro da servoj skribitaj en la .NET Framework funkcias nur en Vindozo. Servoj bazitaj sur .NET Core nun aperas, sed estas malmultaj el ili ankoraŭ.

Deploja fleksebleco. Ni ŝatus kombini servojn kiel ni bezonas ĝin, kaj ne kiel la kodo devigas ĝin.

Uzo de novaj teknologioj. Ĉi tio estas interesa por iu ajn programisto.

Problemoj de transiro


Kompreneble, se estus facile rompi monoliton en mikroservojn, ne necesus paroli pri ĝi en konferencoj kaj verki artikolojn. Estas multaj malfacilaĵoj en ĉi tiu procezo; mi priskribos la ĉefajn, kiuj malhelpis nin.

La unua problemo tipa por la plej multaj monolitoj: kohereco de komerca logiko. Kiam ni skribas monoliton, ni volas reuzi niajn klasojn por ne skribi nenecesan kodon. Kaj kiam oni moviĝas al mikroservoj, ĉi tio fariĝas problemo: la tuta kodo estas sufiĉe forte kunligita, kaj estas malfacile apartigi la servojn.

Je la komenco de laboro, la deponejo havis pli ol 500 projektojn kaj pli ol 700 mil liniojn de kodo. Ĉi tio estas sufiĉe granda decido kaj dua problemo. Ne eblis simple preni ĝin kaj dividi ĝin en mikroservojn.

Tria problemo — manko de necesa infrastrukturo. Fakte, ni permane kopiis la fontkodon al la serviloj.

Kiel moviĝi de monolito al mikroservoj


Provizante mikroservojn

Unue, ni tuj determinis por ni mem, ke la apartigo de mikroservoj estas ripeta procezo. Ni ĉiam estis postulataj disvolvi komercajn problemojn paralele. Kiel ni efektivigos ĉi tion teknike jam estas nia problemo. Tial ni prepariĝis por ripeta procezo. Ĝi ne funkcios alimaniere se vi havas grandan aplikaĵon kaj ĝi ne estas komence preta por esti reverkita.

Kiajn metodojn ni uzas por izoli mikroservojn?

La unua vojo — movi ekzistantajn modulojn kiel servojn. Ĉi-rilate, ni estis bonŝancaj: jam estis registritaj servoj, kiuj funkciis per la WCF-protokolo. Ili estis apartigitaj en apartajn kunigojn. Ni portis ilin aparte, aldonante malgrandan lanĉilon al ĉiu konstruo. Ĝi estis skribita uzante la mirindan Topshelf-bibliotekon, kiu ebligas al vi ruli la aplikaĵon kaj kiel servon kaj kiel konzolon. Ĉi tio estas oportuna por senararigado ĉar neniuj aldonaj projektoj estas bezonataj en la solvo.

La servoj estis konektitaj laŭ komerca logiko, ĉar ili uzis komunajn asembleojn kaj laboris kun komuna datumbazo. Ili apenaŭ povus esti nomitaj mikroservoj en sia pura formo. Tamen ni povus provizi ĉi tiujn servojn aparte, en malsamaj procezoj. Ĉi tio sole ebligis redukti ilian influon unu sur la alia, reduktante la problemon kun paralela evoluo kaj ununura punkto de fiasko.

Asembleo kun la gastiganto estas nur unu linio de kodo en la Program-klaso. Ni kaŝis laboron kun Topshelf en helpa klaso.

namespace RBA.Services.Accounts.Host
{
   internal class Program
   {
      private static void Main(string[] args)
      {
        HostRunner<Accounts>.Run("RBA.Services.Accounts.Host");

       }
    }
}

La dua maniero asigni mikroservojn estas: krei ilin por solvi novajn problemojn. Se samtempe la monolito ne kreskas, tio jam estas bonega, kio signifas, ke ni moviĝas en la ĝusta direkto. Por solvi novajn problemojn, ni provis krei apartajn servojn. Se estis tia ŝanco, tiam ni kreis pli "kanonajn" servojn, kiuj tute administras sian propran datummodelon, apartan datumbazon.

Ni, kiel multaj, komencis per aŭtentigaj kaj rajtigaj servoj. Ili estas perfektaj por ĉi tio. Ili estas sendependaj, kiel regulo, ili havas apartan datummodelon. Ili mem ne interagas kun la monolito, nur ĝi turnas sin al ili por solvi iujn problemojn. Uzante ĉi tiujn servojn, vi povas komenci la transiron al nova arkitekturo, sencimigi la infrastrukturon sur ili, provi iujn alirojn rilatajn al retbibliotekoj ktp. Ni ne havas teamojn en nia organizo, kiuj ne povis krei aŭtentikigservon.

La tria maniero por asigni mikroservojnTiu, kiun ni uzas, estas iom specifa por ni. Ĉi tio estas la forigo de komerca logiko de la UI-tavolo. Nia ĉefa UI-aplikaĵo estas labortablo; ĝi, kiel la backend, estas skribita en C#. La programistoj periode faris erarojn kaj transdonis partojn de logiko al la UI, kiu devus esti ekzisti en la backend kaj esti reuzata.

Se vi rigardas realan ekzemplon el la kodo de la UI-parto, vi povas vidi, ke la plej granda parto de ĉi tiu solvo enhavas realan komercan logikon, kiu estas utila en aliaj procezoj, ne nur por konstrui la UI-formularon.

La transiro de monolito al mikroservoj: historio kaj praktiko

La vera UI-logiko estas nur en la lastaj du linioj. Ni transdonis ĝin al la servilo por ke ĝi estu reuzita, tiel reduktante la UI kaj atingante la ĝustan arkitekturon.

La kvara kaj plej grava maniero izoli mikroservojn, kiu ebligas redukti la monoliton, estas la forigo de ekzistantaj servoj kun prilaborado. Kiam ni elprenas ekzistantajn modulojn kiel estas, la rezulto ne ĉiam plaĉas al la programistoj, kaj la komerca procezo eble fariĝis malmoderna de kiam la funkcieco estis kreita. Kun refactoring, ni povas subteni novan komercan procezon ĉar komercaj postuloj konstante ŝanĝiĝas. Ni povas plibonigi la fontkodon, forigi konatajn difektojn kaj krei pli bonan datummodelon. Estas multaj profitoj akirantaj.

Apartigi servojn de pretigo estas nesolveble ligita al la koncepto de limigita kunteksto. Ĉi tio estas koncepto de Domain Driven Design. Ĝi signifas sekcion de la domajna modelo en kiu ĉiuj terminoj de ununura lingvo estas unike difinitaj. Ni rigardu la kuntekston de asekuro kaj fakturoj kiel ekzemplon. Ni havas monolitan aplikon, kaj ni devas labori kun la konto en asekuro. Ni atendas, ke la programisto trovos ekzistantan Konton-klason en alia asembleo, referencu ĝin el la Asekuro-klaso, kaj ni havos funkcian kodon. La DRY-principo estos respektata, la tasko estos farita pli rapide uzante ekzistantan kodon.

Kiel rezulto, rezultas, ke la kuntekstoj de kontoj kaj asekuro estas konektitaj. Ĉar novaj postuloj aperas, ĉi tiu kunigo malhelpos evoluon, pliigante la kompleksecon de jam kompleksa komerca logiko. Por solvi ĉi tiun problemon, vi devas trovi la limojn inter kuntekstoj en la kodo kaj forigi iliajn malobservojn. Ekzemple, en la asekura kunteksto, estas sufiĉe eble, ke 20-cifera kontnumero de la Centra Banko kaj la dato, kiam la konto estis malfermita, estos sufiĉaj.

Por apartigi ĉi tiujn limigitajn kuntekstojn unu de la alia kaj komenci la procezon de apartigo de mikroservoj de monolita solvo, ni uzis aliron kiel krei eksterajn APIojn ene de la aplikaĵo. Se ni scius, ke iu modulo devas fariĝi mikroservo, iel modifita ene de la procezo, tiam ni tuj faris alvokojn al la logiko, kiu apartenas al alia limigita kunteksto per eksteraj alvokoj. Ekzemple, per REST aŭ WCF.

Ni firme decidis, ke ni ne evitos kodon, kiu postulus distribuitajn transakciojn. En nia kazo, montriĝis sufiĉe facile sekvi ĉi tiun regulon. Ni ankoraŭ ne renkontis situaciojn kie striktaj distribuitaj transakcioj estas vere necesaj - la fina konsistenco inter moduloj estas sufiĉe sufiĉa.

Ni rigardu specifan ekzemplon. Ni havas la koncepton de orkestranto - dukto, kiu prilaboras la enton de la "aplikaĵo". Li kreas klienton, konton kaj bankkarton siavice. Se la kliento kaj konto estas kreitaj sukcese, sed la kreado de kartoj malsukcesas, la aplikaĵo ne moviĝas al la "sukcesa" statuso kaj restas en la "karto ne kreita" statuso. En la estonteco, fona agado reprenos ĝin kaj finos ĝin. La sistemo estas en stato de nekonsekvenco dum iom da tempo, sed ni ĝenerale estas kontentaj pri tio.

Se okazos situacio, kiam necesas konstante konservi parton de la datumoj, ni plej verŝajne iros por plifirmigi la servon por prilabori ĝin en unu procezo.

Ni rigardu ekzemplon de asignado de mikroservo. Kiel vi povas alporti ĝin al produktado relative sekure? En ĉi tiu ekzemplo, ni havas apartan parton de la sistemo - modulon pri salajroservo, unu el la kodaj sekcioj de kiuj ni ŝatus fari mikroservon.

La transiro de monolito al mikroservoj: historio kaj praktiko

Antaŭ ĉio, ni kreas mikroservon reverkante la kodon. Ni plibonigas iujn aspektojn, pri kiuj ni ne ĝojis. Ni efektivigas novajn komercajn postulojn de la kliento. Ni aldonas API-Enirejon al la konekto inter la UI kaj la backend, kiu provizos alvokon.

La transiro de monolito al mikroservoj: historio kaj praktiko

Poste ni liberigas ĉi tiun agordon en funkciadon, sed en pilota stato. Plej multaj el niaj uzantoj ankoraŭ laboras kun malnovaj komercaj procezoj. Por novaj uzantoj, ni disvolvas novan version de la monolita aplikaĵo, kiu ne plu enhavas ĉi tiun procezon. Esence, ni havas kombinaĵon de monolito kaj mikroservo funkcianta kiel piloto.

La transiro de monolito al mikroservoj: historio kaj praktiko

Kun sukcesa piloto, ni komprenas, ke la nova agordo ja estas realigebla, ni povas forigi la malnovan monoliton de la ekvacio kaj lasi la novan agordon anstataŭ la malnova solvo.

La transiro de monolito al mikroservoj: historio kaj praktiko

Entute ni uzas preskaŭ ĉiujn ekzistantajn metodojn por dividi la fontkodon de monolito. Ĉiuj ili permesas al ni redukti la grandecon de partoj de la aplikaĵo kaj traduki ilin al novaj bibliotekoj, farante pli bonan fontkodon.

Laborante kun la datumbazo


La datumbazo povas esti dividita pli malbone ol la fontkodo, ĉar ĝi enhavas ne nur la nunan skemon, sed ankaŭ amasigitajn historiajn datumojn.

Nia datumbazo, kiel multaj aliaj, havis alian gravan malavantaĝon - ĝian grandegan grandecon. Ĉi tiu datumbazo estis dizajnita laŭ la malsimpla komerca logiko de monolito, kaj rilatoj akumuliĝis inter la tabeloj de diversaj limigitaj kuntekstoj.

En nia kazo, por superi ĉiujn problemojn (granda datumbazo, multaj konektoj, foje neklaraj limoj inter tabeloj), aperis problemo, kiu okazas en multaj grandaj projektoj: la uzo de la komuna datumbaza ŝablono. Datenoj estis prenitaj de tabeloj tra vido, tra reproduktado, kaj ekspeditaj al aliaj sistemoj kie ĉi tiu reproduktado estis necesa. Kiel rezulto, ni ne povis movi la tabelojn en apartan skemon ĉar ili estis aktive uzataj.

La sama divido en limigitajn kuntekstojn en la kodo helpas nin en apartigo. Ĝi kutime donas al ni sufiĉe bonan ideon pri kiel ni malkonstruas la datumojn ĉe la datumbaza nivelo. Ni komprenas, kiuj tabeloj apartenas al unu barita kunteksto kaj kiuj al alia.

Ni uzis du tutmondajn metodojn de datumbaza dispartigo: dispartigo de ekzistantaj tabeloj kaj dispartigo per prilaborado.

Disigi ekzistantajn tabelojn estas bona metodo por uzi se la datumstrukturo estas bona, plenumas komercajn postulojn, kaj ĉiuj estas feliĉaj pri ĝi. En ĉi tiu kazo, ni povas apartigi ekzistantajn tabelojn en apartan skemon.

Fako kun prilaborado necesas kiam la komerca modelo multe ŝanĝiĝis, kaj la tabeloj tute ne plu kontentigas nin.

Disigi ekzistantajn tabelojn. Ni devas determini, kion ni apartigos. Sen ĉi tiu scio, nenio funkcios, kaj ĉi tie la apartigo de limigitaj kuntekstoj en la kodo helpos nin. Kiel regulo, se vi povas kompreni la limojn de kuntekstoj en la fontkodo, evidentiĝas, kiuj tabeloj devus esti inkluditaj en la listo por la fako.

Ni imagu, ke ni havas solvon, en kiu du monolitmoduloj interagas kun unu datumbazo. Ni devas certigi, ke nur unu modulo interagas kun la sekcio de apartigitaj tabeloj, kaj la alia komencas interagi kun ĝi per la API. Por komenci, sufiĉas, ke nur registrado estas farita per la API. Ĉi tio estas necesa kondiĉo por ke ni parolu pri la sendependeco de mikroservoj. Legaj ligoj povas resti tiel longe kiel ne estas granda problemo.

La transiro de monolito al mikroservoj: historio kaj praktiko

La sekva paŝo estas, ke ni povas apartigi la sekcion de kodo, kiu funkcias kun apartaj tabeloj, kun aŭ sen prilaborado, en apartan mikroservon kaj ruli ĝin en aparta procezo, ujo. Ĉi tio estos aparta servo kun konekto al la monolita datumbazo kaj tiuj tabeloj, kiuj ne rilatas rekte al ĝi. La monolito ankoraŭ interagas por legado kun la forprenebla parto.

La transiro de monolito al mikroservoj: historio kaj praktiko

Poste ni forigos ĉi tiun konekton, tio estas, legado de datumoj de monolita aplikaĵo de apartaj tabeloj ankaŭ estos transdonitaj al la API.

La transiro de monolito al mikroservoj: historio kaj praktiko

Poste, ni elektos el la ĝenerala datumbazo la tabelojn kun kiuj funkcias nur la nova mikroservo. Ni povas movi la tabelojn al aparta skemo aŭ eĉ al aparta fizika datumbazo. Ankoraŭ ekzistas legado inter la mikroservo kaj la monolita datumbazo, sed estas nenio por zorgi, en ĉi tiu agordo ĝi povas vivi sufiĉe longe.

La transiro de monolito al mikroservoj: historio kaj praktiko

La lasta paŝo estas tute forigi ĉiujn ligojn. En ĉi tiu kazo, ni eble bezonos migri datumojn de la ĉefa datumbazo. Kelkfoje ni volas reuzi iujn datumojn aŭ dosierujojn reproduktitajn de eksteraj sistemoj en pluraj datumbazoj. Ĉi tio okazas al ni periode.

La transiro de monolito al mikroservoj: historio kaj praktiko

Fako de prilaborado. Ĉi tiu metodo estas tre simila al la unua, nur en inversa ordo. Ni tuj asignas novan datumbazon kaj novan mikroservon, kiu interagas kun la monolito per API. Sed samtempe restas aro da datumbazaj tabeloj, kiujn ni volas forigi estonte. Ni ne plu bezonas ĝin; ni anstataŭigis ĝin en la nova modelo.

La transiro de monolito al mikroservoj: historio kaj praktiko

Por ke ĉi tiu skemo funkciu, ni verŝajne bezonos transiran periodon.

Estas tiam du eblaj aliroj.

La unua: ni duobligas ĉiujn datumojn en la novaj kaj malnovaj datumbazoj. En ĉi tiu kazo, ni havas datumojn redundo kaj sinkronigado problemoj povas ekesti. Sed ni povas preni du malsamajn klientojn. Unu funkcios kun la nova versio, la alia kun la malnova.

La dua: ni dividas la datumojn laŭ iuj komercaj kriterioj. Ekzemple, ni havis 5 produktojn en la sistemo, kiuj estis konservitaj en la malnova datumbazo. Ni metas la sesan ene de la nova komerca tasko en novan datumbazon. Sed ni bezonos API-Enirejon, kiu sinkronigos ĉi tiujn datumojn kaj montros al la kliento de kie kaj kion akiri.

Ambaŭ aliroj funkcias, elektu laŭ la situacio.

Post kiam ni estas certaj, ke ĉio funkcias, la parto de la monolito, kiu funkcias kun malnovaj datumbazaj strukturoj, povas esti malŝaltita.

La transiro de monolito al mikroservoj: historio kaj praktiko

La lasta paŝo estas forigi la malnovajn datumstrukturojn.

La transiro de monolito al mikroservoj: historio kaj praktiko

Por resumi, ni povas diri, ke ni havas problemojn kun la datumbazo: estas malfacile labori kun ĝi kompare kun la fontkodo, estas pli malfacile kundividi, sed ĝi povas kaj devas esti farita. Ni trovis kelkajn manierojn, kiuj ebligas al ni fari tion tute sekure, sed estas ankoraŭ pli facile erari kun datumoj ol kun fontkodo.

Laborante kun fontkodo


Jen kiel aspektis la fontkoda diagramo kiam ni komencis analizi la monolitan projekton.

La transiro de monolito al mikroservoj: historio kaj praktiko

Ĝi povas esti proksimume dividita en tri tavolojn. Ĉi tio estas tavolo de lanĉitaj moduloj, kromaĵoj, servoj kaj individuaj agadoj. Fakte, ĉi tiuj estis enirpunktoj ene de monolita solvo. Ĉiuj ili estis firme sigelitaj per Komuna tavolo. Ĝi havis komercan logikon, kiun la servoj kunhavis kaj multajn rilatojn. Ĉiu servo kaj kromaĵo uzis ĝis 10 aŭ pli da komunaj asembleoj, depende de ilia grandeco kaj la konscienco de la programistoj.

Ni bonŝancis havi infrastrukturajn bibliotekojn, kiuj povus esti uzataj aparte.

Foje okazis situacio kiam iuj komunaj objektoj fakte ne apartenis al ĉi tiu tavolo, sed estis infrastrukturaj bibliotekoj. Ĉi tio estis solvita per renomado.

La plej granda zorgo estis limigitaj kuntekstoj. Okazis, ke 3-4 kuntekstoj estis miksitaj en unu Komuna asembleo kaj uzataj unu la alian ene de la samaj komercaj funkcioj. Necesis kompreni kie ĉi tio povus esti dividita kaj laŭ kiaj limoj, kaj kion fari poste kun mapado de ĉi tiu dividado en fontkodasembleojn.

Ni formulis plurajn regulojn por la procezo de disigo de kodo.

La unua: Ni ne plu volis dividi komercan logikon inter servoj, agadoj kaj kromprogramoj. Ni volis fari komercan logikon sendependa ene de mikroservoj. Mikroservoj, aliflanke, estas ideale pensataj kiel servoj kiuj ekzistas tute sendepende. Mi kredas, ke ĉi tiu aliro estas iom malŝparema, kaj estas malfacile atingi, ĉar, ekzemple, servoj en C# ĉiukaze estos ligitaj per norma biblioteko. Nia sistemo estas skribita en C#; ni ankoraŭ ne uzis aliajn teknologiojn. Tial ni decidis, ke ni povas permesi uzi komunajn teknikajn asembleojn. La ĉefa afero estas, ke ili ne enhavas fragmentojn de komerca logiko. Se vi havas komfortan envolvaĵon super la ORM, kiun vi uzas, tiam kopii ĝin de servo al servo estas tre multekosta.

Nia teamo estas ŝatanto de domajna dezajno, do cepo-arkitekturo estis bonega por ni. La bazo de niaj servoj ne estas la datuma alirtavolo, sed aro kun domajna logiko, kiu enhavas nur komercan logikon kaj ne havas ligojn kun la infrastrukturo. Samtempe, ni povas sendepende modifi la domajnan asembleon por solvi problemojn rilatajn al kadroj.

En ĉi tiu etapo ni renkontis nian unuan seriozan problemon. La servo devis rilati al unu domajna asembleo, ni volis fari la logikon sendependa, kaj la DRY-principo tre malhelpis nin ĉi tie. La programistoj volis reuzi klasojn de najbaraj asembleoj por eviti duobligon, kaj kiel rezulto, domajnoj komencis esti kunligitaj denove. Ni analizis la rezultojn kaj decidis, ke eble la problemo ankaŭ kuŝas en la areo de la fontkoda konserva aparato. Ni havis grandan deponejon enhavantan la tutan fontkodon. La solvo por la tuta projekto estis tre malfacile kunvenebla sur loka maŝino. Tial, apartaj malgrandaj solvoj estis kreitaj por partoj de la projekto, kaj neniu malpermesis aldoni al ili iun komunan aŭ domajnan asembleon kaj reuzi ilin. La sola ilo, kiu ne permesis al ni fari tion, estis koda revizio. Sed foje ĝi ankaŭ malsukcesis.

Tiam ni komencis moviĝi al modelo kun apartaj deponejoj. Komerca logiko ne plu fluas de servo al servo, domajnoj vere sendependiĝis. Limitaj kuntekstoj estas subtenataj pli klare. Kiel ni reuzas infrastrukturajn bibliotekojn? Ni apartigis ilin en apartan deponejon, poste metis ilin en Nuget-pakaĵojn, kiujn ni metis en Artifactory. Kun ajna ŝanĝo, muntado kaj publikigo okazas aŭtomate.

La transiro de monolito al mikroservoj: historio kaj praktiko

Niaj servoj komencis referenci internajn infrastrukturpakaĵojn sammaniere kiel eksteraj. Ni elŝutas eksterajn bibliotekojn de Nuget. Por labori kun Artifactory, kie ni metis ĉi tiujn pakaĵojn, ni uzis du pakaĵadministrilojn. En malgrandaj deponejoj ni ankaŭ uzis Nuget. En deponejoj kun multoblaj servoj, ni uzis Paket, kiu provizas pli da versio-konsistenco inter moduloj.

La transiro de monolito al mikroservoj: historio kaj praktiko

Tiel, laborante pri la fontkodo, iomete ŝanĝante la arkitekturon kaj apartigante la deponejojn, ni igas niajn servojn pli sendependaj.

Problemoj pri infrastrukturo


Plej multaj malavantaĝoj al transloĝiĝo al mikroservoj rilatas al infrastrukturoj. Vi bezonos aŭtomatan deplojon, vi bezonos novajn bibliotekojn por funkciigi la infrastrukturon.

Mana instalado en medioj

Komence ni instalis la solvon por medioj permane. Por aŭtomatigi ĉi tiun procezon, ni kreis CI/KD-dukton. Ni elektis la kontinuan liveran procezon ĉar kontinua disfaldo ankoraŭ ne estas akceptebla por ni el la vidpunkto de komercaj procezoj. Sekve, sendo por operacio estas farita per butono, kaj por testado - aŭtomate.

La transiro de monolito al mikroservoj: historio kaj praktiko

Ni uzas Atlassian, Bitbucket por konservado de fontkodo kaj Bambuo por konstruado. Ni ŝatas skribi konstruajn skriptojn en Cake ĉar ĝi estas la sama kiel C#. Pretaj pakaĵoj venas al Artifactory, kaj Ansible aŭtomate venas al la testaj serviloj, post kio ili povas esti tuj provitaj.

La transiro de monolito al mikroservoj: historio kaj praktiko

Aparta registrado


Foje, unu el la ideoj de la monolito devis disponigi komunan arbodehakadon. Ni ankaŭ bezonis kompreni kion fari kun la individuaj protokoloj kiuj estas sur la diskoj. Niaj protokoloj estas skribitaj al tekstaj dosieroj. Ni decidis uzi norman ELK-stakon. Ni ne skribis al ELK rekte pere de la provizantoj, sed decidis, ke ni modifos la tekstajn protokolojn kaj skribos la spuran identigilon en ili kiel identigilon, aldonante la servonomon, por ke ĉi tiuj protokoloj estu analizitaj poste.

La transiro de monolito al mikroservoj: historio kaj praktiko

Uzante Filebeat, ni havas la ŝancon kolekti niajn protokolojn de serviloj, poste transformi ilin, uzi Kibana por konstrui demandojn en la UI kaj vidi kiel la voko iris inter servoj. Trace ID multe helpas kun ĉi tio.

Testado kaj sencimiga rilataj servoj


Komence, ni ne plene komprenis kiel sencimigi la evoluantajn servojn. Ĉio estis simpla kun la monolito; ni prizorgis ĝin per loka maŝino. Komence ili provis fari la samon kun mikroservoj, sed foje por plene lanĉi unu mikroservon necesas lanĉi plurajn aliajn, kaj ĉi tio estas maloportuna. Ni rimarkis, ke ni devas translokiĝi al modelo, kie ni lasas sur la loka maŝino nur la servon aŭ servojn, kiujn ni volas sencimigi. La ceteraj servoj estas uzataj de serviloj, kiuj kongruas kun la agordo kun prod. Post senararigado, dum testado, por ĉiu tasko, nur la ŝanĝitaj servoj estas elsenditaj al la testa servilo. Tiel, la solvo estas provita en la formo en kiu ĝi aperos en produktado estonte.

Estas serviloj, kiuj nur funkcias produktadversiojn de servoj. Ĉi tiuj serviloj estas bezonataj en kazo de incidentoj, por kontroli liveron antaŭ deplojo kaj por interna trejnado.

Ni aldonis aŭtomatan testan procezon uzante la popularan bibliotekon Specflow. Testoj funkcias aŭtomate uzante NUnit tuj post deplojo de Ansible. Se la taskokovrado estas plene aŭtomata, tiam ne necesas manlibrotestado. Kvankam foje kroma mana testado ankoraŭ necesas. Ni uzas etikedojn en Jira por determini kiajn testojn fari por specifa afero.

Plie, la bezono de ŝarĝtestado pliiĝis; antaŭe ĝi estis farita nur en maloftaj kazoj. Ni uzas JMeter por ruli testojn, InfluxDB por stoki ilin, kaj Grafana por konstrui procezajn grafikojn.

Kion ni atingis?


Unue, ni forigis la koncepton de "liberigo". Forpasis la dumonataj monstraj eldonoj, kiam ĉi tiu koloso estis deplojita en produktadmedio, provizore interrompante komercajn procezojn. Nun ni deplojas servojn averaĝe ĉiujn 1,5 tagojn, grupigante ilin ĉar ili ekfunkcias post aprobo.

Ne estas fatalaj misfunkciadoj en nia sistemo. Se ni liberigas mikroservon kun cimo, tiam la funkcio asociita kun ĝi estos rompita, kaj ĉiuj aliaj funkcioj ne estos tuŝitaj. Ĉi tio multe plibonigas la sperton de uzanto.

Ni povas kontroli la deplojan ŝablonon. Vi povas elekti grupojn de servoj aparte de la resto de la solvo, se necese.

Krome, ni signife reduktis la problemon kun granda vico da plibonigoj. Ni nun havas apartajn produktteamojn kiuj laboras kun iuj el la servoj sendepende. La Scrum-procezo jam taŭgas ĉi tie. Specifa teamo povas havi apartan Produktposedanton kiu asignas taskojn al ĝi.

Resumo

  • Mikroservoj taŭgas por malkomponi kompleksajn sistemojn. En la procezo, ni komencas kompreni kio estas en nia sistemo, kiaj limigitaj kuntekstoj ekzistas, kie iliaj limoj kuŝas. Ĉi tio ebligas al vi ĝuste distribui plibonigojn inter moduloj kaj malhelpi kodkonfuzon.
  • Mikroservoj provizas organizajn avantaĝojn. Ofte oni parolas pri ili nur kiel arkitekturo, sed iu ajn arkitekturo necesas por solvi komercajn bezonojn, kaj ne memstare. Tial ni povas diri, ke mikroservoj bone taŭgas por solvi problemojn en malgrandaj teamoj, ĉar Scrum estas tre populara nun.
  • Apartigo estas ripeta procezo. Vi ne povas preni aplikaĵon kaj simple dividi ĝin en mikroservojn. La rezulta produkto verŝajne ne estos funkcia. Dediĉante mikroservojn, estas utile reverki la ekzistantan heredaĵon, tio estas, igi ĝin kodon, kiun ni ŝatas kaj pli bone respondas al komercaj bezonoj laŭ funkcieco kaj rapideco.

    Malgranda averto: La kostoj de transloĝiĝo al mikroservoj estas sufiĉe signifaj. Necesis longa tempo por solvi la infrastrukturan problemon sole. Do se vi havas malgrandan aplikaĵon, kiu ne postulas specifan skalon, krom se vi havas grandan nombron da klientoj konkurantaj por la atento kaj tempo de via teamo, tiam mikroservoj eble ne estas tio, kion vi bezonas hodiaŭ. Ĝi estas sufiĉe multekosta. Se vi komencas la procezon per mikroservoj, tiam la kostoj komence estos pli altaj ol se vi komencos la saman projekton kun la disvolviĝo de monolito.

    PS Pli emocia rakonto (kaj kvazaŭ por vi persone) - laŭ ligilo.
    Jen la plena versio de la raporto.

fonto: www.habr.com

Aldoni komenton