Java >> Java tutorial >  >> Tag >> Netty

Whirlpool:Mikrotjenester, der bruger Netty og Kafka

Introduktion

I min sidste blog introducerede jeg Netty, der bruges som webserver. Det eksempel fungerede godt... så længe en broadcast-server er det, der var brug for.

Det er oftest ikke særlig nyttigt. Det er mere sandsynligt, at behovet er, at hver klient kun modtager de data, der er beregnet til dem, med udsendelser forbeholdt særlige omstændigheder som "Serveren går ned om 15 minutter!" Den anden ting ved det særlige servereksempel var, at alt var selvstændigt. Monolitiske applikationer er fine som eksempler, men i nutidens miljøer er distribuerede mikrotjenester meget bedre. Skalerbarhed og pålidelighed er altafgørende.

Netty og Kafka er fantastiske sammen. Netty er fantastisk til at håndtere en bådladning af kunder, og Kafka er god til at få en bådladning af tjenester til at arbejde sammen. Kombineret er de et sødt sted i udviklingen. Der er dog nogle "gotchas", der kan gøre det besværligt. Denne blog vil sammen med eksemplet med mikroservice/Netty-arkitekturen og fuldt fungerende kode forhåbentlig hjælpe med at lindre irritationerne og aktivere sødmen.

Først ting først

Koden til eksemplet er placeret her.

Der er en detaljeret README, der beskriver, hvad der skal til for at konfigurere miljøet. Jeg forsøgte at holde kravene på et minimum, kun Java 8 og Maven. SLF4J og Logback bruges til logning. Jeg har konfigureret scripts til Mac OSX og Ubuntu (14.04 kører i en Parallels-beholder er det, jeg testede med), så undskyld hvis du udvikler på Windows. Koden er udelukkende Java, og jeg har set Kafka-tutorials derude til Windows, så alt burde køre der. Maven-bygningen skulle også producere mål, der kan startes, så med lidt albuefedt ved installation af Zookeeper/Kafka (du kan følge scriptet for at se, hvilke indstillinger der er nødvendige), burde det ikke være en stor sag at få det til at køre manuelt på Windows.

BEMÆRK: Som forklaret i README.md , vil scriptet fjerne enhver eksisterende Zookeeper/Kafka installation og data. Hvis du har en eksisterende opsætning, skal du ikke bruge scriptet!

Efter installation og konfiguration af forudsætningerne skal du enten køre mvn package hvis du ikke bruger scriptet, eller maclocal_run.sh (eller linuxlocal_run.sh ) hvis du er. Scriptet downloader (hvis det ikke har endnu) Zk/Kafka, installerer dem, konfigurerer dem, starter dem, kører mvn package , starter tjenesterne og starter til sidst serveren. Når den starter, skal du modstå trangen til at navigere væk fra skallen, fordi den automatisk åbner nye faner for hver del af arkitekturen. Når Whirlpool-serveren starter, er du klar til at gå.

Jeg anbefaler stærkt at oprette et script, der installerer, konfigurerer, bygger og starter dit mikroservicemiljø lokalt. At skabe hver enkelt service er en stor smerte. Docker kan også bruges, hvis det er nødvendigt, men jeg synes, det kræver langt mindre downloading for bare at køre alt native.

