Introduktion til Hystrix

1. Oversigt

Et typisk distribueret system består af mange tjenester, der samarbejder sammen.

Disse tjenester er tilbøjelige til svigt eller forsinkede svar. Hvis en tjeneste mislykkes, kan det påvirke andre tjenester, der påvirker ydeevnen og muligvis gøre andre dele af applikationen utilgængelige eller i værste fald nedbringe hele applikationen.

Der er naturligvis løsninger til rådighed, der hjælper med at gøre applikationer modstandsdygtige og fejltolerante - en sådan ramme er Hystrix.

Hystrix-rammebiblioteket hjælper med at kontrollere interaktionen mellem tjenester ved at give fejltolerance og latenstolerance. Det forbedrer systemets generelle modstandsdygtighed ved at isolere de svigtende tjenester og stoppe den kaskade effekt af fejl.

I denne række indlæg begynder vi med at se på, hvordan Hystrix kommer til undsætning, når en tjeneste eller et system mislykkes, og hvad Hystrix kan udrette under disse omstændigheder.

2. Enkelt eksempel

Måden Hystrix giver tolerance for fejl og latenstid er at isolere og omslutte opkald til eksterne tjenester.

I dette enkle eksempel omslutter vi et opkald i løb() metode til HystrixCommand:

klasse CommandHelloWorld udvider HystrixCommand {privat strengnavn; CommandHelloWorld (String name) {super (HystrixCommandGroupKey.Factory.asKey ("ExampleGroup")); dette.navn = navn; } @ Override beskyttet String run () {return "Hej" + navn + "!"; }}

og vi udfører opkaldet som følger:

@Test offentligt ugyldigt givetInputBobAndDefaultSettings_whenCommandExecuted_thenReturnHelloBob () {assertThat (ny CommandHelloWorld ("Bob"). Execute (), equalTo ("Hello Bob!")); }

3. Maven-opsætning

For at bruge Hystrix i et Maven-projekt skal vi have hystrix-kerne og rxjava-kerne afhængighed fra Netflix i projektet pom.xml:

 com.netflix.hystrix hystrix-core 1.5.4 

Den nyeste version kan altid findes her.

 com.netflix.rxjava rxjava-kerne 0.20.7 

Den seneste version af dette bibliotek kan altid findes her.

4. Opsætning af fjernservice

Lad os starte med at simulere et virkeligt verdenseksempel.

I eksemplet nedenfor, klassen RemoteServiceTestSimulator repræsenterer en tjeneste på en ekstern server. Den har en metode, der reagerer med en besked efter den givne periode. Vi kan forestille os, at denne ventetid er en simulering af en tidskrævende proces på fjernsystemet, hvilket resulterer i et forsinket svar på opkaldstjenesten:

klasse RemoteServiceTestSimulator {privat lang ventetid; RemoteServiceTestSimulator (lang ventetid) kaster InterruptedException {this.wait = vent; } String execute () kaster InterruptedException {Thread.sleep (vent); returner "Succes"; }}

Og her er vores prøveklient der kalder det RemoteServiceTestSimulator.

Opkaldet til tjenesten er isoleret og indpakket i løb() metode til en HystrixCommand. Det er denne indpakning, der giver den modstandsdygtighed, vi rørte ved ovenfor:

klasse RemoteServiceTestCommand udvider HystrixCommand {private RemoteServiceTestSimulator remoteService; RemoteServiceTestCommand (Setter config, RemoteServiceTestSimulator remoteService) {super (config); this.remoteService = remoteService; } @ Override beskyttet String run () kaster undtagelse {return remoteService.execute (); }}

Opkaldet udføres ved at ringe til udføre () metode på en forekomst af RemoteServiceTestCommand objekt.

Følgende test viser, hvordan dette gøres:

@Test offentlig ugyldighed givenSvcTimeoutOf100AndDefaultSettings_whenRemoteSvcExecuted_thenReturnSuccess () kaster InterruptedException {HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey (HystrixCommandGroupKey.Factory.as. assertThat (ny RemoteServiceTestCommand (config, ny RemoteServiceTestSimulator (100)). execute (), equalTo ("Succes")); }

Indtil videre har vi set, hvordan man kan indpakke fjerntjenesteopkald i HystrixCommand objekt. Lad os i afsnittet nedenfor se på, hvordan vi skal håndtere en situation, hvor fjerntjenesten begynder at blive forværret.

5. Arbejde med fjernservice og defensiv programmering

5.1. Defensiv programmering med timeout

Det er almindelig programmeringspraksis at indstille timeouts for opkald til fjerntjenester.

Lad os begynde med at se på, hvordan vi indstiller timeout til HystrixCommand og hvordan det hjælper ved kortslutning:

@Test offentlig ugyldighed givenSvcTimeoutOf5000AndExecTimeoutOf10000_whenRemoteSvcExecuted_thenReturnSuccess () kaster InterruptedException {HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey (HystrixCommandGroupKey.Fey) HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter (); commandProperties.withExecutionTimeoutInMilliseconds (10_000); config.andCommandPropertiesDefaults (commandProperties); assertThat (ny RemoteServiceTestCommand (config, ny RemoteServiceTestSimulator (500)). execute (), equalTo ("Succes")); }

I ovenstående test forsinker vi tjenestens svar ved at indstille timeout til 500 ms. Vi sætter også timeout for eksekvering til HystrixCommand skal være 10.000 ms, hvilket giver tilstrækkelig tid til, at fjerntjenesten reagerer.

Lad os nu se, hvad der sker, når udførelsestimeout er mindre end servicetimeout-opkaldet:

@Test (forventet = HystrixRuntimeException.class) offentlig ugyldigt givenSvcTimeoutOf15000AndExecTimeoutOf5000_whenRemoteSvcExecuted_thenExpectHre () kaster InterruptedException {HystrixCommand.Setter config = HystrixCommand .Setter. HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter (); commandProperties.withExecutionTimeoutInMilliseconds (5_000); config.andCommandPropertiesDefaults (commandProperties); ny RemoteServiceTestCommand (config, ny RemoteServiceTestSimulator (15_000)). execute (); }

Bemærk, hvordan vi har sænket bjælken og indstillet timeout til udførelse til 5.000 ms.

Vi forventer, at tjenesten reagerer inden for 5.000 ms, hvorimod vi har indstillet tjenesten til at svare efter 15.000 ms. Hvis du bemærker, når du udfører testen, afsluttes testen efter 5.000 ms i stedet for at vente på 15.000 ms og kaster et HystrixRuntimeException.

Dette viser, hvordan Hystrix ikke venter længere end den konfigurerede timeout for et svar. Dette hjælper med at gøre systemet beskyttet af Hystrix mere lydhørt.

I nedenstående sektioner vil vi se på indstilling af trådpulstørrelse, som forhindrer tråde i at blive opbrugt, og vi vil diskutere dens fordel.

5.2. Defensiv programmering med begrænset trådpulje

Indstilling af timeouts til serviceopkald løser ikke alle de problemer, der er forbundet med fjerntjenester.

Når en fjerntjeneste begynder at reagere langsomt, fortsætter en typisk applikation med at kalde den fjerntjeneste.

Applikationen ved ikke, om fjerntjenesten er sund eller ej, og nye tråde frembringes, hver gang en anmodning kommer ind. Dette vil medføre, at tråde på en allerede kæmpende server bruges.

Vi ønsker ikke, at dette skal ske, da vi har brug for disse tråde til andre fjernopkald eller processer, der kører på vores server, og vi vil også undgå, at CPU-udnyttelse øges.

Lad os se, hvordan du indstiller trådpulstørrelsen i HystrixCommand:

@Test offentlig ugyldighed givenSvcTimeoutOf500AndExecTimeoutOf10000AndThreadPool_whenRemoteSvcExecuted _thenReturnSuccess () throws InterruptedException {HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey (HystrixCommand.Server) HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter (); commandProperties.withExecutionTimeoutInMilliseconds (10_000); config.andCommandPropertiesDefaults (commandProperties); config.andThreadPoolPropertiesDefaults (HystrixThreadPoolProperties.Setter () .withMaxQueueSize (10) .withCoreSize (3) .withQueueSizeRejectionThreshold (10)); assertThat (ny RemoteServiceTestCommand (config, ny RemoteServiceTestSimulator (500)). execute (), equalTo ("Succes")); }

I ovenstående test indstiller vi den maksimale køstørrelse, kernekøstørrelsen og køafvisningsstørrelsen. Hystrix vil afvise anmodningerne, når det maksimale antal tråde har nået 10, og opgavekøen har nået en størrelse på 10.

Kernestørrelsen er antallet af tråde, der altid holder sig i live i trådpuljen.

5.3. Defensiv programmering med kortslutnings-mønster

Der er dog stadig en forbedring, som vi kan foretage i forbindelse med fjernservicekald.

Lad os overveje sagen om, at fjerntjenesten er begyndt at mislykkes.

Vi vil ikke fortsætte med at skyde anmodninger mod det og spilde ressourcer. Vi vil ideelt set stoppe med at fremsætte anmodninger i et bestemt tidsrum for at give tjenesten tid til at komme sig, inden vi derefter genoptager anmodningerne. Dette er hvad der kaldes Kortslutningsafbryder mønster.

Lad os se, hvordan Hystrix implementerer dette mønster:

@Test offentlig ugyldighed givenCircuitBreakerSetup_whenRemoteSvcCmdExecuted_thenReturnSuccess () kaster InterruptedException {HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey (HystrixCommandGroupKey.Factory.asKeygruppe) ("RemoteService") HystrixCommandProperties.Setter egenskaber = HystrixCommandProperties.Setter (); properties.withExecutionTimeoutInMilliseconds (1000); properties.withCircuitBreakerSleepWindowInMilliseconds (4000); properties.withExecutionIsolationStrategy (HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); properties.withCircuitBreakerEnabled (true); properties.withCircuitBreakerRequestVolumeThreshold (1); config.andCommandPropertiesDefaults (egenskaber); config.andThreadPoolPropertiesDefaults (HystrixThreadPoolProperties.Setter () .withMaxQueueSize (1) .withCoreSize (1) .withQueueSizeRejectionThreshold (1)); assertThat (this.invokeRemoteService (config, 10_000), equalTo (null)); assertThat (this.invokeRemoteService (config, 10_000), equalTo (null)); assertThat (this.invokeRemoteService (config, 10_000), equalTo (null)); Tråd. Søvn (5000); assertThat (ny RemoteServiceTestCommand (config, ny RemoteServiceTestSimulator (500)). execute (), equalTo ("Succes")); assertThat (ny RemoteServiceTestCommand (config, ny RemoteServiceTestSimulator (500)). execute (), equalTo ("Succes")); assertThat (ny RemoteServiceTestCommand (config, ny RemoteServiceTestSimulator (500)). execute (), equalTo ("Succes")); }
public String invokeRemoteService (HystrixCommand.Setter config, int timeout) kaster InterruptedException {String response = null; prøv {respons = ny RemoteServiceTestCommand (config, ny RemoteServiceTestSimulator (timeout)). execute (); } fange (HystrixRuntimeException ex) {System.out.println ("ex =" + ex); } returnere svar }

I ovenstående test har vi indstillet forskellige afbryderegenskaber. De vigtigste er:

  • Det CircuitBreakerSleepWindow som er indstillet til 4.000 ms. Dette konfigurerer afbrydervinduet og definerer det tidsinterval, hvorefter anmodningen til fjerntjenesten genoptages
  • Det CircuitBreakerRequestVolumeThreshold som er indstillet til 1 og definerer det mindste antal anmodninger, der kræves, før fejlprocenten overvejes

Med ovenstående indstillinger på plads, er vores HystrixCommand vil nu åbne efter to mislykkede anmodninger. Den tredje anmodning rammer ikke engang fjerntjenesten, selvom vi har indstillet serviceforsinkelsen til 500 ms, Hystrix vil kortslutte, og vores metode vender tilbage nul som svaret.

Vi vil derefter tilføje en Thread.sleep (5000) for at krydse grænsen for det sovevindue, vi har indstillet. Dette vil forårsage Hystrix for at lukke kredsløbet, og de efterfølgende anmodninger vil løbe igennem med succes.

6. Konklusion

Sammenfattende er Hystrix designet til at:

  1. Giv beskyttelse og kontrol over fejl og ventetid fra tjenester, der typisk fås via netværket
  2. Stop kaskadering af fejl, der skyldes, at nogle af tjenesterne er nede
  3. Mislykkes hurtigt og hurtigt komme sig
  4. Nedgrader yndefuldt, hvor det er muligt
  5. Realtidsovervågning og alarmering af kommandocentret om fejl

I det næste indlæg vil vi se, hvordan fordelene ved Hystrix kombineres med Spring-rammen.

Den fulde projektkode og alle eksempler findes på github-projektet.