Introduksjon til Hystrix

1. Oversikt

Et typisk distribuert system består av mange tjenester som samarbeider.

Disse tjenestene er utsatt for svikt eller forsinkede svar. Hvis en tjeneste mislykkes, kan det påvirke andre tjenester som påvirker ytelsen og muligens gjøre andre deler av applikasjonen utilgjengelige eller i verste fall få ned hele applikasjonen.

Selvfølgelig er det tilgjengelige løsninger som bidrar til å gjøre applikasjoner elastiske og feiltolerante - et slikt rammeverk er Hystrix.

Hystrix rammebibliotek hjelper til med å kontrollere samspillet mellom tjenester ved å gi feiltoleranse og ventetid Det forbedrer systemets generelle motstandsdyktighet ved å isolere de sviktende tjenestene og stoppe den kaskade effekten av feil.

I denne serien av innlegg vil vi begynne med å se på hvordan Hystrix kommer til unnsetning når en tjeneste eller et system mislykkes, og hva Hystrix kan oppnå under disse omstendighetene.

2. Enkelt eksempel

Måten Hystrix gir toleranse for feil og ventetid er å isolere og pakke samtaler til eksterne tjenester.

I dette enkle eksemplet pakker vi en samtale i løpe() metoden for HystrixCommand:

klasse CommandHelloWorld utvider HystrixCommand {private strengnavn; CommandHelloWorld (strengnavn) {super (HystrixCommandGroupKey.Factory.asKey ("ExampleGroup")); this.name = navn; } @ Override-beskyttet strengkjøring () {return "Hei" + navn + "!"; }}

og vi utfører samtalen som følger:

@Test offentlig ugyldighet gittInputBobAndDefaultSettings_whenCommandExecuted_thenReturnHelloBob () {assertThat (ny CommandHelloWorld ("Bob"). Execute (), equalTo ("Hello Bob!")); }

3. Maven-oppsett

For å bruke Hystrix i et Maven-prosjekt, må vi ha hystrix-kjerne og rxjava-kjerne avhengighet fra Netflix i prosjektet pom.xml:

 com.netflix.hystrix hystrix-core 1.5.4 

Den siste versjonen finner du alltid her.

 com.netflix.rxjava rxjava-kjerne 0.20.7 

Den siste versjonen av dette biblioteket finner du alltid her.

4. Sette opp ekstern tjeneste

La oss starte med å simulere et eksempel fra den virkelige verden.

I eksemplet nedenfor, klassen RemoteServiceTestSimulator representerer en tjeneste på en ekstern server. Den har en metode som reagerer med en melding etter den angitte tidsperioden. Vi kan forestille oss at denne ventetiden er en simulering av en tidkrevende prosess ved det eksterne systemet som resulterer i et forsinket svar på anropstjenesten:

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

Og her er vår eksempelklient som kaller RemoteServiceTestSimulator.

Anropet til tjenesten er isolert og innpakket i løpe() metode for a HystrixCommand. Det er denne innpakningen som gir motstandskraften vi berørte ovenfor:

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

Samtalen utføres ved å ringe henrette() metoden på en forekomst av RemoteServiceTestCommand gjenstand.

Følgende test viser hvordan dette gjøres:

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

Så langt har vi sett hvordan vi kan pakke eksterne tjenesteanrop i HystrixCommand gjenstand. I delen nedenfor, la oss se på hvordan du skal håndtere en situasjon når fjerntjenesten begynner å forverres.

5. Arbeide med ekstern service og defensiv programmering

5.1. Defensiv programmering med tidsavbrudd

Det er generell programmeringspraksis å sette tidsavbrudd for samtaler til eksterne tjenester.

La oss begynne med å se på hvordan du setter tidsavbrudd på HystrixCommand og hvordan det hjelper ved kortslutning:

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

I testen ovenfor utsetter vi tjenestens respons ved å sette tidsavbruddet til 500 ms. Vi setter også tidsavbrudd for utførelse på HystrixCommand å være 10.000 ms, og dermed tillate tilstrekkelig tid for den eksterne tjenesten til å svare.

La oss nå se hva som skjer når timeout for kjøring er mindre enn timeout-samtalen for tjenesten:

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

Legg merke til hvordan vi har senket linjen og satt tidsavbrudd for utførelse til 5.000 ms.

Vi forventer at tjenesten skal svare innen 5.000 ms, mens vi har satt tjenesten til å svare etter 15.000 ms. Hvis du merker når du utfører testen, avsluttes testen etter 5.000 ms i stedet for å vente på 15.000 ms og vil kaste et HystrixRuntimeException.

Dette demonstrerer hvordan Hystrix ikke venter lenger enn den konfigurerte tidsavbruddet for et svar. Dette bidrar til å gjøre systemet beskyttet av Hystrix mer responsivt.

I avsnittene nedenfor vil vi se på innstilling av trådbassengstørrelse som forhindrer at tråder blir utmattet, og vi vil diskutere fordelen.

