Projekt u sklopu internship-a u kojem je cilj bio učenje određenih koncepata i tehnologija u programiranju.
Ideja ovog projekta je izrada servisa koji bi imao kontrolu nad sesijom. Sesiju možemo definirati kao jedno gledanje videa nekog sadržaja. Sesija je entitet koji ima svoj identifikator te stanja koja se mijenjaju kroz PING. Stanja sesije mogu biti sljedeća: PLAY, PAUSE, UNSTARTED, VIDEO_CUED,ENDED i CLOSED koja dobivamo iz događaja na playeru. Sesija započinje prilikom play-a playera te traje do gašenja playera. Ukoliko PING ne dolazi 60 sekundi, servis treba zatvoriti sesiju. Potrebno je održavati sesiju kroz PING svakih 30 sekundi te slati status playera kako imali zapis što se izvodi.
Rješenje projekta dijelimo na 5 komponenata: klijentska aplikacija, web servis, cache, baza i backgroundworker.
Ove dvije komponente odrađuju isti zadatak, stoga se pokreće samo jedna od njih:
BackgroundWorker: omogućava periodičku obradu podataka te prebacivanje isteklih sesija u bazu.
Hangfire: komponenta koja odrađuje isti posao kao BackgroundWorker. Na Hangfire server je uz pomoć Hangfire klijenta i RecurirringJob-a postavljen posao (Job) koji se obavi svake minute - pregledava u cache-u istekle sesije te one koje su istekle sprema u SQL bazu.
Na slici možemo vidjeti prikaz navedenih komponenata aplikacije, gdje je na 5 komponenata dodan još StackExchange.Redis kao Redis klijent za C# i Dapper kao ORM koji služi mapiranju između baze i C#-a.
Kako bi se simulirao realni scenarij korištenja API-a, cache-a i baze, potrebno je bilo izraditi load test koji bi sadržavao prikaz potencijalnog rada ovih komponenti. Load test bi prikazao perfomanse izvođenja ovog sustava, a za samo modeliranje i izvedbu testa koristit ćemo JMeter.
Jedan od scenarija koji be testirao ponašanje komponenata te mjerio perfomanse istih glasi ovako:
Navedeni scenarij izrađen je u JMeter-u, kao što i vidimo na slici ispod. U Test Plan stavili smo novu Thread grupu ("Sesija") u kojo smo specificirali 1000 thread-ova te ukupno trajanje od 1h. Na samom početku ove grupe specificirali smo u HTTP Header Manager-u Content-Type koji će prihvaćati json (application/json) te u HTTP Request Defaults-u specificirano je ime servera, port number i protokol. Sada dolazimo do dijela gdje određujemo što će svaki thread raditi: "Once Only Controller" upravlja POST request-om te dogoditi će se samo jednom, što znači pokrenuti ćemo sesiju (postavljamo objekt u cache te ima status "PLAY" te ID sesije će biti generiran s UUID funkcijom). Nadalje dolazimo do "Loop Controller-a" koji se odvija N puta (N je zapravo neki random broj od 5 do 20 kojeg generiramo u "Random Number of Pings"). S "Random Controller-om" odabiremo jedan od PUT request-ova koji će biti poslani (šalje se status "RESUME", "SEEK" ili "PAUSE"). Unutar ovog kontrolera smješten je i timer koji će pauzirati odvijanje loop-a na 30 sekundi. Još jedan element u ovom kontroleru je "HTTP Request Defaults" koji preuzima ime servera, port i protokol od elementa konfiguriranog na samom početku ove grupe. U njega je samo dodatno je smještena putanja (PATH) za dohvaćanje resursa (endpoint). Zadnji kontroler je "Throughput Controller" koji se odvije jednom za svaki thread te postavlja na 70% thread-ova prekid sesije (PUT Request s statusom "FINISHED"). Tu je isto element "HTTP Request Defaults" u koji smještamo putanju do resursa.
Na kraju Thread grupe imamo dva reporta: "View Results Report" i "Aggregate Report". Prema njima možemo doći do nekih zapažanja i zaključaka. Prema "View Result Report" se može vidjeti jesu li zahtjevi uspješno izvršeni, ali to isto, samo preglednije možemo vidjeti na ""Aggregate Report-u", koji je prikazan na slici ispod. Vidimo da je izvršeno 1000 POST zahtjeva, što znači da su svi thread-ovi uspješno pokrenuli svoju sesiju. Potom vidimo PUT zahtjeve (Resume, Pause, Seek) koji su se random slali svakih 30 sekundi. Prema PUT zahtjevu za završetak sesije vidimo da je 700 sesija prekinuto (status FINISHED) što je upravo ono što smo zadali (70%). Ukupni broj zahtjeva je 14277 koji su se odvili u 10 minuta i 53 sekunde što je zadovoljavajuće. U stupcu "Error %" vidimo da je postotak grešaka za sve metode 0.00 %.
Pozadinski dio ovog testa je isto uspješan. Background Worker je uspješno periodički (svakih 60 sekundi) obavljao perzistenciju - provjeravao istekle sesije te sesije sa statusom FINISHED, iste spremao u bazu te brisao ih iz cache-a.
Kako bi pokrenuli sve komponente, dovoljno je preuzeti docker-compose.yaml datoteku koja se nalazi u glavnom direktoriju ovog repozitorija. Nakon preuzimanja, navedenu datoteku možemo spremiti u određeni folder na našem računalu. Uz pomoć omiljenog terminala navodimo se do tog foldera te ulazimo u njega. Uz preduvjet da je Docker već instaliran na računalu, s naredbom "docker-compose up --build" možemo preuzeti slike i pokrenuti kontejnere.
Na slici ispod vidimo da je pokrenuto svih 5 komponenata te možemo vidjeti nazive kontejnera, nazive njihovih image-a te koji su portovi pridjeljeni u kontejnerima.
Za izradu projekta korištene su sljedeće tehnologije:
U ovom dijelu će biti opisana i implementirana event-based komunikacija između mikroservisa. Kada koristimo event-based komunikaciju, mikroservis objavljuje (publish) event kada se nešto značajno dogodi te ostali mikroservisi se pretplate (subscribe) na taj događaj. Kada mikroservis zaprimi event pomoću event handlera, on može uraditi bilo kakvu promjenu te također može sam objaviti novi event. Ovaj publish/subscribe sustav se uglavnom izvodi pomoću implementacije event bus-a. Event bus može biti dizajniran kao sučelje s APIjem potrebnim za pretplatu na evente i objavljivanje event-a. Implementaciju sučelja event bus-a izvest ćemo uz pomoć RabbitMQ-a. RabbitMQ je message broker: on prihvaća i prosljeđuje poruke. RabbitMQ funkcionira kao posrednik između publishera i subscribera kako bi upravljao distribucijom.
Zadatak u kojem bi koristi koncept event-based komunikacije i tehnologiju RabbitMQ glasi ovako: Pri promjeni broja zapisa u cache-u, emitirati event s brojem zapisa na RabbitMQ (publish) te napisati event handlera na servisu SignalR koji će broadcast-at tu informaciju na korisničku aplikaciju.
Rješavanje zadatka je započeto s podizanjem RabbitMQ Event bus-a uz pomoć Dockera. Potom je kreiran shared library naziva "RabbitMqEventBus" u kojem je je kreirana klasa CacheSizeChangedIntegrationEvent koja nasljeđuje IntegrationEvent te ova klasa zapravo predstavlja naš event koji ćemo emitiriati i s kojim ćemo prosljedit podatke. Nadalje kreirano je generičko sučelje IEventBus s metodama Publish i Subscribe koje su izvedene u klasi RabbitMqClient. Nakon implementacije RabbitMqClient klase, event bus je registriran u Startup klasi servisa SessionControl te okidanjem POST metode na kontroleru, publish-a se event na RabbitMQ. U servisu SignalR također je registriran event bus u Startup klasi te se SignalR pretplatio na event publish-an u servisu SessionControl. Za pristupanje tom event-u, kreiran je event handler CacheSizeChangedIntegrationEventHandler te metoda Handle iz koje se broadcast-a informacija na korisničku aplikaciju uz pomoć hub-a.
SignalR je open-source library koji pojednostavljuje dodavanje real-time funkcionalnosti aplikacijama uz pomoć HTTP-a (protokol za komunikaciju između klijenta i servera). Real-time znači da server strana push-a podatke istog trena na sve klijente koji su povezani. SignalR koristi "hubs" - high level pipeline koji omogućuje klijentu i serveru međusobno pozivanje metoda.
Za primjenu SignalR, riješen je sljedeći zadatak: SignalR servis na okidanje eventa CacheSizeChangedIntegrationEvent push-a trenutni broj zapisa u cache-u na korisničku aplikaciju te se taj broj prikazuje u headeru.
Rješavanje zadatka je započeto s kreiranjem novog projekta kojem je dodjeljen naziv SignalRService te odmah su konfigurirane postavke za pokretanje ovog projekta. Nastavili smo s preuzimanjem SignalR library-a. Budući da je SignalR server library već uključen u ASP.NET Core, samo je preuzet SignalR client library za Angular. Nakon toga kreirana je klasa SessionHub koja se izvodi iz klase SignalR Hub. SessionHub klasa je prazna jer zasad ostvarujemo samo jednosmjernu komunikaciju (server prema klijentu), stoga nijedna metoda nije tu kreirana. Potom, u ConfigureService metodi dodan je SignalR u IService kolekciju te u metodi Configure usmjeravamo SignalR zahtjeve na SessionHub uz pomoć putanje /signalr. Kako bi upravljali s HTTP zahtjevima, kreiramo novi kontroler koji ima putanju api/signalr. U kontroleru kreiramo instancu interface-a IHubContext (dependency injection) te s objektom instance može pristupiti i pozvati hub-ove metode. Kreirana je metoda Get u kojoj je pozvana također instanca klase TimerManager gdje je sređen timer. Svi klijenti se mogu subscribe-ati na ime "ShowNumber" kako bi dobivali podatak o broju zapisa u bazi. Na Angular aplikaciji, nakon preuzimanje SignalR library-a, kreiran je servis u kojem startamo konekciju na naš SignalR, dodajemo listenera na metodu "ShowNumber" i šaljemo HTTP zahtjev kako bi dobili odgovor s servera. Podatak o broju zapisa u bazi prikazujemo u Navigation komponenti.
Xunit je open-source alat za testiranje dijelova u .NET Core-u. Unit testovi su provedeni na testnoj klasi koja je kreirana samo za odrađivanje ovog zadatka. Ime klase je Weather te sadrži metodu za testiranje. Metoda "MethodTest" provjerava parametre metode da li su null te provjerava da li lokacija sadrži brojeve. Nadalje, metoda poziva 3rd party servis (Weather API), čeka odgovor te ga evaluira. Rad s servisom treba biti riješen korištenjem Moy frameworka, to znači da ćemo imitirati ponašanje objekata HttpWebRequest i HttpWebRequest te time nećemo stvarati prave objekte (nećemo pozivati servis, čekati odgovor, evaluirati pravi odgovor). Ovo izolira kôd koji testiramo, osiguravajući da radi sam za sebe i da niti jedan drugi kôd neće srušiti testove.
Prvo su provedena tri testa koja su vezana uz provjeru argumenata, a zatim su provedena tri testa koji obuhvaćaju rad s mockanim objektima. U tim testovima prvo su kreirani mockani objekti te zatim je testirano ponašanje dijela metode s servisom.
Hangfire je open-source framework koji pomaže u kreiranju, obrađivanju i upravljanju pozadinskih poslova, tj. operacija koje ne želimo smjestiti na pipeline za obradu zahtjeva (izrada različitih grafikona, obrada slike/ videozapisa, brisanje korisnika, slanje notifikacija, automatska izvješća, održavanje baze podataka). Hangfire podržava sve vrste background tasko-ova - kratkotrajne, dugotrajne, intezivne za procesor i I/O, jednokratne i ponavljajuće. Vrste su: fire-and-forget, delayed, reccuring, continuations, batches i batch continuations.
Za primjenu Hangfire-a, odrađen je posao BackgroundWorkera - provjera isteklih sesija u cache-u i spremanje istih u SQL bazu. Za potrebu obavljanja zadatka kreirana su tri nova projekta: HangfireServer, HangfireClientService i HangfireWorker.
Content type
Image
Digest
Size
91.9 MB
Last updated
almost 6 years ago
docker pull doc2210/background_worker:blatest