Sedan 2019 har det funnits en lag i Ryssland om obligatorisk mÀrkning. Lagen gÀller inte alla varugrupper, och tidsramarna för införandet av obligatorisk mÀrkning för olika varugrupper Àr olika. De första produkterna som omfattas av obligatorisk mÀrkning Àr tobak, skor och lÀkemedel; senare kommer Àven andra produkter som parfymer, textilier och mjölk att lÀggas till. Denna lagstiftningsnyhet har lett till utvecklingen av nya IT-lösningar som gör det möjligt att spÄra hela livscykeln för en produkt frÄn produktion till köp av slutanvÀndaren, bÄde för staten och alla organisationer som sÀljer produkter med obligatorisk mÀrkning.
I X5 har systemet som ska spĂ„ra produkter med mĂ€rkning och utbyta data med staten och leverantörer fĂ„tt namnet âMarkusâ. Vi berĂ€ttar i tur och ordning hur och av vem det har utvecklats, vilken teknologistack det har och varför vi har anledning att vara stolta.

Verklig HighLoad
âMarkusâ löser mĂ„nga uppgifter, den viktigaste av dem Ă€r integrationsinteraktionen mellan informationssystemen hos X5 och statens informationssystem för mĂ€rkta produkter (GIS MP) för att spĂ„ra rörelsen av mĂ€rkta produkter. Plattformen lagrar ocksĂ„ alla mottagna mĂ€rkningkoder och hela historien om dessa koders rörelse mellan objekt, och hjĂ€lper till att eliminera förvĂ€xlingar med mĂ€rkta produkter. Till exempel, med tobaksprodukter som ingĂ„r i de första grupperna av mĂ€rkta varor, innehĂ„ller endast en bil med cigaretter cirka 600 000 paket, som vart och ett har sin egen unika kod. VĂ„r uppgift Ă€r att spĂ„ra och verifiera lagligheten av flytten av varje sĂ„dant paket mellan lager och butiker, och i slutĂ€ndan verifiera möjligheten att sĂ€lja dem till slutkonsumenten. Vi registrerar cirka 125 000 kassatransaktioner i timmen, och vi mĂ„ste Ă€ven registrera hur varje paket kom in i butiken. Med tanke pĂ„ alla flyttningar mellan objekt förvĂ€ntar vi oss tiotals miljarder poster per Ă„r.
Team M
Trots att "Markus" betraktas som ett projekt inom X5, genomförs det utifrÄn en produktansats. Teamet arbetar enligt Scrum. Projektet startade sommaren förra Äret, men de första resultaten kom först i oktober - teamet sattes samman, systemarkitekturen utvecklades och utrustning köptes in. Idag bestÄr teamet av 16 personer, varav sex arbetar med backend- och frontendutveckling, tre med systemanalys. Ytterligare sex personer ansvarar för manuell, belastning, automatiserad testning och produktunderhÄll. Dessutom har vi en SRE-specialist.
I vÄrt team skriver inte bara utvecklare kod, nÀstan alla i gruppen kan programmera och skriver automatiserade tester, belastningsskript och automatiseringsskript. Vi lÀgger stor vikt vid detta, eftersom Àven produktunderhÄll krÀver en hög nivÄ av automatisering. Vi försöker alltid hjÀlpa kollegor som tidigare inte programmerat genom att ge dem smÄ uppgifter.
Med anledning av coronaviruspandemin har vi övergÄtt till distansarbete. Tillsammans med vÄr tillgÄng till alla verktyg för att hantera utvecklingen och den uppbyggda arbetsflödet i Jira och GitLab, lyckades vi enkelt klara denna fas. De mÄnader som spenderades pÄ distans visade att teamets produktivitet inte pÄverkades, för mÄnga ökade komforten i arbetet, det enda som saknas Àr det personliga mötet.
Teamets möte före distansarbetet

Möten under distansarbetet