Som en teaser er her brugergrænsefladen (du kan også se dette fra README.md på GitHub).

  • For at tilføje et aktiesymbol skal du indtaste det (dvs. "GOOG") og klikke på A-knappen under "Aktier". For at fjerne det, klik på X.
  • For at tilføje et websted for at teste, om det er op eller ned, skal du indtaste den fuldt kvalificerede URL (dvs. http://facebook.com) og klikke på A-knappen under "UpDown". For at fjerne det, klik på X.
  • For at tilføje et vejrtjek skal du skrive byen, staten i (dvs. "chicago,il") og klikke på A-knappen under "By, stat". For at fjerne det, klik på X.
  • Abonnementer overlever sideopdatering og endda login/log ud (med samme bruger-id), fordi de er gemt med hver tjeneste i hukommelsen. Et "rigtigt" system ville selvfølgelig bruge en database.
  • Abonnementer opdateres hvert 10. sekund, så jeg ikke overvælder Yahoo API'erne, så vær tålmodig efter at have tilføjet data.

Arkitektur

Med dette eksempel prøvede jeg at tænke på gode generiske tjenester, der kunne være nyttige. Jeg endte med at vælge en aktiekurstjeneste, en "er denne hjemmeside op eller ned"-tjeneste og en vejrtjeneste. Hver af disse kører uafhængigt af de andre med deres egne Kafka-emner.

Den måde, jeg valgte at konfigurere Kafka på, var med et kommandoemne pr. tjeneste og et dataemne pr. tjeneste. Alt kunne også bare bruge et enkelt globalt emne, hvor læserne beslutter, hvad der skal behandles, men at adskille tingene fra gør det mere klart og rent.

Her er et diagram over, hvordan data flyder gennem Kafka. Det blev gjort med et gratis Nøglehul webbaseret hjælpeprogram kaldet Mockola. Bemærk, at serveren kender til alle emner, men at tjenesterne kun kender til deres egne emner. cmd emner bruges til at sende kommandoer til tjenesterne, hvorimod dataemnerne (dem uden -cmd på dem) bruges til at sende data fra tjenesterne. Igen kunne alt dette håndteres på en enkelt bus emne, men det er meget nemmere at se, hvad der foregår ved at adskille dem.

Tjenester

Lad os nu tale om tjenesterne. Alle tre er meget ens, så der er en basistjeneste, der klarer det meste af arbejdet. Hver tjeneste har tre tråde, som håndteres af Java ExecutorService. En god ting ved Executor-tjenesten er, at den automatisk genstarter tråden, hvis noget går galt. Dette hjælper modstandskraften.

Hver tjeneste starter sig selv med at fortælle basisklassen, hvilket emne og kommandoemne der skal bruges. Basisklassen starter derefter de tre tråde:én til at læse kommandoer fra cmd-emnet, én til periodisk indsamling af data for klienter og én til at sende data om dataemnet. Disse tråde kommunikerer ved hjælp af de ikke-blokerende Java samtidighedsklasser ConcurrentLinkedQueue og ConcurrentHashMap . Hash-kortet gemmer per-bruger sæt af abonnementer, og køen gemmer svar klar til at blive sendt til dataemnet.

Flow for hver tjeneste er de tre tråde, der arbejder samtidigt. Læseren bruger en Kafka-forbruger til at læse kommandoer fra dens kommandoemne. Baseret på kommandoen tilføjes eller fjernes abonnementet. Denne tråd er ret dum, fordi den ikke beder tjenesten om at foretage nogen validering af anmodningen, den tilføjer blot blindt, hvad der sendes til abonnementet. Produktionskoden ville naturligvis tilføje et opkald for at bede tjenesten om at validere kommandoen, før abonnementet lykkes. Der oprettes et svar til at sætte på emnet, og så venter det på den næste kommando.

BEMÆRK :Et par ord om, at data placeres på emner. Jeg bruger JSON som et transportformat, men XML eller noget andet, du ønsker, vil også fungere. Det vigtige er, at alle er enige om dataformatet og holder sig til det. Det fælles modul har POJO-klasser, der definerer de kontrakter, som dataene vil være i overensstemmelse med. Ting, der generelt er nyttige for alle meddelelser, er et tidsstempel, meddelelsestypen og klientens id.

En anden nyttig ting ville være et udløbstidsstempel. Disse eksempelbeskeder lever bare for evigt. Message klasse ser kun på typen og id'et af en meddelelse. Dette bruges af serveren til at bestemme, hvilken slags besked der skal behandles, og hvem der er interesseret i beskeden. Uden disse er det meget svært, hvis ikke umuligt, at behandle data. Nu kan meddelelsesformater blive ret involverede, hvor nogle bruger overskrifter og sektioner til at beskrive komplekse data. Dette eksempel forsøger at holde alt så enkelt som muligt.

Netty Server

Lad os gå gennem serveren en klasse ad gangen.

NettyHttpFileHandler

Denne klasse er stort set uændret i forhold til den tidligere blog. De genanvendelige brikker er blevet flyttet til WebSocketHelper klasse. Hovedanvendelsen af ​​denne fil er at servere filer, der bliver bedt om af browseren.

WebSocketHelper

Det første element, der kan være forvirrende, er klassevariablen clientAttr . Lagring af data i en Netty Channel kræver, at den er knyttet til en AttributeKey . Dette ligner en Atomic-instans fra de samtidige Java-klasser - det giver en beholder til data. Vi gemmer klient-id'et (i vores tilfælde brugernavnet, men det kunne lige så nemt være et session-id), så vi kan finde ud af, hvilken kanal der skal modtage beskeder.

realWriteAndFlush() metode indstiller de passende overskrifter, indholdslængden og cookien. Den skriver og fjerner derefter HTTP-svaret. linjen

channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);

