Uvod u Hystrix

1. Pregled

Tipični distribuirani sustav sastoji se od mnogih usluga koje zajedno surađuju.

Te su usluge sklone neuspjehu ili odgođenim odgovorima. Ako usluga propadne, to može utjecati na druge usluge koje utječu na performanse i možda učiniti druge dijelove aplikacije nedostupnima ili u najgorem slučaju srušiti cijeli program.

Naravno, postoje rješenja koja pomažu da aplikacije budu elastične i otporne na greške - jedan takav okvir je Hystrix.

Okvirna knjižnica Hystrix pomaže u kontroli interakcije između usluga pružajući toleranciju na greške i kašnjenje. Poboljšava ukupnu otpornost sustava izoliranjem neuspjelih usluga i zaustavljanjem kaskadnog učinka kvarova.

U ovoj seriji postova započet ćemo s gledanjem kako Hystrix dolazi u pomoć kad usluga ili sustav zakaže i što Hystrix može postići u tim okolnostima.

2. Jednostavan primjer

Način na koji Hystrix pruža toleranciju grešaka i kašnjenja je izoliranje i zamatanje poziva udaljenim službama.

U ovom jednostavnom primjeru umotavamo poziv u trčanje() metoda HystrixCommand:

klasa CommandHelloWorld proširuje HystrixCommand {naziv privatnog niza; CommandHelloWorld (naziv niza) {super (HystrixCommandGroupKey.Factory.asKey ("ExampleGroup")); this.name = ime; } @Override protected String run () {return "Hello" + name + "!"; }}

a poziv izvršavamo na sljedeći način:

@Test public void givenInputBobAndDefaultSettings_whenCommandExecuted_thenReturnHelloBob () {assertThat (new CommandHelloWorld ("Bob"). Execute (), jednakTo ("Hello Bob!")); }

3. Postavljanje Mavena

Da bismo koristili Hystrix u projektima Maven, to moramo imati histriks-jezgra i rxjava-jezgra ovisnost o Netflixu u projektu pom.xml:

 com.netflix.hystrix hystrix-core 1.5.4 

Najnoviju verziju uvijek možete pronaći ovdje.

 com.netflix.rxjava rxjava-core 0.20.7 

Najnoviju verziju ove knjižnice uvijek možete pronaći ovdje.

4. Postavljanje daljinske usluge

Krenimo od simulacije primjera iz stvarnog svijeta.

U donjem primjeru, razred RemoteServiceTestSimulator predstavlja uslugu na udaljenom poslužitelju. Ima metodu koja porukom odgovara nakon određenog vremenskog razdoblja. Možemo zamisliti da je ovo čekanje simulacija dugotrajnog procesa na udaljenom sustavu koji rezultira odgođenim odgovorom pozivajućoj službi:

klasa RemoteServiceTestSimulator {privatno dugo čekati; RemoteServiceTestSimulator (dugo čekanje) baca InterruptedException {this.wait = wait; } String execute () baca InterruptedException {Thread.sleep (pričekajte); povratak "Uspjeh"; }}

I ovdje je naš uzorak klijenta koji poziva RemoteServiceTestSimulator.

Poziv službi izoliran je i zamotan u trčanje() metoda a HystrixCommand. Ovo ovo omotavanje pruža elastičnost koju smo dotakli gore:

klasa RemoteServiceTestCommand proširuje HystrixCommand {private RemoteServiceTestSimulator remoteService; RemoteServiceTestCommand (Setter config, RemoteServiceTestSimulator remoteService) {super (config); this.remoteService = remoteService; } @Override zaštićeni niz run () baca iznimku {return remoteService.execute (); }}

Poziv se izvršava pozivanjem izvršiti() metoda na instanci RemoteServiceTestCommand objekt.

Sljedeći test pokazuje kako se to radi:

@Test public void givenSvcTimeoutOf100AndDefaultSettings_whenRemoteSvcExecuted_thenReturnSuccess () baca InterruptedException {HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey (HystrixCommandGroupKeyGepGeupGeuKeyGeapGeupGeupGeupGeupGeupGeupGeupGeupGeupGeaGePaGeKeyGeaPGeGePaGeGePaGeKeyGeapGeupGeaGePaGeKeyGeapGeupGeupGeaGePaGeKeaGeGaGePaGeKeaGeGeupGeeKeaGeGeup. assertThat (novi RemoteServiceTestCommand (config, novi RemoteServiceTestSimulator (100)). execute (), jednakTo ("Uspjeh")); }

Do sada smo vidjeli kako zamotati pozive udaljenih usluga u HystrixCommand objekt. U odjeljku u nastavku pogledajmo kako se nositi sa situacijom kada se udaljena usluga počne pogoršavati.

5. Rad s udaljenom uslugom i obrambenim programiranjem

5.1. Obrambeno programiranje s vremenskim ograničenjem

Opća je praksa programiranja postavljanje vremenskih ograničenja za pozive udaljenim uslugama.

Počnimo s ispitivanjem kako postaviti vremensko ograničenje HystrixCommand i kako pomaže kratkim spojem:

@Test javna praznina givenSvcTimeoutOf5000AndExecTimeoutOf10000_whenRemoteSvcExecuted_thenReturnSuccess () baca InterruptedException {HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKee (HystrixGroupKeyText (HystrixGroupKeyText). HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter (); commandProperties.withExecutionTimeoutInMilliseconds (10_000); config.andCommandPropertiesDefaults (commandProperties); assertThat (novi RemoteServiceTestCommand (config, novi RemoteServiceTestSimulator (500)). execute (), jednakTo ("Uspjeh")); }

U gore navedenom testu odgađamo odgovor usluge postavljanjem vremenskog ograničenja na 500 ms. Također postavljamo vremensko ograničenje izvršenja HystrixCommand biti 10.000 ms, čime se omogućuje dovoljno vremena da daljinska usluga odgovori.

Sada da vidimo što će se dogoditi kada je vremensko ograničenje izvršenja manje od poziva vremenskog ograničenja usluge:

@Test (= očekuje HystrixRuntimeException.class) public void givenSvcTimeoutOf15000AndExecTimeoutOf5000_whenRemoteSvcExecuted_thenExpectHre () baca InterruptedException {HystrixCommand.Setter konfiguracija = HystrixCommand .Setter .withGroupKey (HystrixCommandGroupKey.Factory.asKey ( "RemoteServiceGroupTest5")); HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter (); commandProperties.withExecutionTimeoutInMilliseconds (5_000); config.andCommandPropertiesDefaults (commandProperties); novi RemoteServiceTestCommand (config, novi RemoteServiceTestSimulator (15_000)). izvršiti (); }

Primijetite kako smo spustili letvicu i postavili vremensko ograničenje izvršavanja na 5000 ms.

Očekujemo da usluga odgovori u roku od 5000 ms, dok smo uslugu postavili da reagira nakon 15 000 ms. Ako primijetite kada izvršite test, test će izaći nakon 5000 ms umjesto da čeka 15 000 ms i bacit će HystrixRuntimeException.

To pokazuje kako Hystrix ne čeka odgovor dulje od konfiguriranog vremena čekanja. To pomaže učiniti sustav koji zaštićen Hystrixom osjetljivijim.

U odjeljcima u nastavku razmotrit ćemo postavljanje veličine spremišta niti koja sprečava iscrpljivanje niti i raspravit ćemo o njegovoj koristi.

5.2. Obrambeno programiranje s ograničenim navojem

Postavljanje vremenskih ograničenja za poziv usluge ne rješava sve probleme povezane s udaljenim uslugama.

Kad udaljena usluga počne sporo reagirati, tipični program će i dalje pozivati ​​tu udaljenu uslugu.

Aplikacija ne zna je li udaljena usluga zdrava ili nije i nove niti stvaraju se svaki put kad se zahtjev zahtijeva. To će uzrokovati upotrebu niti na poslužitelju koji se već bori.

Ne želimo da se to dogodi jer su nam potrebne ove niti za druge udaljene pozive ili procese koji se izvode na našem poslužitelju, a također želimo izbjeći ubrzanje upotrebe CPU-a.

Pogledajmo kako postaviti veličinu spremišta niti u HystrixCommand:

@Test javna praznina givenSvcTimeoutOf500AndExecTimeoutOf10000AndThreadPool_whenRemoteSvcExecuted _thenReturnSuccess () baca InterruptedException {HystrixCommand.Setter config = HystrixCommand .Setter .withndGroupGexProuter. HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter (); commandProperties.withExecutionTimeoutInMilliseconds (10_000); config.andCommandPropertiesDefaults (commandProperties); config.andThreadPoolPropertiesDefaults (HystrixThreadPoolProperties.Setter () .withMaxQueueSize (10) .withCoreSize (3) .withQueueSizeRejectionThreshold (10)); assertThat (novi RemoteServiceTestCommand (config, novi RemoteServiceTestSimulator (500)). execute (), jednakTo ("Uspjeh")); }

U gore navedenom testu postavljamo maksimalnu veličinu reda, veličinu jezgrenog reda i veličinu odbijanja reda. Hystrix počet će odbijati zahtjeve kada maksimalan broj niti dosegne 10, a red zadataka dosegne veličinu 10.

Veličina jezgre je broj niti koje uvijek ostaju žive u spremištu niti.

5.3. Obrambeno programiranje s uzorkom kratkog prekidača

Međutim, još uvijek možemo poboljšati pozive na daljinsku uslugu.

Razmotrimo slučaj da je udaljena usluga počela propadati.

Ne želimo nastaviti pucati na zahtjeve i rasipati resurse. Idealno bi bilo da na određeno vrijeme zaustavimo podnošenje zahtjeva kako bismo usluzi dali vremena za oporavak prije nego što nastavimo sa zahtjevima. To je ono što se naziva Prekidač kratkog spoja uzorak.

Pogledajmo kako Hystrix implementira ovaj obrazac:

@Test javna praznina givenCircuitBreakerSetup_whenRemoteSvcCmdExecuted_thenReturnSuccess () baca InterruptedException {HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey (HystrixCommandGroupKeyGerGroupKeyGerGroupKeyGerGroupKeyGerGroupKey. HystrixCommandProperties.Setter svojstva = HystrixCommandProperties.Setter (); svojstva.withExecutionTimeoutInMilliseconds (1000); svojstva.withCircuitBreakerSleepWindowInMilliseconds (4000); properties.withExecutionIsolationStrategy (HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); svojstva.withCircuitBreakerEnabled (true); svojstva.withCircuitBreakerRequestVolumeThreshold (1); config.andCommandPropertiesDefaults (svojstva); config.andThreadPoolPropertiesDefaults (HystrixThreadPoolProperties.Setter () .withMaxQueueSize (1) .withCoreSize (1) .withQueueSizeRejectionThreshold (1)); assertThat (this.invokeRemoteService (config, 10_000), jednakTo (null)); assertThat (this.invokeRemoteService (config, 10_000), jednakTo (null)); assertThat (this.invokeRemoteService (config, 10_000), jednakTo (null)); Navoj.spavanje (5000); assertThat (novi RemoteServiceTestCommand (config, novi RemoteServiceTestSimulator (500)). execute (), jednakTo ("Uspjeh")); assertThat (novi RemoteServiceTestCommand (config, novi RemoteServiceTestSimulator (500)). execute (), jednakTo ("Uspjeh")); assertThat (novi RemoteServiceTestCommand (config, novi RemoteServiceTestSimulator (500)). execute (), jednakTo ("Uspjeh")); }
javni String invokeRemoteService (HystrixCommand.Setter config, int timeout) baca InterruptedException {String response = null; isprobajte {response = new RemoteServiceTestCommand (config, novi RemoteServiceTestSimulator (timeout)). execute (); } catch (HystrixRuntimeException ex) {System.out.println ("ex =" + ex); } odgovor na povratak; }

U gornjem ispitivanju postavili smo različita svojstva prekidača. Najvažniji su:

  • The CircuitBreakerSleepWindow koja je postavljena na 4.000 ms. Ovo konfigurira prozor prekidača i definira vremenski interval nakon kojeg će se nastaviti zahtjev za udaljenom uslugom
  • The CircuitBreakerRequestVolumeThreshold koja je postavljena na 1 i definira minimalni broj zahtjeva potrebnih prije razmatranja stope otkaza

S gore postavljenim postavkama, naša HystrixCommand će se sada otvoriti nakon dva neuspjela zahtjeva. Treći zahtjev neće pogoditi ni udaljenu uslugu iako smo postavili kašnjenje usluge na 500 ms, Hystrix će doći do kratkog spoja i naša metoda će se vratiti null kao odgovor.

Naknadno ćemo dodati a Navoj.spavanje (5000) kako bismo prešli granicu prozora za spavanje koji smo postavili. Ovo će uzrokovati Hystrix za zatvaranje kruga i sljedeći će zahtjevi uspješno proći.

6. Zaključak

Ukratko, Hystrix je dizajniran da:

  1. Pružaju zaštitu i kontrolu nad kvarovima i kašnjenjem usluga koje se obično pristupaju putem mreže
  2. Prestanite s kaskadnim kvarovima uslijed propadanja nekih usluga
  3. Ne uspije brzo i brzo se oporaviti
  4. Graciozno degradirajte gdje god je to moguće
  5. Praćenje i upozoravanje naredbenog centra u stvarnom vremenu na kvarove

U sljedećem postu vidjet ćemo kako kombinirati blagodati Hystrixa s Springovim okvirom.

Cjelovit kod projekta i svi primjeri mogu se naći na projektu github.