5.2. Defensiv programmering med begrenset trådbasseng

Innstilling av tidsavbrudd for tjenesteanrop løser ikke alle problemene knyttet til eksterne tjenester.

Når en ekstern tjeneste begynner å svare sakte, vil en typisk applikasjon fortsette å ringe den eksterne tjenesten.

Søknaden vet ikke om fjerntjenesten er sunn eller ikke, og nye tråder gis hver gang en forespørsel kommer inn. Dette vil føre til at tråder på en allerede sliter server skal brukes.

Vi vil ikke at dette skal skje, ettersom vi trenger disse trådene for andre eksterne samtaler eller prosesser som kjører på serveren vår, og vi vil også unngå at CPU-bruken øker.

La oss se hvordan du angir trådgruppestørrelsen i HystrixCommand:

@Test offentlig ugyldig gittSvcTimeoutOf500AndExecTimeoutOf10000AndThreadPool_whenRemoteSvcExecuted _thenReturnSuccess () kaster 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 ("Success")); }

I testen ovenfor setter vi maksimal køstørrelse, kjernestørrelse og køavstørrelse. Hystrix begynner å avvise forespørslene når maksimalt antall tråder har nådd 10 og oppgavekøen har nådd størrelsen 10.

Kjernestørrelsen er antall tråder som alltid holder seg i live i trådbassenget.

5.3. Defensiv programmering med kortslutningsmønster

Imidlertid er det fremdeles en forbedring vi kan gjøre med eksterne tjenesteanrop.

La oss vurdere saken om at fjerntjenesten har begynt å mislykkes.

Vi vil ikke fortsette å skyte av forespørsler mot det og kaste bort ressurser. Vi ønsker ideelt sett å slutte å sende forespørsler i en viss tid for å gi tjenesten tid til å komme seg før vi deretter fortsetter forespørslene. Dette er det som kalles Kortslutningsbryter mønster.

La oss se hvordan Hystrix implementerer dette mønsteret:

@Test offentlig ugyldig gittCircuitBreakerSetup_whenRemoteSvcCmdExecuted_thenReturnSuccess () kaster InterruptedException {HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey (HystrixCommandGroupKey.Factory.asKeyker ("RemoteService)") HystrixCommandProperties.Setter egenskaper = HystrixCommandProperties.Setter (); properties.withExecutionTimeoutInMilliseconds (1000); properties.withCircuitBreakerSleepWindowInMilliseconds (4000); properties.withExecutionIsolationStrategy (HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); properties.withCircuitBreakerEnabled (true); properties.withCircuitBreakerRequestVolumeThreshold (1); config.andCommandPropertiesDefaults (egenskaper); 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 ("Success")); assertThat (ny RemoteServiceTestCommand (config, ny RemoteServiceTestSimulator (500)). execute (), equalTo ("Success")); assertThat (ny RemoteServiceTestCommand (config, ny RemoteServiceTestSimulator (500)). execute (), equalTo ("Success")); }
public String invokeRemoteService (HystrixCommand.Setter config, int timeout) kaster InterruptedException {String response = null; prøv {respons = new RemoteServiceTestCommand (config, new RemoteServiceTestSimulator (timeout)). execute (); } fange (HystrixRuntimeException ex) {System.out.println ("ex =" + ex); } returnere svar; }

I testen ovenfor har vi satt forskjellige effektbryteregenskaper. De viktigste er:

  • De CircuitBreakerSleepWindow som er satt til 4000 ms. Dette konfigurerer brytervinduet og definerer tidsintervallet som forespørselen til fjerntjenesten vil gjenopptas etter
  • De CircuitBreakerRequestVolumeThreshold som er satt til 1 og definerer det minste antall forespørsler som trengs før feilprosenten vil bli vurdert

Med ovenstående innstillinger, vår HystrixCommand vil nå reise åpen etter to mislykkede forespørsler. Den tredje forespørselen vil ikke engang treffe fjerntjenesten selv om vi har satt serviceforsinkelsen til 500 ms, Hystrix vil kortslutte og metoden vår kommer tilbake null som svaret.

Vi vil deretter legge til en Thread.sleep (5000) for å krysse grensen for søvnvinduet vi har satt. Dette vil føre til Hystrix for å lukke kretsen, og de påfølgende forespørslene vil strømme gjennom.

6. Konklusjon

Oppsummert er Hystrix designet for å:

  1. Gi beskyttelse og kontroll over feil og ventetid fra tjenester som vanligvis er tilgjengelige via nettverket
  2. Stopp kaskaden av feil som skyldes at noen av tjenestene er nede
  3. Mislykkes raskt og gjenoppretter raskt
  4. Nedgradere grasiøst der det er mulig
  5. Sanntidsovervåking og varsling av kommandosenteret om feil

I neste innlegg vil vi se hvordan du kan kombinere fordelene med Hystrix med Spring-rammeverket.

Fullstendig prosjektkode og alle eksempler finner du på github-prosjektet.