performanceevaluering af en microservice og monolitisk...
TRANSCRIPT
1
Performanceevaluering af en
microservice og monolitisk applikation
Af
Michael Birkholm & Jesper Mørup
14-06-2016
Vejleder
Henrik Bærbak Christensen
Abstract:
Denne rapport indeholder en undersøgelse af en
dobbelt implementeret applikation.
Her sammenlignes responstid og througput af disse
applikationer, der bygger på to arkitekturer.
2
Indholdsfortegnelse 1 Motivation ...........................................................................................................3
1.1 Definition af microservice ..................................................................................6
2 Hypotese ..............................................................................................................7
2.1 Afgrænsning .......................................................................................................7
3 Metode ................................................................................................................8
3.1 Web applikation .............................................................................................. 14
3.2 Event drevet microservice .............................................................................. 18
3.3 Driver til test ................................................................................................... 19
4 Analyse og resultater ........................................................................................ 25
4.1 Implementering af RabbitMQ ......................................................................... 25
4.2 Docker ............................................................................................................. 26
4.3 Docker compose ............................................................................................. 29
4.4 JMeter ............................................................................................................. 31
4.5 Test af den monolitiske applikation ................................................................ 32
4.6 Test af microservice applikation ..................................................................... 34
5 Diskussion ......................................................................................................... 38
6 Relateret arbejde .............................................................................................. 44
7 Konklusion ......................................................................................................... 45
8 Referencer......................................................................................................... 47
9 Bilag ................................................................................................................... 48
9.1 Docker compose - microservice ...................................................................... 48
9.2 Docker compose – monolitiske ....................................................................... 50
9.3 Gradle .............................................................................................................. 51
9.4 Implementering af RabbitMQ ......................................................................... 55
9.5 Konfiguration af performance test ................................................................. 59
9.6 JMeter opsætning af testplan ......................................................................... 62
9.7 Opsætning af Amazon instanser ..................................................................... 67
3
1 Motivation Siden begrebet microservices blev introduceret af Dr. Peter Rodgers i 2005 til
Cloud Computing Expo, har der ikke været den store årvågenhed før 2012
hvor det langsomt begyndte at blive interessant igen, i takt med at der
begyndte at vise sig samstemmende udfordringer med den gennemprøvede
lagdelte arkitektur.
Efterhånden som etablerede virksomheder såsom Microsoft, Netflix, Ebay,
Amazon mv. fik øjnene op for fordelene ved microservice arkitekturen, har
det medført et enormt momentum igennem it verdenen.
Det kunne også ses på bl.a. goto og javazone konferencerne hvor mange
workshops omhandlede microservices.
Der har indtil starten af 2016 ikke været begrænset med videnskabelige
dokumenter omhandlende microservices.
Fordelene og ulemperne ved microservices er løst beskrevet på diverse sider,
også af kendte personer indenfor IT, blandt andet Martin Fowler [Martin
Fowler, Microservices] og Chris Richardson.
En af de påståede fordele ved microservices, er at man kan opnå bedre
performance gennem skalering, end man kan med den almindelig brugte
lagdelte monolitiske arkitektur.
Martin Fowler har dog skrevet at kommunikationsformen i en microservice
arkitektur gør at det ikke performer så godt. Vi har derfor sat os for at
undersøge hvor stor en indflydelse denne arkitektur har på performance,
samt hvad der skal til for at en micorservice arkitektur kan performe bedre.
Vi har igennem fagpakken ”Software architecture in Practice” lært omkring
kvalitetsattributter og metoder til at lave målinger af dem.
Mange elementer i tankegangen bag microservices harmonerer også med
fagpakken Reliable Software Architecture in Practice, specielt med Nygaards
taktikker.
En monolitisk web applikation er simpel at deploye, samt at overskue set fra
allocation viewpoint, den har typisk følgende opbygning:
4
:Server
:EksempelApplication
:Server
:Database
Indeholder funktionalitet til:StoreFrontUIAccountingServiceInventoryServiceShippingService
:PC
:Browser http
Det er simpelt at overskue, da der kun er én server som hoster hele
applikationen med alt funktionalitet. Derudover er der en eller flere servere
der hoster en central database.
En typisk opbygning af en arkitektur baseret på microservices er vist
herunder, her ses at applikationen er splittet ud i flere mindre services, som
kan skaleres vertikalt.
Disse services har et lille ansvarsområde, og er afkoblet fra de andre services,
derfor kommunikeres der typisk ved HTTP kald eller gennem en
messagequeue.
:Server
:StoreFrontUI
:PC
:Browser http
:Server
:AccountingService
:Server
:InventoryService
:Server
:ShippingService
5
Der er flere måder at skalere på, Abbott et. al. beskriver det som en terning
[Abbott et. al., Scale Cube], hvor Chris Richardson har lavet nedenstående
illustration [Chris Richardson, Scale Cube]:
Skalering på x aksen sker ved at klone applikationen, dvs. at køre flere
instanser af samme applikation.
Skalering på y aksen sker ved at splitte applikationen i flere dele, opdelingen
kan bl.a. ske på baggrund af ansvarsområde.
Slutteligt kan skalering ske på z aksen, ved at partitionere de data hver
applikation varetager, så de kun arbejder på et subset af data, der er relevant
for det pågældende request.
Vi har tidligere erfaring med skalering på x aksen, men ikke med skalering på
y aksen hvor man opdeler en applikation.
Derfor finder vi det interessant at se på hvilke indvirkningerne det har på
performance, at skalere på y aksen.
6
1.1 Definition af microservice Før en arkitektur baseret på microservice kan evalueres, skal definitionen af
en microservice først slåes fast. Der er på nuværende tidspunkt ikke nogle
videnskabelige artikler der definerer en microservice.
Derfor findes karakteristika på denne arkitektur, som vil føre til vores
definition af microservices.
En af de vigtige karakteristika er at en microservice har et afgrænset
ansvarsområde. Dette sikrer høj samhørighed, og gør det let for udviklere at
overskue hvad formålet er med servicen, hvilket sikrer maintainability.
Microservices kan udvikles i vilkårlige sprog, derfor sætter dette krav til
kommunikationen for at gøre brug af dem. Det er påkrævet at det er en
sprog uafhængig kommunikation der anvendes, som gerne er letvægts, dvs
fx. REST eller ved brug af en message queue.
Derudover skal microservices kunne deployes uafhængigt af hinanden. Den
løst koblede kommunikation over fx. REST modvirker at lave stærke
bindinger.
Dette gør at en microservice også lettere kan udskiftes, så længe den
overholder samme interface som tidligere.
Ud fra ovenstående kan man konkludere at microservices er overskuelige set
fra module viewpoint. Uafhængige set fra allocation viewpoint. Derudover
har de et klart defineret interface der kommunikeres gennem, set fra
component and connector viewpoint. [Christensen et al, 2012]
Dette står i kontrast til en monolitisk software arkitektur, hvor
ansvarsområdet er større. Dette har indflydelse på kommunikationen, hvor
der nødvendigvis ikke er klare linjer for, hvilken del af applikationen der
kommunikeres med. Omvendt foregår kaldene her typisk som inter-process
kald. Ud fra allocation viewpoint vil man kun have 1 applikation, som
håndterer alt, modsat microservices hvor der er flere, men hvor det står klart
for system administratorerne hvilket ansvar hver enkel service har.
Derudover er der en central database, som står i kontrast til microservices
der ofte har hver deres egen private database.
7
2 Hypotese Denne opgave vil undersøge forskellen i responstid og throughput af en
applikation udviklet af Chris Richardson, som enten kan afvikles som en
monolitisk applikation eller som microservices.
Da der er noget overhead ved at afvikle samme funktionalitet som flere
applikationer i stedet for én, forventer vi at microservices har en negativ
indvirkning på kvalitetsattributten performance, hvis der blot afvikles én
instans af hver service.
Microservices tror vi først giver værdi når der afvikles flere instanser af hver
service, og her forventer vi at man kan opnå bedre throughput og responstid.
Hypotesen er derfor:
Applikationen opbygget over en microservice arkitektur, har en højere
responstid end den monolitisk opbygget webside, ved en kontinuerlig
mængde service kald igennem 30 minutter.
Applikationen opbygget over en microservice arkitektur med 3 instanser af
hver service, har en lavere responstid og højere throughput end den
monolitisk opbyggede webside, ved en kontinuerlig mængde service kald
igennem 30 minutter.
2.1 Afgrænsning Da applikationerne anvender en event store fra tredje part [Eventuate], kan
den have dårlig indvirkning på performance, som vi ikke har mulighed for at
undersøge.
Resultaterne kan ikke sige noget generelt for en vilkårlig microservice eller
monolitisk applikation, da der kan være forskelle i implementering.
8
3 Metode Til at undersøge performance vil der blive anvendt en dobbelt implementeret
applikation. Det vil sige to applikationer der har samme funktionalitet, men
kan afvikles henholdvis som microservices eller en monolitisk applikation.
Til kommunikationen til microservicen vil vi anvende RabbitMQ, da en
message queue giver nogle fordele hvis der skal skaleres horizontalt.
For at have sammenlignelige resultater vil kommunikationen til den
monolitiske applikation også anvende RabbitMQ.
RabbitMQ vil fungere med topic exchange, så hver besked bliver routet til
den relevante service.
For at kunne udføre performance målinger der er reproducerbare og
sammenlignelige, vil Apache JMeter [Apache JMeter] blive anvendt.
JMeter er et værktøj til performance målinger, den fungerer ved at sende
HTTP forespørgsler.
Disse HTTP forespørgsler vil blive sendt til en applikation der bliver udviklet til
formålet, som sender forespørgslen videre til message queuen, som derefter
afleverer det til web applikationen.
Grunden til at der skal anvendes en applikation til at modtage kaldet fra
JMeter og sende det videre, er at denne vil fungere som en test driver.
Dette giver mulighed for i applikationen, at der sendes forskellige parametre
med i disse requests, derved sendes samme forespørgsel ikke hver gang.
Apache anbefaler at man ved remote testing afvikler JMeter Engine tæt på
applikationen der skal testes, ved tæt på mener de samme Ethernet segment
[JMeter FAQ].
JMeter Engine er derved den applikation som udfører load testing, og sender
resultater tilbage til JMeter GUI.
JMeter GUI vil blive afviklet på vores egen computer, og anvendes blot til at
starte de instanser af JMeter Engine der skal bruges til test, samt til at
opsamle resultater.
For hurtigt at kunne opsætte et testmiljø afvikles applikationerne i Docker
containers.
Docker er en virtualiserings applikation oven på operativsystemet, der kan
afvikle containers.
Det sikrer at vores tests er lette at gentage, også på andre miljøer, især hvor
flere applikationer og tilhørende databaser skal integreres.
Under udførelsen af dette vil det være en hjælp at kunne opsætte containers
lokalt, og når alt fungerer kunne afvikle vores Docker containers i det rette
testmiljø og lave performance målinger.
9
Vi har tidligere lavet et projekt i fagpakken Reliable Software Architecture in
Practice om Docker. Her voldte det at have et multi-container setup
problemer, med at linke dem til hinanden, og sikre at de rette porte var åbne
til kommunikation mellem fx. applikation og database.
Til at løse dette problem er der kommet en udvidelse til Docker kaldet Docker
Compose [Docker Compose], som er et værktøj til at håndtere et multi-
container setup og afhængigheder.
Docker containers beskrives i en Dockerfile, som sikrer reproducerbarhed og
samtidig kan fungere som dokumentation af containeren.
Hele multi container setup’et beskrives i en docker-compose fil, som sikrer at
containers startes i den korrekte rækkefølge hvis der er afhængigheder, samt
at porte mellem containers er åbne.
Applikationerne afvikles i Amazon EC2, som er en cloud løsning af IaaS typen.
IaaS står for Infrastructure as a Service, dette betyder at abstraktionslaget for
det virtuelle er hardwaren som Amazon varetager, men at vi har fuld kontrol
over konfiguration af OS og software på maskinerne.
Derfor skal vi selv installere og konfigurere alt software der skal anvendes.
Da der vil blive gjort brug af Docker containers kunne man argumentere for
at anvende en cloud løsning af PaaS typen, som står for Platform as a Service,
hvor Docker var konfigureret af udbyderen og vi blot skulle administrere
vores containers.
Men da JMeter Engine vil blive afviklet på samme netværk som
applikationerne der skal testes, og at Apache fraråder at der kører andet
software på de servere JMeter Engine afvikles på, er der brug for en cloud
løsning af typen IaaS.
På nedenstående allocation view er testmiljøet vist:
10
:Amazon EC2 micro
:PC
:JMeter
:Docker container
:PerformanceTester
:Amazon EC2 micro
:JMeter Engine http
http
:Docker container
RabbitMQ:MessageQueue
:Amazon EC2 medium
:Docker container
:Monolith application
:Docker container
MongoDB:Database
:Amazon EC2 micro
:Docker container
:PerformanceTester
:Docker container
RabbitMQ:MessageQueue
:Amazon EC2 micro
http
:Docker container
:Microservice
:Amazon EC2 micro
:Docker container
:Microservice
:Amazon EC2 micro
:Docker container
:Microservice
:Amazon EC2 micro
:Docker container
MongoDB:Database
11
De enkelte komponenter i testmiljøet er beskrevet herunder:
PC PC der afvikler JMeter, denne er vores egen computer og er ikke placeret ved Amazon.
JMeter Engine Bruges til at udføre loadtest, ved at sende http forespørgsler til PerformanceTester. JMeter Engine har ingen GUI og skal fjernstyres fra JMeter, som den sender resultater fra forespørgslerne tilbage til.
JMeter JMeter er klienten til at udføre loadtest, som modtager målingerne. Den forbinder til JMeter Engine og konfigurerer dem afhængigt af hvilken test der udføres. JMeter kører i 30 minutter for hver test, for at få et retvisende billede af loadtesten. Ud fra de målinger bliver 90% linjen anvendt, det vil sige de 90% som resultaterne ligger indenfor, derved vil grænsetilfælde ikke medtages, da de kan skyldes indvirkning fra baggrundsprocesser, problemer på netværket mm.
Amazon EC2 Serverne hvor applikationer og JMeter afvikles fra. Disse er virtuelle maskiner i Amazons cloud løsning af typen IaaS.
PerformanceTester Webapplikation udviklet i Java til at modtage forespørgsler fra JMeter og sende videre til den applikation som er under test.
Docker container Docker er en container virtualiserings applikation. Docker bruges til at opsætte testmiljøet med de rette applikationer. Dette gøres ud fra en Dockerfile, som kan bruges som dokumentation for hvordan testmiljøet er konfigureret.
Monolith application Den monolitiske udgave af den applikation der skal performance testes.
12
Microservice Microservice dækker over flere individuelle microservices som til sammen udgør samme funktionalitet som den monolitiske applikation.
MongoDB NoSQL database som både den monolitiske og microservice applikationerne gør brug af til at persistere data.
RabbitMQ Message queue som applikationerne får deres requests fra. Dette gør det muligt at afvikle flere Docker containers med microservices som lytter på denne message queue.
Specifikationer på Amazon EC2 t2.medium Instanser:
2 vCPU, Intel Xeon processor med turbo op til 3,3GHz
4GB ram
Netværk oplyser Amazon ikke, serverne er placeret i deres datacenter i
Europa, men da JMeter Engine også afvikles fra Amazons eget netværk
har det ikke den store indflydelse
Til storage anvendes Amazon Elastic Block Store [Amazon EBS]
Ubuntu version 14.04
Specifikationer Amazon EC2 t2.micro instanser:
1 vCPU, Intel Xeon processor med turbo op til 3,3GHz
1GB ram
Netværk, storage og OS som medium instans
Specifikationer på PC:
Intel Core i7-3610QM
16gb DDR3 1600 mhz
Yousee 100/30 mbit/s
SSD harddisk
Windows 10
Da PC’en blot skal indsamle performance målinger fra JMeter Engine, og
dermed ikke skal load teste, er dens performance mindre vigtig. Dog bliver
analyse af de opsamlede data foretaget her, og JMeter Engine skal ikke
13
opleve det som en flaskehals at aflevere de opsamlede resultater, så
performance af maskinen er ikke uvæsentlig.
Specifikationer på anvendte værktøjer:
Apache JMeter 3.0
RabbitMQ 3.6.1
Docker toolbox 1.10.3
MongoDB 3.2
Quality attribute scenario
Til at måle performance vil der blive anvendt Quality attribute scenario,
herefter forkortet QAS. [Bass et al., 2003]
QAS er en måde at beskrive et scenarie på, fra input stimuli, til det målbare
output.
Derved kan der på systematisk vis beskrives de test scenarier, som de to
applikationer vil blive testet under.
QAS beskrives ud fra en skabelon som vist nedenfor, som er tilpasset til at
måle på kvalitetsattributten performance:
Trin i scenarie
Beskrivelse
Source of stimulus
Entitet (internt eller eksternt til systemet, brugere eller programmer).
Stimulus Hvordan ankommer hændelserne. Periodisk, sporadisk eller stokastisk.
Environment Driftstilstand: Normal drift, nøddrift, peak load eller overload
Artifact Artifakten er den del af systemet, der bliver testet.
Response Hvad gør systemet. Fx. Requests bliver processeret. Eventuelt at servicestandarden skifter.
Response Measure
Skal være et målbart udtryk for hvad der er acceptabel svar fra systemet i det givne scenarie. Fx måling på latency, respons tid, throughput eller timeouts.
14
3.1 Web applikation Til at foretage performance målinger har vi valgt at tage udgangspunkt i kode
kreeret af Chris Richardson [Chris Richardson, Money transfer]
[Microservices.io]
Koden er lavet således at den samme applikation er dobbelt implementeret,
det vil sige at det er de samme klasser/metoder der benyttes, programmet er
opbygget i moduler, som enten kan afvikles som en monolitisk applikation,
eller som flere microservices.
Domæne koden er derfor ens, men kan bygges enten som en jar fil, eller 3 jar
filer til hver service.
Dermed passer den perfekt til vores performance målinger da det er selve
arkitekturen vi ønsker at lave performance målinger på, og dermed skal det
ikke baseres på at kodebasen er forskellig.
Applikationerne er udviklet i Java, og anvender Spring Boot [Spring Boot].
Spring Boot er er et framework til at komme hurtigt i gang med Spring
applikationer. Det indeholder embedded webserver, fx. Tomcat eller Jetty, og
gør arbejdet med tredje parts libraries enklere.
Herunder ses den overordnede arkitektur for applikationen, som Chris selv
har beskrevet det. [Chris Richardson, Money transfer]
15
Applikationerne er et eksempel på en lille bank applikation, hvor der kan
oprettes kontonumre, forespørges på kontonumre samt overføre penge
mellem to konti.
Opbygning af applikationerne
Applikationen består af 4 logiske services som vist på diagrammet.
Henholdsvis Accounts, Money Transfers, Account View Reader og Account
View Updater.
Applikationen kan startes på to måder, enten som en samlet Spring Boot jar
fil hvis det skal afvikles som en monolitisk application.
Det kan også afvikles som 3 selvstændige Spring Boot jar filer, hvis det skal
afvikles som microservices.
Her vil der være en service til Accounts command side, dvs. til oprettelse af
kontonumre, som svarer til den logiske service Accounts.
En til transactions command side, til udførsel af transaktioner, denne svarer
til den logiske service Money Transfers.
Herudover er der en service til Account query side, til opdatering og
forespørgsel til databasen, denne svarer til Account View Reader og Account
16
View Updater.
Applikationerne anvender derfor samme kodebase.
Applikationen anvender et pattern kaldet command query responsibility
separation, herefter kaldet CQRS [Martin Fowler, CQRS], sammen med en
event drevet arkitektur, der er beskrevet i afsnit 3.2.
Med CQRS opdeles applikationen i en command side og en query side.
Command side håndterer update requests og publicerer disse som events,
query side subscriber til disse events fra command side, som den opdaterer
databasen med.
Derudover håndterer query side også read forespørgsler.
Det er en utraditionel måde at designe en microservice på, men det har
blandt andet været præsenteret på konferencen microxchg i Berlin 2015
[microxchg 2015].
Normalt ville man designe en service til accounts med tilhørende database,
og en service til transaktioner med sin egen database.
I en forretningsapplikation som dette er et eksempel på, er det dog svært at
sikre konsistens mellem de to databaser i de services, uden en to-faset
commit, hvilket gør at disse services ikke længere er uafhængige.
Med dette pattern er command side og query side uafhængig. Begge sider
har et afgrænset ansvarsområde, de er simple og kan udvikles og skaleres
uafhængigt.
Endpoints
Applikationerne udstiller 3 forskellige REST endpoints, som er følgende:
Account/{kontonummer} – forespørgsel på angivet kontonummer, returnerer
kontonummer og saldo
Account – oprettelse af kontonummer, parameter er ’initial balance’ til at
angive startsaldo, samt kontonummer. Returnerer bekræftelse af oprettelse
ved at skrive kontonummer
Transfer – til at lave overførsel i mellem to kontonumre. Parameter er
’amount’ hvor beløb angives, samt fra kontonummer, og til kontonummer.
Ved korrekt udførelse returneres der et moneytransferid.
Nedenstående diagram viser sekvensen for applikationen når der modtages
et kald til /Account
17
AccountController er ansvarlig for kommunikation med klienten, den laver et
kald til AccountService, som står for at lave kald så der både oprettes og
persisteres den nye konto.
AccountControlleren sender HTTP 200 svar tilbage til klienten.
18
3.2 Event drevet microservice En udfordring med microservices er at der ofte er flere databaser, derfor er
det en udfordring at sikre konsistens af data. Herudover anvendes der typisk
NoSQL databaser, der ud fra CAP theoremet giver afkald på konsistens og i
stedet vægter availability [Gilbert et al., 2002].
Det vil sige at hver service har sin egen private database, og ejerskab over de
data. Databasen behøver ikke være samme type heller, en service kan
anvende en NoSQL database hvor en anden anvender en relationel database.
Dette gør det vanskeligt at lave joins, eller distribuerede transaktioner.
En mulig løsning til dette er at anvende en event drevet arkitektur sammen
med CQRS til sine microservices, hvilket applikationen der anvendes i denne
test har, hvor der anvendes en central Event Store, i denne test anvendes
Eventuate [Eventuate].
Med denne arkitektur opnås ”eventual consistant transactions”.
Transaktioner udføres med et multi-step event drevet workflow, hvor hver
service opdaterer data, og derefter publicerer et event som starter næste
trin.
For at sikre at events er atomare, dur det ikke at man først opdaterer
databasen, og derefter publicerer et event.
Derfor anvendes Event sourcing [Martin Fowler, Event Sourcing], hvilket
betyder at et objekt persisteres ved at gemme en sekvens af events.
Derved kan man genskabe et objekts tilstand ved at genskabe den sekvens af
events der omhandler det objekt.
Eventuate fungerer derfor som en database, hvor den gemmer alle events.
Derudover sørger den for at notificere subscribers om ændringerne, vha.
observer pattern.
En afledt gevinst ved dette er at man på samme tid har implementeret en
audit log.
Eventuate kan man enten afvikle selv, eller bruge en hostet version. Den
hostede version kører på Amazon AWS, hvilket også er her performance
testen udføres. Derfor anvendes den hostede version.
19
3.3 Driver til test Nødvendigheden for at lave en driver – facade
For at kunne lave performance test med et automatiseret værktøj som
JMeter, har vi behov for at kunne lave tilstandsløse kald mod den kørende
web applikation, og dermed måle responstid og throughput.
Web applikationerne baseret på Chris´ kode, som vi har valgt at lave
performance test mod, udstiller 3 forskellige REST services som vi kan bruge
til dette formål. Det giver os dog ét problem, hvilket er at der i alle 3 kald er
behov for at medsende en parameter som det kaldende system, her JMeter,
skal vide.
Ved oprettelse af konto skal der medsendes startbalancen på kontoen, ved
overførsel skal der bruges beløb, fra konto, og til konto, og endelig ved
forespørgsel på konto skal der naturligvis angives hvilket kontonummer der
forespørges på.
Det kan ikke bruges i JMeter da vi ikke kan persistere data og læse derfra for
at lave testkald til applikationen.
For at løse det problem har vi bygget en test driver der fungerer som en
facade udefra mod den kørende applikation.
Vi foretrækker denne metode, for hvis man ændrede applikationen til ikke at
skulle modtage disse parametre, ville dette være en væsentlig ændring.
Derudover er denne metode med en driver mere generisk.
Beskrivelse af driveren
Driveren fungerer således at det er muligt at kalde de 3 eksisterende REST
kald uden at have kendskab til specifikke data på kontonummer samlingen
(collection) i MongoDB.
Driveren er lavet som en Spring Boot applikation og udstiller 3 nye REST
services, som vist herunder :
20
/createTestAccount
Ved kald af denne service, laves et http kald til den kørende web applikation
/account med en fast kodet start saldo på 100000.
Der returneres med en String der angiver at ”kontonummer x er oprettet”
såfremt alt er ok, hvor x er det oprettede kontonummer.
/createTestTransfer
Laver først en count() på accountInfo for at få max antal kontonumre der
bruges, dernæst vælges to tilfældige kontonumre i intervallet 1=min – max
kontonumre der bruges som fra og til kontonummer parameter til /transfer
servicen, samt en fast parameter på 1 til overførselsbeløb.
Der laves kald til /transfer og returneres med en String der angiver
”overførsel i mellem konto x og y er gennemført”
/readTestAccount
Først count() som i den foregående operation for at få max antal, og dernæst
udvælges et tilfældigt tal i intervallet 1=min og max for at få et kontonummer
der kan bruges til at kalde /account service.
Der laves kald til /account, og returneres med en String ”konto : x beløb : y”
21
Klassediagram over Test driveren
Applikationen er lavet som en Spring Boot applikation.
De 3 klasser med annotationen @RestController er de endpoints som JMeter
kan kommunikere med.
Annotationen bliver brugt af Spring Boot, til at udstille denne klasse som et
REST endpoint.
Til at repræsentere en konto findes AccountInfo klassen, hvor
AccountTransactionInfo og AccountChangeInfo repræsenterer transaktioner,
og ændringer i balance.
22
Nedenstående diagram viser sekvensen fra at JMeter sender en request til at
applikationen returnerer et svar:
CreateAccountController
DatabaseAccount service
Http get request til /CreateTestAccount
Besked til accountstopic med data I JSON format:{\initialBalance\ : 1000000 }
Return success
Return accountnumber
JMeter
PerformanceTester:Tomcat server Applikation der performance testes
accountService.openAccount(initialBalance)
db.collection.insert(accountnumber, balance)
HTTP response code 200
Det første request rammer RestControlleren, som er det endpoint der kan
kommunikeres med. Denne sender beskeden videre til applikationen under
test.
Kontoen oprettes og persisteres i databasen, hvorefter der returneres svar til
klienten.
Udfordringer i den eksisterende kode.
For at kunne lave ovenstående ændringer er der behov for at vi kan benytte
et heltal som input til at læse kontonummer, samt lave overførsler. Chris´
kode er dog lavet således at ved oprettelse af kontonummer, dannes der et
unikt tilfældigt UUID, der bruges som _id i mongodb samlingen.
Vi har derfor lavet en mindre rettelse der gør at vi har kontrol over _id ved
oprettelse af kontonumre, og får oprettet kontonumre med fortløbende
numre i intervallet fra 1 - ….
Vi har rettet i AccountQueryWorkFlow således at id i stedet bliver fundet ved
at lave en count på accountInfo samlingen i yourdb databasen, og dernæst
lægge 1 til.
23
AccountQueryWorkFlow.java
@EventHandlerMethod
public Observable<Object>
create(DispatchedEvent<AccountOpenedEvent> de) {
AccountOpenedEvent event = de.event();
//String id = de.getEntityIdentifier().getId();
String id = "" + (accountQueryService.findMaxId() +
1);
String eventId = de.eventId().asString();
logger.info("**************** account version=" + id +
", " + eventId);
BigDecimal initialBalance =
event.getInitialBalance();
accountInfoUpdateService.create(id, initialBalance,
eventId);
return Observable.just(null);
}
AccountQueryService.java
public long findMaxId() {
return accountInfoRepository.count();
}
Metoden findMaxId henter antallet af rækker fra databasen, her kunne man
være i tvivl, om den ændring der er lavet til koden har negative performance
indvirkninger.
Vores overbevisning er at med index på databasen er det en operation der
kan udføres på konstant tid, men for at være sikker på at ændringerne ikke
har haft negative indvirkninger på performance er der blevet udført nogle
tests for at måle eksekveringstiden af denne metode.
Her blev der observeret en nærmest konstant tid for denne funktion, ved
udførsel af test med indsættelse af op til 1 million records.
Dette blev gjort ved en modificering af create metoden til dette:
String id = de.getEntityIdentifier().getId();
Instant start = Instant.now();
String account = "" + (accountQueryService.findMaxId() + 1);
Instant end = Instant.now();
Duration dur = Duration.between(start, end);
System.out.print("tid ialt : " + dur);
Duration er en ny klasse I Java8, dens output er I ISO-8601.
Outputtet er I formatet P<dato>T<tid>.
P for at markere en periode, da vi har med Duration at gøre.
Da vores målinger kun varer sekunder er der intet dato element mellem P og
24
T, derfor starter outputtet med PT<tid>S, hvor S er for at angive at
tidsenheden er sekunder.
Målingerne gav følgende resultater:
Tid med 0 records på MongoDB
PT0.015S
Tid med 1 record på MongoDB
PT0.022S
Tid med 1 million records
PT0.014S
Derfor har de modificeringer til den oprindelige kode ikke negativ indvirkning
på performance, og vil ikke ødelægge testresultaterne.
Test driveren bliver afviklet på samme Amazon server som Rabbit MQ, derfor
vil der ikke være langsomme netværkskald. Indvirkningen test driveren har
på den overordnede performance er derfor minimal, desuden bliver den
brugt til test på begge applikationer, og derfor vil påvirkningen være ens i
begge tilfælde.
25
4 Analyse og resultater
4.1 Implementering af RabbitMQ For at kunne besvare anden del af hypotesen, er det nødvendigt at afvikle
flere instanser af hver microservice.
Chris’ applikation anvender REST endpoints, og ligger derfor ikke op til at
afvikle flere instanser af hver service, da en klient i så fald skulle kende
adressen til hver service for at kommunikere med dem.
Derfor implementeres RabbitMQ til at tage imod requests.
Implementeringen er beskrevet i bilag 9.4.
Hver service vil fungere som consumer på køen, derved kan requests
distribueres mellem alle services når der afvikles flere instanser af hver.
Da der er 3 forskellige services der skal gøre brug af denne message queue,
vil den blive opsat som en topic exchange.
Med en topic exchange bliver beskeder ikke broadcastet til alle services, men
kun routet til de services de er relevante for.
For at få den korrekte responstid fra en besked er afsendt fra publisheren, i
dette tilfælde driveren, til den er udført er det nødvendigt at sende et svar
tilbage fra servicen til driveren gennem message queuen.
Dette bliver gjort ved at implementere en request/reply kø, så svaret først
kommer tilbage til produceren når servicen er færdig.
Da applikationerne i forvejen anvender Spring Boot, er integrationen med
Rabbit også udført i dette, for at fortsætte i samme stil.
Alternativer
Et alternativ til at anvende en message queue kunne være at indføre en
loadbalancer.
Derved kan man opnå samme resultat med at kunne fordele requests mellem
de forskellige services.
Eftersom vi har anvendt Rabbit MQ i fagpakken Reliable Software
Architecture, og dermed opbygget en vis erfaring, faldt valget på den.
Vi har ikke tidligere erfaring med at opsætte fx Nginx til at fungere som HTTP
loadbalancer, og valgte derfor message queuen.
Senere opdagede vi dog at Amazon tilbyder en loadbalancer service [Elastic
Load Balancing]. Ved at bruge denne kunne man have sparet opsætningen af
fx Nginx, der skulle dog formentligt også noget konfiguration til at sætte
dette op.
26
4.2 Docker I forbindelse med udførelse af performance test, er der brug for at deploye
henholdsvis:
den monolitiske opbygning af applikationen.
den microservice baserede version.
test driveren
Rabbit MQ
og sidst en mongoDB database hvor data kan persisteres.
Da vi skal have mulighed for hurtigt at kunne skifte imellem versioner og
konfigurationer stiller det naturligvis nogle udfordringer, for der vil nemt
kunne ske fejl, hvorved man kommer til at køre test på en forkert version
eller konfiguration.
Dette problem løser Docker, da man med Docker kan specificere sin
opsætning i en fil.
Denne fil fungerer derfor både som specifikation af ens konfiguration, samt
sikrer at der ikke sker miskonfigurationer i miljøet, som kan have indvirkning
på testen.
Docker containere er platform uafhængige, derfor giver det mulighed for at
konfigurere miljøet lokalt, og når det er klar til test kan denne konfiguration
provisioneres på serverne der driver test platformen.
I følge kvalitetsattributten for testability i Bass [Bass et al., 2003] bog,
beskriver han bl.a. at tiden det tager at klargøre ens test miljø kan være et
response measure man måler på.
Denne kvalitetsattribut er påvirket positivt af indførslen af Docker, for hvis
dette skulle gøres manuelt vil der være store sandsynligheder for at testen vil
kunne blive afviklet på et forkert grundlag, og derfor ikke valid for nogen
konklusion, samt at det vil være omfattende tidsmæssigt at opsætte et test
miljø.
Derfor er det yderst relevant at anvende Docker til at besvare hypotesen, da
der er flere applikationer og konfigurationer.
27
Opbygning af Dockerfile
De Dockerfiles der anvendes følger samme opbygning, da det de skal gøre, er
at afvikle en jar fil. Derfor er Dockerfilen til den monolitiske applikation vist
herunder, men fremgangsmåden er ens for de andre applikationer, det er
blot en anden jar fil der bliver anvendt.
Her ses Dockerfilen:
FROM frolvlad/alpine-oraclejdk8:slim
VOLUME /tmp
ADD monolithic-service.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-
jar","/app.jar"]
Filen starter med at beskrive at der skal anvendes et image, kaldet
frovlad/alpine-oraclejdk8:slim.
Dette betyder at det er en bruger ved navn frovlad der har lagt dette image i
Dockerhubs repository. Navnet på imaget er alpine-oraclejdk8:slim.
Dette er et image der er baseret på Alpine linux, og indeholder Oracle JDK8.
Den er tagget med slim, da det er en version med kun de nødvendige ting fra
JDK, for at der kan afvikles java applikationer.
Derefter oprettes et mount point under /tmp til brug for applikationen.
Den byggede Jar fil fra udviklingsværkøjet kopieres ind i containeren hvor den
har navnet app.jar. Hvordan dette kopieres fra udviklingsværktøjet er
beskrevet i bilag 9.3 der beskriver build processen.
Entrypoint angiver hvad Docker containeren skal gøre ved start, her startes
applikationen.
Dockerfilen til RabbitMQ og MongoDB anvender de standard konfigurationer
som findes i Docker Hub.
Docker Hub er Dockers repository over Docker containers der kan anvendes.
Integration mellem Docker og Spring Boot
Applikationerne der performance testes, er lavet i Spring Boot.
Spring Boot er et framework der kan importeres med support for Docker.
Dog er der ikke gjort brug af Docker i den oprindelige udgave af
applikationen, derfor skal dette importeres, og da der allerede anvendes
Spring Boot er det mest naturlige at integrere det der.
28
Hvordan Spring boot og Docker er integreret i udviklingsværktøjet, og
anvendes er ligeledes beskrevet i detaljer i bilag 9.3 der omhandler build
processen hvor Gradle indgår.
Applikationen anvender allerede Gradle [Gradle], der er et build automation
værktøj. I bilaget beskrives hvordan afhængighederne til disse 3. parts
værktøjer, som Docker er, refereres i Gradle, så vi sikrer at alle
afhængigheder er inkluderet i builds.
I tidligere fagpakker har vi anvendt ANT som build værktøj, derfor var Gradle
en ny teknologi for os.
At implementere ANT i stedet for ville være spildt arbejde, da der blot skulle
bygges videre på det eksisterende arbejde der var lavet med Gradle.
Men derfor er der en del at skulle sætte sig ind i, og fejlsøgningen er ikke let
når de først opstår ved build tidspunktet, modsat hvis det havde været ved
kompilering.
29
4.3 Docker compose Docker løste problemstillingen med at sikre de rette applikationer, og
konfigurationer under performancetest.
Disse applikationer skal dog kommunikere sammen, dette er endnu et
element som skal konfigureres, og som kan føre til fejl i performancetesten
såfremt dette ikke er konfigureret korrekt.
For henholdsvis den monolitiske og den microservice baserede applikation er
der flere docker containere der skal deployes, hvor der er afhængigheder
imellem dem. For at kunne styre det bedst muligt er der et værktøj der
hedder Docker compose der muliggør dette.
I bilag 9.1 findes docker-compose filen til microservice applikationen, i bilag
9.2 findes filen til den monolitiske applikation.
I Docker compose filerne er der forskellige docker containere der skal startes
op.
Fx applikationen, testdriver, Rabbit MQ og mongoDB.
I en Docker compose fil konfigureres hvilke containere der skal starte, dette
gøres i image der fortæller hvilket image som skal bruges fra dockerhub til at
bygge docker containeren.
For at starte test miljøet op og udføre performance tests skal der bruges
applikationen og test-driveren fra vores docker repository, samt et mongoDB
og Rabbit MQ image fra Dockerhubs eget repository.
I en Docker Compose fil kan man angive Workingdir, Hostname samt
command. Disse er oplysninger der bruges til at fortælle hvordan docker
containeren skal afvikles. I dette tilfælde anvendes det til at angive et dir den
skal pege på, samt java –jar til at starte applikationen i containeren.
Derudover kan man mappe porte, dette gøres med parameteren Ports som
består af to værdier. Den første værdi er den port som applikationen skal
bruge til at få kontakt ud af docker containeren, og den næste værdi er den
port som man kan bruge udefra til at kommunikere ind i docker-containeren.
Til at forbinde sine containere, hvis de skal kommunikere med hinanden kan
man anvende Links. De angiver en dynamisk forbindelse imellem de enkelte
containere, hvilket gør at man ikke behøver at koncentrere sig om ip adresse,
når der skal refereres til de andre docker containere. Dette gøres ved at der
oprettes en mapping i /etc/hosts på source containeren, hvor der laves en
mapping mellem linknavnet og ip-adressen.
Links er dog legacy, det anbefales at man bruger networks i stedet. I vores
30
tilfælde ville det dog kræve at der blev opsat et specifikt network i Docker, i
stedet for at anvende default bridge network.
Dette skyldes at bridge netværket ikke understøtter automatisk service
discovery, som beskrevet sidst i afsnittet af dokumentationen der er
refereret. [Docker Networks]
For enkelthedens skyld er der derfor anvendt links.
Den monolitiske applikation har fx link til mongoDB da den benytter
mongoDB til persistering af data.
Testdriveren har link til RabbitMQ da den laver requests til denne.
Her er et eksempel fra test driveren, hvor man i stedet for at kende ip
adressen på RabbitMQ blot kan anvende linknavnet i stedet.
Derved skal der ikke hardcodes nogle værdier.
String uri = "http://rabbitmq”;
Testdriver har også et link til mongoDB da vi har lavet et par hjælpeværktøjer
der læser og skriver direkte i mongoDB, deleteAll der sletter alle data i
databasen, samt readAll der læser alt hvad der ligger i databasen.
Derudover kan man angive miljøvariable, med parameteren Environment.
For at kunne kalde mongoDB i Spring Boot skal der tilføjes en miljøvariabel
der angiver ip adressen, samt hvilken database der skal benyttes, ved at
bruge links kan der angives container navn, i stedet for den statiske ip
adresse.
SPRING_DATA_MONGODB_URI: mongodb://mongodb/mydb
Fremgangsmåden der bruges i forbindelse med performance test kan derfor
gøres på følgende måde nu:
Tilretning af koden til applikationen, i den editor man foretrækker.
Ændringerne testes lokalt ved at afvikle kode i f.eks. Eclipse, og har Docker
kørende lokalt.
Når resultatet er tilfredsstillende køres det buildtarget ved navn buildDocker i
Gradle.
På testmaskinen er der kun behov for at der er installeret docker samt
docker-compose, og at docker-compose filen er tilstede.
Hele ens multi container setup startes ved at udføre kommandoen:
docker-compose up
31
4.4 JMeter Da vi er interesserede I at sammenligne responstid og throughput mellem de
to arkitekturer er typen af performance test der bliver anvendt loadtests.
Hver test bliver kørt i 30 minutter i JMeter, alt efter hvilken type af test er der
et variabelt antal readers og writers.
For at alle tråde ikke sender forespørgsler samme tid, men distribuerer
forespørgslerne mere realistisk er JMeter konfigureret med en think time på
1000ms. Derved venter hver reader vilkårligt op til 1000ms, før næste
forespørgsel sendes.
En vigtig ting at bemærke er at når der anvendes flere instanser af JMeter til
at udføre load test, er at antallet af readers/writers der bliver konfigureret i
JMeter bliver propageret til hver instans af JMeter Engine.
Derfor er det ikke en konfiguration af antallet af max readers/writers, men
bliver til antallet af readers/writers pr instans af JMeter.
Konfiguration af distribueret performance testing
Da der indgår flere instanser af JMeter i testen, skal disse konfigures til at
udføre en distribueret test.
De instanser af JMeter der udfører testen afvikles på Amazons servere, men
selve testen startes, samt resultater opsamles fra en computer hos os.
Derfor skal det sikres at der må kommunikeres ind/ud til serverne ved
Amazon, det vil sige at de korrekte porte er åbne.
Instansen af JMeter der starter testen skal også have kendskab til hvilke
andre instanser af JMeter den skal starte testen på, dette skal også
konfigureres.
Hvordan dette er konfigureret til denne performance test, er beskrevet i bilag
9.5.
Testplan
Testplanen i JMeter består af en thread group, som simulerer antallet af
brugere der indgår i testen.
For at requests fra disse brugere skal virke naturligt, er der indsat en tilfældig
forsinkelse mellem deres requests.
Da der er flere requests der skal udføres er hver af disse defineret i JMeter.
Til visualisering af målingerne anvendes et plugin kaldet Blaze Meter.
Opsætning af testplan er beskrevet i detaljer i bilag 9.6.
32
4.5 Test af den monolitiske applikation Følgende QAS er opsat for tests af den monolitiske applikation
Trin i scenarie
Beskrivelse
Source of stimulus
Eksternt
Stimulus Stokastisk
Environment Normal drift
Artifact Account, Query og Transaktion
Response Requests bliver processeret uden tab/timeouts
Response Measure
Responstiden er under 200ms, minimum 50 transaktioner per sekund.
Amazon serveren der afvikler applikation og database fandt vi nødvendig, at
den skulle være en medium instans, da der ikke var nok ressourcer med en
micro instans til at afvikle både applikation samt database, og samtidigt
opfylde response measure i QAS.
En interessant observation ved denne test er at performance falder drastisk
over tid, hvor man kan se at antallet af transaktioner falder samtidigt med at
responstiden stiger.
Den monolitiske applikation har således en rigtig god responstid i starten,
men efterhånden som utilization af serveren stiger, falder ydelsen, hvilket er
naturligt, responstiden i første del af testen er mellem 50 og 75ms.
Da databasen og applikationen er på samme server, giver det den gode
responstid indledningsvis, men gør at cpu utilization hurtigt når et kritisk
niveau.
Nedenfor ses en graf over responstider samt transaktioner pr sekund
33
Denne test understreger vigtigheden af at udføre sin test i en længere
periode, i starten udførte vi performancetesten i 10 minutter for at
kontrollere at alle porte og services fungerede. Som man kan se på den
grafen, ville man ikke have opdaget den faldende performance ved blot at
måle i 10 minutter.
Det gennemsnitlige antal transaktioner pr sekund er 100, med en
gennemsnits responstid på 215ms, 90% linjen ligger ved 671 ms.
Derfor opfylder den ikke response measure i det opstillede QAS.
34
4.6 Test af microservice applikation Test med 1 instans pr service
Samme QAS kan opstilles for tests af microservices:
Trin i scenarie
Beskrivelse
Source of stimulus
Eksternt
Stimulus Stokastisk
Environment Normal drift
Artifact Account, Query og Transaktion
Response Requests bliver processeret uden tab/timeouts
Response Measure
Responstiden er under 200ms, minimum 50 transaktioner per sekund.
Hver instans af de 3 services afvikles på hver deres Amazon micro instans.
Her kan man se at applikationen formår at beholde en nogenlunde konstant
responstid og throughput. I midten af testen er der dog en afvigelse, hvor
applikationerne svarer langsomt. Dette kan skyldes baggrundsprocesser eller
netværk. Mange af de requests vil ligge uden for 90% linjen da de afviger.
Nedenfor ses en graf og responstid og throughput:
Det gennemsnitlige antal transaktioner pr sekund er 111, 90% linjen ligger
ved 96 ms, selvom man kan se på grafen at enkelte requests tager længere
tid end normalt, så holder de sig stadigt inden for response measure i QAS.
35
Sammenlignet med den monolitiske applikation formår den microservice
baserede applikation at holde transaktioner pr sekund på et konstant niveau,
som svarer til samme throughput den monolitiske applikation ydede i første
del af testen.
90% linjen er også lavere for den microservice baserede applikation, men
hvor dette igen skyldes den faldende performance senere i testen, af den
monolitiske applikation.
En sammenligning af responstid ses nedenfor, den blå linje er den
monolitiske applikation, og den grønne den microservice baserede:
Transaktioner pr sekund af de 2 tests er vist i nedenstående graf:
Havde den monolitiske applikation haft mere ydelse så den kunne fastholde
den performance der blev leveret i starten, havde den været overlegen, den
har ca 10ms hurtigere responstid i første del af testen.
Derfor er testen udført igen for microservice arkitekturen, hvor der
eksekveres 3 instanser af hver service.
36
3 instanser af hver service
En microservice arkitektur begynder for alvor at vise positive indvirkninger på
performance, når man afvikler flere instanser af hver service.
Derfor er samme test udført, hvor der afvikles 3 instanser af hver service, på
hver sin Amazon micro instans.
En oversigt over serverne der anvendes til denne test er vist herunder.
Der er således 9 servere til at afvikle services, dertil kommer MongoDB,
RabbitMQ og servere til JMeter.
Her blev antallet af brugere opjusteret til 300, da der ellers ikke var nok
belastning på serverne.
Responstiden og throughput er nogenlunde konstant gennem hele testen,
som vist nedenfor:
37
90% linjen for responstid er på 98ms, og et gennemsnitligt antal
transaktioner pr sekund er 288, der er dog et udfald i starten af testen, der
gør at nogle få requests ikke overholder response measure for QAS, dette
kunne dog skyldes en eventuel baggrundsproces eller andet med netværket.
Responstiden har ikke ændret sig synderligt, fra da der blev afviklet 1 instans
af hver microservice. Men da netværkstrafikken skal gennem samme antal
punkter som før, havde vi ikke forventet en forbedring her. Nedenfor kan
man se responstiderne mellem de to tests, den blå linje er testen med 3
instanser af hver service, og den grønne med 1:
Derimod er transaktioner pr sekund væsentligt forbedret. Det svarer dog ikke
til en 3 dobling, selvom der afvikles 3 gange så mange instanser.
En sammenligning af transaktioner pr sekund med henholdsvis 1 og 3
instanser af hver service er vist nedenfor, den blå linje er for testen med 3
instanser og den grønne med 1:
38
5 Diskussion I forbindelse med at udføre performance test af de to applikationer, har vi
haft nogle udfordringer som har krævet noget tid, vi har beskrevet de større
udfordringer vi er stødt på nedenunder.
Der gik meget tid med at gøre testmiljøet klar før performance målingerne
kunne udføres.
Der blev anvendt en del Amazon instanser, disse skulle også konfigureres så
de kunne afvikle docker containers, og da der indgår mange af disse instanser
har det været tidskrævende.
Hvordan Amazon instanserne er opsat er beskrevet i bilag 9.7.
Vi havde iterativ tilgang til opsætningen af testmiljøet og udviklingen af
testdriveren.
Testdriveren startede med at baserede sig på REST kald, som den monolitiske
applikation allerede kunne modtage, og denne var den simpleste applikation
at deploye.
Herefter udviklede vi en testdriver til test af microservice på 1 server. Det
viste sig dog at overheaded ved at køre samme applikation som microservice,
var så stort, at det ikke var muligt at udføre en valid test.
Dernæst fik vi deployed microservice applikationen på hver sin server, som
har dannet grundlag for den ene test.
For at kunne teste flere instanser af hver microservice, opstod behovet for at
implementere RabbitMQ. Derfor ændrede vi testdriveren, til at gøre brug af
RabbitMQ.
Der har været anvendt en del forskellige teknologier i forbindelse med
opgaven, hvor nogle har været nye for os, især at skulle sætte sig ind i Spring
Boot har taget noget tid.
Der findes mange eksempler og dokumentation til Spring, men at overføre
dette til Spring Boot er ikke trivielt, da der er forskel på de to teknologier.
Warstory med Eventuate
Performance testen blev udført med flere forskellige konfigurationer, og over
flere gange.
Derfor var det nødvendigt at initiere testen flere gange, efter første gang
undrede vi os over at når applikationen blev initieret, så kunne vi se at
databasen begyndte at få flere requests og indsætte dokumenter.
Applikationen og database blev startet lokalt på vores egen PC, for at
fejlsøge, også her kom der en del trafik, så meget at vores egen internet linje
blev maxet ud ved 100mbit downstream.
Efter noget tid stoppede applikationen med at virke.
39
Lidt tid efter skrev Chris Richardson, som har udviklet Eventuate til den email
vi havde registreret os med:
Hi,
Because of the excessive load on the server I have temporarily suspended
your account.
At some point I plan to implement per-user quotas but until I can do that I
just need you limit the number of requests.
Please let me know when you have resolved this issue.
Thanks.
Chris
Vi skrev tilbage til ham og undskyldte naturligvis, og forklarede samtidig at vi
var i gang med en hovedopgave hvor vi blandt andet kiggede på performance
i microservice sammenhæng, og derfor lavede den mængde forespørgsler.
Vi fortalte at vi bruger hans eksempel applikation til at teste med.
Da vi havde haft problemer med at hver gang vi startede applikationen op
igen, startede den med at opdatere mongoDB med alle de tidligere
forespørgsler der har været lavet, efterspurgte vi en løsning til dette
problem, som vi var i gang med at fejlsøge lokalt.
Vi troede det var i forbindelse med en fejl vi havde lavet, og skrev samtidig til
Chris om hvad der kunne være galt, og fik følgende svar :
Thanks for getting back to me.
Very interesting.
It is great that you are using Eventuate.
A few points:
One of the features of event-driven architectures is that you need to process
the events you generate.
However, to make development easier we are working on a way to allow you
to "reset" a subscription to mark events as having been read.
In the meantime, there is an experimental feature known as a space that is
associated with your account.
Think of it as a namespace.
40
You should be able to "forget" about everything you have done so far but
specifying a different space.
The configuration property is eventuate.space (like eventuate.api.key.id) so
you can, for example, specify it using the environment variable
EVENTUATE_SPACE=space2
Please let me know whether this solves the problem.
Thanks.
Chris
Meget positivt svar, der i den grad var med til at hjælpe os videre. Vi har
efterfølgende fået lov til at køre tests i perioden indtil vi skal aflevere
opgaven.
RabbitMQ og Spring Boot
Vi havde brug for at benytte en asynkron forbindelse i applikationen for at
kunne skalere yderligere. Valget blev RabbitMQ, men det viste sig at
dokumentationen for RabbitMQ i Spring Boot var utrolig mangelfuld.
Efter utallige søgninger på nettet, har vi fundet én tutorial på nettet
omhandlende Spring Boot og RabbitMQ.
Denne tutorial viste sig også at være utrolig mangelfuld da den viser et kode
eksempel og langtfra forklarer alt hvad der er gjort i kode eksemplet, derfor
har vi været nødt til at eksperimentere en del, hvilket var enormt
tidskrævende.
Desuden var eksemplet skrevet således at alt koden for både producer og
consumer lå samlet så man ikke havde nogen idé om hvad der skulle bruges
hvor, for at gøre forvirringen komplet var eksemplet også baseret på at
RabbitMQ var installeret og kørte lokalt. Måske for nemheds skyld, men
bidrog absolut ikke til forståelsen, og næppe sådan det ville skulle benyttes i
et rigtigt setup.
Vi havde kigget de tutorials igennem der ligger på RabbitMQ´s hjemmeside,
og der er flere forskellige komponenter som blandt andet connectionfactory,
connection, Channel som vi ikke kunne finde i Spring Boot.
Vi fandt ud af at i Spring Boot får man en connectionfactory, og en
rabbittemplate foræret ved at definere at man har en dependency til ampq.
Det gør at man ikke selv skal tænke på at lave sådanne når man skal benytte
RabbitMQ, men når man ikke er vant til at de ting er abstraheret væk,
forvirrer det.
41
Der er flere forskellige metoder der kan benyttes for at angive adressen på
den RabbitMQ server man ønsker at benytte. Angiver man ikke noget er
<localhost> default.
Man kan i koden i configurationen få fat i den connectionfactory der er
default, og benytte de setmetoder der er på host og port.
Man kan også angive parameteren rabbitmq_server_adress : <ip adresse> i
en application.properties fil.
Da vi bruger docker containere, og docker-compose er der også muligheden
for at bruge den samme parameter rabbitmq_server_adress <ip-adresse>
som env properties i docker-compose filen. Det har den fordel at det afkobler
fuldstændig RabbitMQ server adressen fra koden, og gør at vi ikke skal til at
rette kode, og lave nye docker images når vi ændrer ip adressen på en
RabbitMQ server.
Gradle og Eclipse
Vi har brugt Eclipse og gradle i forbindelse med udvikling og tilretning af
kode. Da vi skulle tilføje RabbitMQ til applikationen blev det gjort ved at vi
tog udgangspunkt i én enkelt af de 3 services, fik rettet build.gradle filen til
med den nødvendige dependency, og rettede koden til, og efterfølgende
testet af. Som tidligere beskrevet i afsnittet omkring RabbitMQ i Spring Boot,
var der en del vi skulle have prøvet af og rettet til før det kom til at virke, men
det lykkedes for den ene service. Derefter gik vi i gang, måske for hovedløst,
med at kopiere alle de samme rettelser ud til de andre services. Det krævede
selvfølgelig en del rettelser i gradle build filerne, samt kode, og da alle
rettelserne var på plads, prøvede vi at bygge projektet med henblik på at
teste det af.
Det kunne ikke bygge overhovedet, og efterfølgende skete der en del
underlige fejl, blandt andet hvor JRE var fjernet, så den ikke kunne kompilere,
der var ændringer i build path, og en del andre. Vi brugte en del tid på at få
fundet ud af hvad der var galt, og søgninger på blandt andet stackoverflow
hjalp ikke meget, idet at mange skrev at det muligvis var en bug i eclipse.
Et par af fejlene var blandt andet:
The type java.lang.Object cannot be resolved. It is indirectly referenced from
required .class files
Build path contains duplicate entry:
'org.eclipse.jdt.launching.JRE_CONTAINER'
42
Vi læste at man kunne slette alle eclipse filerne, og bygge forfra ved at skrive
gradle cleanEclipse, efterfulgt af gradle buildEclipse, men det hjalp desværre
heller ikke.
Efter at have brugt rigtig meget tid på fejlsøgning, tog vi beslutningen om at
starte fuldstændigt forfra for at være sikker på at vi havde en ren brugbar
version.
Eclipse blev installeret forfra i en ny version, koden blev hentet ned, lavet
versionsstyring i GIT, og så blev rettelserne tilføjet små skridt af gangen, og
testet af hver gang der var lavet tilføjelser, og efterfølgende opdateret
versionen. Det tog noget tid, men så havde vi en version vi var sikre på
virkede.
Konklusionen må være at vi desværre ”glemte” de dicipliner vi lærte i
fagpakken ”Programmering af Store Objektorienterede Systemer” med at
tage små skridt, refaktorisere, og bruge versionsstyring mere aktivt, en vigtig
lektie lært på den hårde måde.
Warstory med gamle Docker images
Vi valgte at bruge docker containere i forbindelse med deployment, for ikke
at skulle koncentrere os om at køre med forkerte konfigurationer i
forbindelse med afvikling af kode i amazon cloud, hvilket dog ikke helt viste
sig at være sandt hver gang.
I forbindelse med en tilretning af koden, var vores normale procedure at vi
rettede koden lokalt, testede lokalt at rettelserne var ok, hvorefter vi
byggede et nyt docker image, og samtidigt uploadede det til dockerhub.
På den måde skulle vi så kun tilrette docker-compose filen på den givne
instans som det eneste, og derefter starte den op, eller genstarte den.
Det vil sige at vi ubesværet kunne stoppe de/den docker containere på den
givne instans der var startet med docker-compose ved ctrl-c, og derefter
starte den/de op igen med kommandoen docker-compose up.
Docker-compose skulle så sammenligne den/de docker images der lå på
instansen, med dem som var refereret i docker-compose filen, og downloade
fra dockerhub hvis der var en nyere version af docker imaget i repositoriet.
Det virker som det skal når vi prøvede det af lokalt på vores egne maskiner,
men vi kunne observere at ændringerne ikke kom med når vi benyttede
samme fremgangsmåde i Amazon instanserne, og det voldte os en del
problemer i forbindelse med testene.
43
Vi har søgt efter en forklaring på nettet, men er ikke blevet meget klogere på
hvad årsagen kan være, et bud kunne være et problem med en cache som
ikke er opdateret, og derfor ikke ser at der findes en ny version af det image.
I stedet fandt vi en måde at slette alt vedr Docker images, og dermed tvinge
den til at hente nye images, så rettelserne kommer med.
Fremgangsmåden er:
docker-compose rm – for at slette de referede docker images i
docker-compose filen.
rm <folder navn ex. Account-command-service> -r – sletter de fysiske
filer.
Docker images – for at få vist de docker images der er hentet.
Kopier image id på docker instansen.
Docker rmi -f <image id> - sletter det pågælde docker image.
Start op igen ved at skrive: docker-compose up.
44
6 Relateret arbejde I fagpakken Software Arkitektur i Praksis, lavede vi som projekt en
performance evaluering af MongoDB.
Værktøjerne til udførelsen af performance test har taget udgangspunkt i Jens
Edlef Møllers guide til Performance Engineering [Møller, 2012], som blev
præsenteret ved fagpakken Software Arkitektur i Praksis.
Dog er der anvendt nyere versioner end der blev præsenteret dengang, samt
efterfølgere til det plugin som Jens henviste til.
45
7 Konklusion Hypotesen
Applikationen opbygget over en microservice arkitektur, har en højere
responstid end den monolitisk opbygget webside, ved en kontinuerlig
mængde service kald igennem 30 minutter.
Responstiden er højere for applikationen med microservices ved de første 10
minutter af testen, sammenlignet med den monolitiske applikation.
Throughput er på samme niveau som den microservice baserede applikation
de første 10 minutter af den monolitiske applikation.
Applikationen med microservices har også samlet set 4 Amazon micro
instanser, mod den monolitiske der har 1 medium instans.
At have MongoDB databasen på samme server som den monolitiske
applikation, har givet både fordele og ulemper.
Fordelen er en lavere responstid, da man slipper for et netværkskald.
I første omgang kan det virke som et unfair setup set ud fra et
sammenligningsgrundlag. Men den monolitiske applikation har blot fået
optimale betingelser for performance. En arkitektur på microservices giver
ikke mulighed for denne optimering.
Ulempen var som man kunne se i løbet af testen at utilization af serveren
steg, hvilket gjorde løsningen langsommere ved kontinuerlig høj belastning.
Kraftigere hardware kunne naturligvis have udsat tidspunktet for hvor meget
load der skulle genereres før dette indtræffer.
Antallet af threads JMeter havde kørende kunne også være sænket, men var
bevidst ikke sat ned til et punkt hvor denne degradering i performance ville
være skjult.
Større applikationer har ofte databasen på en anden server end
applikationen. Dette vil naturligvis øge responstiden, vi valgte derfor de
optimale betingelser.
Kigger man isoleret på de første 10 minutter af testen, har den monolitiske
applikation således lavere responstid end microservice.
Måler man efter 90% linjen over hele testen er microservice applikationen
hurtigere.
Derfor er konklusionen at den microservice baserede applikation kan have
højere responstid end den monolitiske applikation.
Dog er en af fordelene i en microservice arkitektur, at der er lettere mulighed
for at skalere ens services, derfor finder vi næste del af hypotesen
interessant.
46
Hypotesen
Applikationen opbygget over en microservice arkitektur med 3 instanser af
hver service, har en lavere responstid og højere throughput end den
monolitisk opbyggede webside, ved en kontinuerlig mængde service kald
igennem 30 minutter.
Testen med 3 instanser af hver service viste et højere throughput.
Responstiden var nærmest uændret fra testen med 1 instans af hver service,
dog set over hele testen er den lavere end den monolitiske applikation.
Når man ser på allocation viewpoint for denne opsætning, er der en del
netværkskald mellem servere. Derfor kan det være vanskeligt at opnå en
markant lavere responstid.
Responstid og throughput var tilfredsstillende i forhold til den opstillede QAS,
og konklusionen er at med 3 instanser af hver service opnår den microservice
baserede applikation højere throughput, men at responstiden nødvendigvis
ikke er forbedret.
En afledt effekt vi bemærkede under vores tests, var at microservices har en
negativ indvirkning på kvalitetsattributten testability.
Der skulle bruges betydelig ekstra tid på at opsætte et testmiljø med
microservices, end der skulle med den monolitiske.
Selvom Amazons web interface fungerer godt, så tager det alligevel noget tid
at starte fx 9 servere, ssh til dem og starte applikationen.
47
8 Referencer Martin Fowler, Microservices:
http://martinfowler.com/articles/microservices.html
Abbott et. al., Scale Cube:
http://akfpartners.com/techblog/2008/05/08/splitting-applications-or-
services-for-scale/
Chris Richardson, Scale Cube:
http://microservices.io/articles/scalecube.html
Microservices.io: http://microservices.io/
Eventuate: http://eventuate.io/
Martin Fowler, CQRS: http://martinfowler.com/bliki/CQRS.html
microxchg 2015: http://microxchg.io/2015/index.html
Martin Fowler, Event Sourcing:
http://martinfowler.com/eaaDev/EventSourcing.html
Gilbert et al., 2002: http://dl.acm.org/citation.cfm?id=564585.564601
Spring Boot: http://projects.spring.io/spring-boot/
Chris Richardson, Money transfer: https://github.com/cer/event-
sourcing-examples
Docker Compose: https://docs.docker.com/compose/
Docker networks:
https://docs.docker.com/engine/userguide/networking/work-with-
networks/#connect-containers
Apache JMeter: http://jmeter.apache.org/
JMeter FAQ: http://wiki.apache.org/jmeter/JMeterFAQ
Amazon EBS: https://aws.amazon.com/ebs/
BlazeMeter Sense: https://sense.blazemeter.com/features/
Bass et al., 2003: Bass, L., Clements, P., and Kazman, R. (2003). Software
Architecture in Practice, 2nd Edition, Addison Wesley, 2003. ISBN:
0321154959
Gradle: http://gradle.org/
Elastic Load Balancing: https://aws.amazon.com/elasticloadbalancing/
Møller, 2012: https://cs.au.dk/~baerbak/c/saip/resource/pe-tools.pdf
Debian Aptitude:
http://packages.debian.org/search?keywords=aptitude
Christensen et al., 2012:
https://cs.au.dk/~baerbak/c/saip/notes/christensen2012.pdf
48
9 Bilag
9.1 Docker compose - microservice Her er 5 Docker-compose filer. En til hver server.
Links i Docker compose filer virker ikke på tværs af servere. Derfor er
parameteren extra_hosts sat ind, som på samme måde som link indsætter en
mapping i /etc/hosts. Denne værdi skal dog sikres har den rette IP adresse
inden containerne startes.
accountscommandside: image: birkholm/accounts-command-side-service working_dir: /app volumes: - ./accounts-command-side-service/build/libs:/app command: java -jar /app/accounts-command-side-service.jar ports: - "8080:8080" environment: EVENTUATE_API_KEY_ID: 62BB0DU84C3YXAM79KKC9I21E EVENTUATE_API_KEY_SECRET: EVENTUATE_SPACE: space5 SPRING_RABBITMQ_HOST: 52.19.168.215
transactionscommandside: image: birkholm/transactions-command-side-service working_dir: /app volumes: - ./transactions-command-side-service/build/libs:/app command: java -jar /app/transactions-command-side-service.jar ports: - "8080:8080" environment: EVENTUATE_API_KEY_ID: 62BB0DU84C3YXAM79KKC9I21E EVENTUATE_API_KEY_SECRET: EVENTUATE_SPACE: space2 SPRING_RABBITMQ_HOST: 52.19.168.215
accountsqueryside: image: birkholm/accounts-query-side-service working_dir: /app volumes: - ./accounts-query-side-service/build/libs:/app command: java -jar /app/accounts-query-side-service.jar ports:
49
- "8080:8080" extra_hosts: - "mongodb:52.50.56.169" environment: EVENTUATE_API_KEY_ID: 62BB0DU84C3YXAM79KKC9I21E EVENTUATE_API_KEY_SECRET: EVENTUATE_SPACE: space2 SPRING_DATA_MONGODB_URI: mongodb://mongodb/mydb SPRING_RABBITMQ_HOST: 52.19.168.215
rabbitmq:
image: rabbitmq:3-management
hostname: rabbitmq
ports:
- "5672:5672"
- "15672:15672"
testdriverrabbitmq:
image: birkholm/test-driver-rabbitmq
working_dir: /app
hostname: testdriver
command: java -jar /app/test-driver-rabbitmq.jar
ports:
- "8090:8090"
extra_hosts:
- "mongodb:52.50.56.169"
environment:
SPRING_DATA_MONGODB_URI: mongodb://52.50.56.169/mydb
mongodb: image: mongo:3.0.4 hostname: mongodb command: mongod ports: - "27017:27017"
50
9.2 Docker compose – monolitiske Der er to Docker compose filer, da applikationerne skal afvikles fra to
servere.
monolithic:
image: birkholm/monolithic-service
working_dir: /app
hostname: monolithic
command: java -jar /app/java-mono.jar
ports:
- "8080:8080"
links:
- mongodb
environment:
SPRING_DATA_MONGODB_URI: mongodb://mongodb/mydb
SPRING_RABBITMQ_HOST: 52.19.168.215
mongodb:
image: mongo:3.0.4
hostname: mongodb
command: mongod
ports:
"27017:27017"
rabbitmq:
image: rabbitmq:3-management
hostname: rabbitmq
ports:
- "5672:5672"
- "15672:15672"
testdriverrabbitmq:
image: birkholm/test-driver-rabbitmq
working_dir: /app
hostname: testdriver
command: java -jar /app/test-driver-rabbitmq.jar
ports:
- "8090:8090"
extra_hosts:
- "mongodb:52.50.56.169"
environment:
SPRING_DATA_MONGODB_URI: mongodb://52.50.56.169/mydb
51
9.3 Gradle Integration med docker
I hver applikation der skal deployes, hvor der er en tilhørende gradle build fil,
skal der laves ændringer i gradle build scriptet.
I dependencies skal der tilføjes en dependency til docker librariet, hvilket gør
det muligt at udnytte docker relaterede elementer i koden, og i forbindelse
med bygge processen.
Ex. Fra Test-driver applikationen
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-
plugin:1.3.2.RELEASE")
classpath('se.transmode.gradle:gradle-docker:1.2')
}
}
Der tilføjes en group, som er navnet på det docker repository der skal bruges
i applikationen til f.eks. push, pull osv.
group = 'birkholm'
Der skal tilføjes et plugin, så det er muligt at bruge docker specifikke
kommandoer i gradle scriptet.
apply plugin: 'docker'
Og til sidst skal selve der konstrueres selve den task der skal bruges til at lave
arbejdet med at referere til en Dockerfile med oplysninger om
konstruktionen af docker containeren, samt hvad der skal gøres med den
52
konstruerede docker container. I det her tilfælde skal der laves en push til
dockerhub repositoriet.
task buildDocker(type: Docker, dependsOn: build) {
push = true
applicationName = jar.baseName
dockerfile = file('build/libs/Dockerfile')
doFirst {
copy {
from jar
into stageDir
}
}
}
Den nye build.gradle script vil efter ændringerne så se sådan ud for Test-
driver applikationen.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-
plugin:1.3.2.RELEASE")
classpath('se.transmode.gradle:gradle-docker:1.2')
}
}
group = 'birkholm'
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
apply plugin: 'docker'
jar {
baseName = 'test-driver'
}
53
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile("org.springframework.boot:spring-boot-starter-web") {
}
compile("org.springframework.boot:spring-boot-starter-data-mongodb")
compile ("org.apache.commons:commons-collections4:4.0")
compile ("org.apache.httpcomponents:httpcore:4.4.1")
compile ("org.apache.httpcomponents:httpclient:4.5")
compile("org.springframework.boot:spring-boot-starter-actuator")
testCompile("junit:junit")
}
task wrapper(type: Wrapper) {
gradleVersion = '2.3'
}
task buildDocker(type: Docker, dependsOn: build) {
push = true
applicationName = jar.baseName
dockerfile = file('build/libs/Dockerfile')
doFirst {
copy {
from jar
into stageDir
}
}
}
I den task er der blandt andet en reference til en dockerfile, i det her tilfælde
ligger den placeret i build/libs/ netop fordi det er der jar filen med selve
applikationen ligger placeret.
54
For at afvikle det nye gradle task skrives der :
gradle buildDocker
I det task der hedder buildDocker er der en dependency til build, så ved
kørsel vil den først kompilere og samle selve projektet, og ved succes, dvs
ingen fejl vil den lave en push til det angivne repository i Docker Hub.
55
9.4 Implementering af RabbitMQ På producer siden skal der gøres følgende for at kreere og sende en besked
til en RabbitMQ server, der er taget udgangspunkt i en basal type
messagequeue med én producer, og én eller flere consumere:
Produceren i dette tilfælde er vores testdriver der sender til RabbitMQ i
stedet for at lave rest kald, som oprindeligt i Chris’ applikation.
For overhovedet at kunne benytte RabbitMQ i Spring Boot skal der angives
en dependency i gradle build filen, til amqp frameworket hvor RabbitMQ er
inkluderet i.
compile("org.springframework.boot:spring-boot-starter-amqp")
Alt omkring configurationen af RabbitMQ laves i en konfigurationsfil i Spring
Boot.
Vi har kaldt vores RabbitConfiguration, og den skal annoteres med
@Configuration.
Der skal angives en instansvariabel for hver af de køer man ønsker at benytte,
ex:
final static String queueNameAccountCommand =
"accountCommand";
En Bean der laver en ny Queue instans udfra navnet og returnerer den, vi har
valgt at sætte durability til false, hvilket betyder at den pågældende kø ikke
overlever en server restart, for at vi ikke skulle risikere at gamle beskeder
påvirker vores test, derudover har det også en negativ indvirkning på
performance at skulle persistere disse beskeder.
@Bean
Queue queueAccountCommand() {
return new Queue(queueNameAccountCommand, false);
}
En Bean der definerer navnet på den Exchange man ønsker at benytte samt
returnerer en ny instans af den. I ampq modellen skriver man ikke direkte til
en kø, men i stedet til en exchange der sørger for at distribuere beskederne
til den kø der er lavet en binding til.
56
Der er flere forskellige typer af exchanges der hver har deres eget formål
efter hvilken model man ønsker at benytte. Vi har valgt at bruge en Topic
Exchange, da den giver os de muligheder vi har brug for.
@Bean
TopicExchange accountExchange() {
return new TopicExchange("accountcommand-exchange");
}
Derudover skal der laves en Bean der laver en binding, imellem kø navn og
instans.
@Bean
Binding bindingAccountCommand(Queue queueAccountCommand,
TopicExchange accountExchange) {
return BindingBuilder.bind(queueAccountCommand)
.to(accountExchange)
.with(queueNameAccountCommand);
}
Derefter er selve integrationen til RabbitMQ på plads, og der vil kunne
produceres beskeder kan sendes.
Til at generere beskederne har vi de enkelte rest controllere i testdriveren
der modtager et request, og omdanner det til en besked til RabbitMQ hvor
den sender det til den tilhørende exchange.
For eksempelvis CreateAccountController, har følgende implementering for
at sende til RabbitMQ.
Først en instansvariabel der angiver det kø navn man ønsker at benytte.
final static String queueNameAccountCommand = "accountCommand";
En autowire på rabbitTemplate, der findes en default rabbitTemplate i Spring
Boot man kan bruge til at arbejde med de forskellige køer.
@Autowired
RabbitTemplate rabbitTemplate;
I metoden kan der så benyttes den rabbitTemplate til at sende en besked til
køen ”accountCommand” med en BigDecimal værdi på 100000, som vi har
angivet som initial værdi ved oprettelse af en konto.
57
rabbitTemplate.convertAndSend(queueNameAccountCommand,
new BigDecimal(1000000) );
På consumer siden som er vores applikation under test, skal der laves den
samme konfiguration som på producer siden, det vil sige en kø, en exchange
samt en binding i mellem.
Ud over skal der og laves en Receiver. En receiver er en klasse man laver der
modtager den pågældende besked fra MQ, der er intet specielt over klassen,
det er en regulær POJO.
I konfigurationsfilen laves der en Bean, der laver en instans af den
ovennævnte receiver.
@Bean
TransferReceiver receiver() {
return new TransferReceiver();
}
Der skal laves en ListenerAdapter der har til formål at lave en Listener, hvor
man definerer hvilken klasse der er receiver, samt hvilken metode der skal
kaldes når der modtages en besked på køen.
I det her eksempel har vi en klasse der hedder TransferReceiver, og det er i
metoden receiveMessage beskeden bliver modtaget i.
@Bean
MessageListenerAdapter listenerAdapter(TransferReceiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
Slutteligt skal der laves en container der lytter efter beskeder på den kø der
skal lyttes på, ud fra den definerede listener, samt binder listener sammen
med den default connectionFactory der er indbygget i Spring Boot.
58
@Bean
SimpleMessageListenerContainer container(ConnectionFactory
connectionFactory, MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container =
new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(requestQueueName);
container.setMessageListener(listenerAdapter);
return container;
}
Nu kan der etableres kontakt fra Consumer siden til MQ exchange. For at
kanalisere beskederne videre i applikationen benyttes de samme kald som er
i de eksisterende rest controllere, ex for klassen AccountController, har vi
lavet en ny klasse i samme pakke kaldet AccountReceiver, der laver et kald til
samme metode som AccountController gør når den modtager et rest kald.
public class AccountReceiver {
@Autowired
private AccountService accountService;
public AccountReceiver() {
}
public void receiveMessage(BigDecimal message) {
accountService.openAccount(message);
}
}
59
9.5 Konfiguration af performance test Klienten forbinder til JMeter Engine på port 1099.
Derudover bruges Java RMI til at forbinde fra JMeter Engine tilbage til JMeter
til at sende resultaterne.
RMI anvender som standard en dynamisk port til at forbinde.
Da JMeter Engine afvikles på Amazons servere, der som standard ikke tillader
indgående trafik, skal der åbnes en port i Amazons administrations interface,
som vist herunder ved at gå ind og ændre security group.
Derefter skal RMI anvende en statisk port, dette opsættes i
konfigurationsfilen jmeter.properties, hvor følgende linjer normalt er
udkommenteret, i den sidste linje fjernes kommentaren som vist herunder,
hvorefter port 4000 vil blive anvendt.
# Parameter that controls the RMI port used by the
RemoteSampleListenerImpl (The Controler)
# Default value is 0 which means port is randomly assigned
# You may need to open Firewall port on the Controller machine
client.rmi.localport=4000
For hver server hvor JMeter skal afvikles på startes JMeter i server mode med
følgende kommando efterfølgende, hvor IP-adressen i parameteren svarer til
den public IP adresse Amazon serveren har:
JMETER_HOME/bin/jmeter-server -Djava.rmi.server.hostname=52.51.147.37
60
Grunden til at serverens public IP adresse skal medsendes som parameter, er
at måden Amazons netværk fungerer på, da JMeter ellers får serverens
interne IP adresse, og dermed ikke kan forbinde med RMI.
Herefter skal JMeter klienten opsættes, først skal den konfigures med IP
adresser på de Amazon servere hvor JMeter Engine kører, dette gøres også i
jmeter.properties.
Her angives ip-adresserne på hver server kommasepareret
# Remote Hosts - comma delimited
remote_hosts= 52.50.241.119,52.51.147.37,52.51.193.142
Ip-adresserne findes ligeledes I Amazons administrations interface, som vist
herunder:
Derudover skal man sikre at indgående trafik på port 4000 er åbnet såfremt
der kører en firewall på denne computer, så resultater fra JMeter Engine kan
modtages.
Ligeledes erfarede vi gennem længere tids fejlsøgning, at såfremt
computeren hvorfra JMeter skal opsamle resultater er bag NAT, er det
nødvendigt at starte JMeter på følgende måde, her vist hvor computeren
kører Windows:
jmeter.bat -Djava.rmi.server.hostname=80.162.197.54
Slutteligt startes testen i JMeter hvor der vælges Remote Start All som vist
herunder, vælges der kun Start, enten i menuen eller på den tilhørende knap,
så får man ikke et retvisende billede af sin performance test da det kun er
den ene instans af JMeter som starter:
61
62
9.6 JMeter opsætning af testplan Der skal konfigureres en del I JMeter for at opsætte en fornuftig testplan.
Først er der indsat en Thread group, den er af typen Concurrency thread
group, hvor man kan opsætte et vedvarende load, som vist herunder:
For at give det load der genereres et mere realistisk scenarie konfigureres der
think time for hver thread, som er en tilfældig pause der ventes indtil næste
forespørgsel sendes, dette gøres ved at tilføje en timer som vist her:
63
Da der er flere REST endpoints i vores applikationer, er det ikke muligt blot at
sende en forespørgsel til samme adresse konstant, der skal veksles mellem
de forskellige endpoints, det gøres med en logic controller. Nedenfor er vidst
en logic controller af typen bzm – weighted switch controller, hvor man kan
konfigurere vægtningen af de forskellige requests der foretages:
Når man har flere requests i sin test, skal der opsættes IP adresse og port for
hver, dog vil de i de fleste tilfælde være ens, og det vil være path der i stedet
er forskellig. For ikke at indtaste IP adresse og port flere steder, men blot
have et samlet sted at konfigurere dette er der tilføjet et konfigurations
element til testplanen af typen HTTP Requests Defaults:
64
Slutteligt er hver HTTP request opsat, ved at tilføje en sampler af typen HTTP
Request. Bemærk at her ikke er indtastet IP-adresse eller port, da den
anvender indstilingerne fra HTTP Requests Defaults:
65
Visualisering af resultater
Til at visualisere responstid og throughput af loadtests anvendes et plugin til
JMeter kaldet BM.Sense Uploader.
Den uploader resultaterne efter endt test til BlazeMeter Sense [BlazeMeter
Sense], hvor målingerne analyseres og derefter kan ses i en graf.
Pluginet kræver man opretter en konto på sense.blazemeter.com, hvor man
får en upload token som indtastes i det pågældende plugin. Derved ved
den hvilken bruger de pågældende målinger hører til.
Nedenunder kan man se hvor upload token skal indtastes. Man skal tilføje
en listener af typen bzm – BlazeMeter Sense Uploader, hvori det skal
indtastes:
66
Pluginet er en del af en større samling plugins til JMeter kaldet Standard Set
som findes her: http://jmeter-plugins.org/wiki/StandardSet/
Installationen er simpel, man downloader en zip fil som skal udpakkes i
JMeter biblioteket, derefter skal JMeter genstartes.
Dette skal kun gøres på den instans af JMeter som fungerer som master, og
opsamler data fra de andre instanser af JMeter, da den så vil være ansvarlig
for at uploade resultaterne efter endt test.
67
9.7 Opsætning af Amazon instanser Her en beskrivelse af fremgangsmåden på hvordan Amazon EC2 er blevet
brugt til at konfigurere instanserne i testmiljøet.
Inden der blev begyndt at oprette instanser blev der lavet en VPC, som er et
privat virtuelt cloud, hvor instanserne kommer til at køre.
I VPC dashboardet vælges der ’Create VPC’
Der er et par forberedende ting der skal være på plads inden der laves
instanser, de behøver ikke nødvendigvis at gælde alle instanser, men til vores
brug har vi brugt samme key pair og security group til alle instanser vi har
kørt.
Der skal først laves et key pair der kan tilknyttes en given instans, således at
sikkerheden med hensyn til adgang via SSH er på plads til en instans.
Det gøres i EC2 dashboardet under menupunktet Network and Security ->
Key Pairs og vælg ’Create Key Pair’
Dernæst skal der laves en security group der ligeledes bliver tilknyttet en
instans. En security group definerer hvordan adgangen skal være til
instansen, dvs. hvilke porte der skal være åbne.
Vælg Network and Security -> Security Groups og vælg ’Create Security
Group’.
68
Her angiver man de porte der skal åbnes, og for hvilken protokol.
VPC sættes til den VPC vi tidligere har lavet.
Base instans
Vi har valgt at lave en base instans af den type instans vi vil bruge til alle
vores servere, denne blev der taget et snapshot af, så man hurtigt og enkelt
kan starte nye instanser op med den samme grund konfiguration.
Måden base instansen blev lavet, var ved først at starte en ny Ubuntu instans
som vist nedenfor:
Vælg ’Launch Instance’
Efterfølgende blev der tilknyttes den security group samt key pair som der
blev lavet tidligere.
Når instansen er startet op, er der blevet installeret docker-compose, så der
kan startes docker containere op, og dermed ikke er afhængig af andre
programmer som java, mongoDB, rabbitMQ mv.
Følgende guide er blevet brugt for at installere docker compose hvor der
anvendes Debian Aptitude, som er en pakkemanager [Debian Aptitude]:
(https://docs.docker.com/engine/installation/linux/ubuntulinux/ )
Nu er den overordnede base lavet og der kan laves et snapshot.
Nedenstående er mest relevant hvis man har flere instanser, da man skal
bruge volume ID på den instans man vil lave et snapshot af.
Vælg menupuktet Elastic Block Store -> Volumes
Her vises en oversigt over de volumes der er tilknyttet de enkelte instanser,
under attachment Information står instans navnet.
69
Vælg menupunktet Elastic Block Store – Snapshots, og tryk ’Create Snapshot’
Der skal indtastes det Volume ID fra tidligere.
Klik på ’Create’ og der bliver dannet et snapshot.
Højreklik efterfølgende på det dannede snapshot og vælg ’Create Image’, i
Virtualization type skal der vælges Hardware-assisted virtualization.
Under menupunktet Images -> AMIs vil der nu ligge det image der lige er
blevet lavet.
Når der skal startes en ny instans udfra det givne image, vælges der en af de
AMI images, og trykkes ’Launch’.
Efter det basale microservice setup var lavet, med 1 server med mongoDB, 1
server med testdriveren, samt 1 server hver til henholdsvis de 3
komponenter i applikationen, er der taget et snapshot og image af hver af de
instanser som vi forventer skulle replikeres.
70
Fast ip-adresse
For henholdsvis mongoDB, testdriveren, samt RabbitMQ har det været mest
hensigtsmæssigt at have en fast ip adresse, så vi ikke skal rette i samtlige
docker-compose filer på hver enkelt instans hver gang vi har skulle starte en
ny test, og instanserne har været stoppet.
Derfor er der tilknyttet en række ip adresser til de pågældende instanser.
I VPC dashboardet vælges menupunktet Virtual Private Cloud, og ’Allocate
New Adress’
Når ip adressen er allokeret, og den fremgår i oversigten højreklikkes og
vælges ’associate adress’
Ved næste dialog kan der vælges den instans, hvor ip adressen skal tilknyttes.