Den tekniska stacken för lösningen
Den standardrepository och CI/CD-verktyget för X5 Àr GitLab. Vi anvÀnder det för att lagra koden, kontinuerlig testning, och distribution pÄ test- och produktionsservrar. Vi tillÀmpar Àven en code review-praxis, dÀr minst tvÄ kollegor mÄste godkÀnna de förÀndringar som utvecklaren gjort i koden. De statiska kodanalysatorerna SonarQube och JaCoCo hjÀlper oss att hÄlla koden ren och sÀkerstÀlla att den nödvÀndiga nivÄn av enhetstestning upprÀtthÄlls. Alla kodÀndringar mÄste först genomgÄ dessa kontroller. Alla testscenarier som körs manuellt automatiseras senare.
För att framgÄngsrikt genomföra affÀrsprocesserna med "Markus" var vi tvungna att lösa ett antal tekniska utmaningar, en i taget.
Uppgift 1. Behov av horisontell skalbarhet i systemet
För att lösa denna uppgift valde vi en mikrotjÀnstarkitektur. Det var mycket viktigt att förstÄ ansvarsomrÄdena för tjÀnsterna. Vi försökte dela upp dem utifrÄn affÀrsoperationer med hÀnsyn till processernas specifik. Till exempel, mottagningen pÄ lagret Àr en ovanlig, men mycket omfattande operation, dÀr man snabbt mÄste fÄ information frÄn myndigheterna om de mottagna varorna, dÀr antalet i en leverans kan nÄ upp till 600000, kontrollera om det Àr tillÄtet att ta emot dessa varor pÄ lagret och ge all nödvÀndig information till lagrets automatiseringssystem. à andra sidan har utfoutskick frÄn lagren en mycket högre intensitet, men arbetar med mindre datavolymer.
Vi implementerar alla tjÀnster enligt stateless-principen och försöker Àven dela upp interna operationer i steg, genom att anvÀnda det vi kallar self-Àmnen i Kafka. Detta innebÀr att mikrotjÀnsten skickar meddelanden till sig sjÀlv, vilket gör att belastningen pÄ mer resurskrÀvande operationer kan fördelas och förenklar produktunderhÄll, men mer om detta senare.
Vi beslutade att separera modulerna för interaktion med externa system i egna tjÀnster. Detta löste problemet med snabbt förÀnderliga API:er i externa system, nÀstan utan pÄverkan pÄ tjÀnster med affÀrsfunktionalitet.

Alla mikrotjÀnster distribueras i en OpenShift-kluster, vilket bÄde hanterar problem med skalning av varje mikrotjÀnst och gör att vi kan undvika att anvÀnda externa verktyg för Service Discovery.
Uppgift 2. Behov av att stödja hög belastning och mycket intensiv datakommunikation mellan plattformens tjÀnster: bara i projektets startfas genomförs cirka 600 operationer per sekund. Vi förvÀntar oss att detta vÀrde ökar till 5000 op/s i takt med att handelsobjekten kopplas till vÄr plattform.
Denna uppgift löstes genom att implementera en Kafka-kluster och praktiskt taget helt överge synkron interaktion mellan plattformens mikrotjÀnster. Detta krÀver en mycket noggrann analys av systemets krav, eftersom inte alla operationer kan vara asynkrona. Vi skickar inte bara hÀndelser via en broker, utan överför ocksÄ all nödvÀndig affÀrsinformation i meddelandet. DÀrför kan storleken pÄ meddelandet uppgÄ till flera hundra kilobyte. BegrÀnsningen av meddelandestorlek i Kafka krÀver att vi exakt kan förutsÀga meddelandestorlekar, och vid behov delar vi upp dem, men uppdelningen Àr logisk och relaterad till affÀrsoperationer.
Till exempel, för varor som anlĂ€nder med bil delar vi upp dem i lĂ„dor. För synkrona operationer avsĂ€tts separata mikrotjĂ€nster, och noggranna prestandatester utförs. AnvĂ€ndningen av Kafka stĂ€llde en annan utmaning för oss â att kontrollera hur vĂ„r tjĂ€nst fungerar med tanke pĂ„ integrationen av Kafka gör alla vĂ„ra enhetstester asynkrona. Denna uppgift löste vi genom att skriva egna tillgĂ€ngliga metoder med Embedded Kafka Broker. Detta avskaffar inte behovet av att skriva enhetstester för separata metoder, men för komplexa fall föredrar vi att testa med Kafka.
Vi har lagt mycket fokus pÄ loggtracing sÄ att deras TraceId inte gÄr förlorade vid undantag i tjÀnsternas drift eller vid hantering av Kafka-batchar. Om det första inte orsakade nÄgra sÀrskilda frÄgor, var vi i det andra fallet tvungna att logga alla TraceId som batchen kom med och vÀlja ett för att fortsÀtta spÄrningen. DÄ kan anvÀndaren enkelt hitta det ursprungliga TraceId och se vilket spÄrning som fortsatte.
Uppgift 3. Behovet av att lagra stora mÀngder data: mer Àn 1 miljard mÀrkningar per Är, bara för tobak, kommer in i X5. Dessa krÀver stÀndig och snabb Ätkomst. Totalt ska systemet hantera cirka 10 miljarder poster av datarörelser för mÀrkta varor.
För att lösa den tredje uppgiften valdes NoSQL-databasen MongoDB. Vi har byggt ett sharded kluster med 5 noder, och i varje nod finns en Replica Set med 3 servrar. Detta möjliggör horisontell skalning av systemet genom att lÀgga till nya servrar i klustret och sÀkerstÀlla dess fel-tolerans. HÀr stötte vi pÄ ett annat problem - att sÀkerstÀlla transaktionalitet i ett mongo-kluster med tanke pÄ anvÀndningen av horisontellt skalbara mikrotjÀnster. Till exempel Àr en av vÄra systems uppgifter att upptÀcka försök till ÄterförsÀljning av varor med samma mÀrkning. HÀr uppstÄr problem med felaktiga skanningar eller felaktiga kasörer. Vi har upptÀckt att sÄdana dubbletter kan uppkomma bÄde inom en behandlad Kafka-batch och inom tvÄ parallellt behandlade batches. DÀrför gav kontrollen av dubbletter genom en databasfrÄga ingenting. För varje mikrotjÀnst hanterade vi problemet separat baserat pÄ affÀrslogiken för den tjÀnsten. Till exempel har vi för kvitton lagt till en kontroll inom batchen och en separat hantering för att upptÀcka dubbletter vid insÀttning.
För att anvÀndarnas arbete med transaktionshistorik inte skulle pÄverka det mest centrala - funktionen av vÄra affÀrsprocesser, har vi separerat all historisk data i en sÀrskild tjÀnst med en sÀrskild databas, som ocksÄ fÄr information genom Kafka. PÄ sÄ sÀtt arbetar anvÀndarna med en isolerad tjÀnst utan att pÄverka de tjÀnster som hanterar data om aktuella transaktioner.
Uppgift 4. Ombehandling av köer och övervakning:
I distribuerade system uppstÄr oundvikligen problem och fel med tillgÄng till databaser, köer och externa datakÀllor. I fallet med "Markus" Àr kÀllan till sÄdana fel integrationen med externa system. Det var nödvÀndigt att hitta en lösning som skulle möjliggöra omförfrÄgningar vid felaktiga svar med en viss timeout, men utan att avbryta behandlingen av framgÄngsrika förfrÄgningar i den primÀra kön. För detta valdes den sÄ kallade konceptet "topic based retry". För varje huvudtopic skapas en eller flera retry-topics dit felaktiga meddelanden dirigeras, vilket samtidigt utesluter fördröjning i behandlingen av meddelanden frÄn huvudtoppen. Kommunikationsschemat -