fortæller Netty, at dette er slutningen af ​​de data, der skal skrives til klienten, så Netty sender det ud.

SÆRLIG BEMÆRK :Med hensyn til oprettelse af cookies skal du sørge for HTTP Only flag er IKKE sat. Hvis det er det, kan JavaScript ikke se cookien, og den vil heller ikke blive sendt sammen med WebSocket-opgraderingsanmodningen. Dette gør det så, at du skal oprette din egen metode til sideopdateringsstyring og sessionsstyring.

Den anden ting ved cookies er at bruge den STRIKTE version af Netty-cookie-encoderen, så den ikke tillader flere cookies med samme navn. Jeg er ikke sikker på, hvornår det ville være nyttigt at tillade denne situation at opstå.

WebSocketMessageHandler

Denne klasse definerer blot en grænseflade, der WhirlpoolServerHandler bruger til at tale med WhirlpoolMessageHandler .

WhirlpoolMessageHandler

Det er her, forbindelsen er mellem Netty og Kafka. To Executors håndterer en læsetråd og en forfattertråd.

Forfatter-tråden leder efter beskeder i anmodningskøen (mere om, hvor disse beskeder kommer fra om et minut), og placerer beskederne i det relevante Kafka-kommandoemne.

Læsertråden leder efter indgående beskeder om Kafka-dataemnerne, slår den korrekte kanal op for hvert emne og skriver beskederne til disse emner.

Når klienten sender en besked over WebSockets, WhirlpoolServerHandler vil sørge for, at en komplet besked er ankommet, og derefter ringe til handleMessage() . Denne metode finder ud af, om det er en gyldig besked, og føjer derefter anmodningen til anmodningskøen, så læsertråden kan samle den op og give den til Kafka.

WhirlpoolServerHandler

Der er flere interessante ting i denne klasse. For det første kan den kende forskel på en HTTP-, REST- og WebSocket-meddelelse. Den Netty-tilsidesatte metode, der gør dette, er channelRead0 . Dette er den metode, Netty bruger til at fortælle os, hvornår en besked ankommer, og hvilken type besked det er. For HTTP- og REST-opkald, handleHttpRequest kaldes, og for websockets, handleWebSocketFrame Hedder.

Metoden handleHttpRequest læser cookien, hvis en er til stede. På POSTs ser den efter login og logout. Til login finder den ud af brugernavnet/adgangskoden, opretter cookien og forhindrer flere login med samme navn. Al den kode ville blive delt ud med yderligere sikkerhed tilføjet i en produktionsversion af applikationen. For at logge ud, slår den op i kanalen, rydder den op, lukker den og udløber cookien.

For en WebSocketUpgrade , beder den Netty om at håndtere det komplekse håndtryk, der kræves for at få en websocket i gang. Når det er færdigt, føjer det brugeren til den kanal, der blev oprettet under håndtrykket. Det er her, brugeren er forbundet til kanalen, og det ville ikke være særlig nemt, hvis cookien ikke kom på tværs af anmodningen.

Den eneste anden ting at bemærke her er, at denne klasse er sat op til at håndtere klienter kodet til SPA (enkeltsidet applikation), da den vil omdirigere ethvert ukendt opkald til index.html .

De andre metoder i klassen er mere til informationsformål og vil blive brugt i avancerede situationer.

WhirlpoolServer

Denne klasse starter Netty-serveren og opretter kanalpipeline. Det er en standardklasse for Netty, der følger Netty-eksemplerne.

Sidste tanker

Der kunne naturligvis gå meget mere ind i denne kode. Flere forekomster af hver tjeneste og serveren kunne køre på samme tid, og Zk/Kafka kunne grupperes for at hjælpe med robusthed. Et fantastisk værktøj, der tester modstandsdygtigheden af ​​mikroserviceapplikationer, er et andet gratis open source nøglehulsværktøj kaldet TroubleMaker. Jeg har ikke haft mulighed for at teste dette eksempel endnu, men jeg ser frem til muligheden.

Vi berørte ikke sikkerhed, og selvom jeg tidligere håbede at vise integration af Netty med Shiro, er det et meget komplekst emne. Alt, hvad jeg kan sige om det, er, at det er muligt, men jeg har ikke viklet hovedet nok omkring alle delene til at formulere en sammenhængende blog endnu.

Jeg håber du har nydt bloggen og finder koden nyttig. Kontakt mig via bloggen eller Twitter (@johnwboardman hvor jeg altid sætter pris på nye følgere).

Java tag