För att genomföra denna lösning behövde vi integrera den med Spring och undvika kodduplicering. Vi stötte pÄ en liknande lösning online, baserad pÄ Spring BeanPostProcessor, men den verkade onödigt tungrodd. VÄrt team skapade en enklare lösning som gör det möjligt att integrera med Spring-cykeln för att skapa konsumenter och dessutom lÀgga till Retry-konsumenter. Prototypen av vÄr lösning presenterade vi för Spring-teamet, den kan ses . Antalet Retry-konsumenter och antalet försök för varje konsument kan konfigureras via parametrar, beroende pÄ behov inom affÀrsprocessen, och för att allt ska fungera krÀvs bara den vÀlkÀnda annoteringen org.springframework.kafka.annotation.KafkaListener som alla Spring-utvecklare kÀnner till.
Om ett meddelande inte kunde bearbetas efter alla retry-försök gÄr det till DLT (dead letter topic) med hjÀlp av Spring DeadLetterPublishingRecoverer. PÄ begÀran av supporten utvidgade vi denna funktion och skapade en separat tjÀnst som gör det möjligt att se meddelanden som hamnat i DLT, stackTrace, traceId och annan nyttig information om dem. Dessutom har övervakning och larm pÄ alla DLT-topiker lagts till, och i dagslÀget Àr ett meddelande i DLT-topiken i stort sett en anledning till att göra en felanmÀlan. Detta Àr mycket praktiskt - genom topikens namn förstÄr vi direkt vilket steg i processen problemet uppstod, vilket avsevÀrt pÄskyndar sökandet efter dess rotorsak.

Nyligen implementerade vi ett grÀnssnitt som gör det möjligt att Äterigen skicka meddelanden med hjÀlp av vÄr support, efter att orsaken till problemen ÄtgÀrdats (till exempel ÄterstÀllande av den externa systemets funktionalitet) och givetvis gör en motsvarande felanmÀlan för analys. HÀr kom vÄra self-topiker till nytta, för att inte behöva starta om hela kedjan av bearbetning kan den startas om frÄn det önskade steget.

Drift av plattformen
Plattformen Àr redan i produktiv drift, varje dag genomför vi leveranser och frakt, samt kopplar nya distributionscentra och butiker. Inom ramen för piloten fungerar systemet med produktgrupperna "Tobak" och "Skor".
Hela vÄrt team deltar i piloterna, analyserar de problem som uppstÄr och kommer med förslag pÄ förbÀttringar av vÄr produkt, frÄn att förbÀttra loggar till förÀndringar i processerna.
För att undvika att upprepa sina misstag Äterspeglas alla fall som upptÀckts under piloten i automatiserade tester. NÀrvaron av ett stort antal automatiserade tester och enhetstester möjliggör regressionsprovning och att tillÀmpa en hotfix bokstavligt talat inom nÄgra timmar.
Just nu fortsÀtter vi att utveckla och förbÀttra vÄr plattform och stöter stÀndigt pÄ nya utmaningar. Om du Àr intresserad kan vi berÀtta om vÄra lösningar i kommande artiklar.
KĂ€lla: habr.com
