31

Hands-on Hystrix - Best Practices und Stolperfallen

Embed Size (px)

Citation preview

WER ERZÄHLT DIE NÄCHSTEN 50+ MINUTENETWAS?

Gerrit Brehmer, Karlsruhe

Auf der Arbeit: So�ware-Entwickler/Architekt bei der inovex GmbH

In der Freizeit: Meine Tochter & Smart Home

REPRESENTATIVE UMFRAGE

Wer hat Hystrix schon im Produktiv-Einsatz oder plant es in Kürze?

WARUM IST ROBUSTHEIT WICHTIG?Michael Nygard über den Alltag

bei mittleren bis großen SW-Systemen:

“The normal mode of operation is partial failure.“

BEISPIELE GROSSER AUSFÄLLE

VORAB: HÄUFIGES (ANTI-)PATTERN@Transactional @RequestMapping("api") public ResponseEntity updateUser(UserData data) { validate(data); // DB

checkWithExternalService(data); // Extern

Entity updatedUsed = saveToDb(data); // DB

return toResponseEntity(updatedUsed); // DB / DTO }

DB-Transaktion/Verbindung so kurz wie möglichWartende Requests blockieren kostbare RessourcenBöse: OpenSessionInViewFilter

VERURSACHER VON AUSFÄLLENTeilausfälle

HardwareSo�ware

FehlerkaskadenLaufzeitproblemeLast-Peaks

ABHÄNGIGKEITEN IM GRIFFNichtfunktionale Anforderungen

an den eigenen Dienstan und von externen Diensten

SLAs (leider viel zu selten vorhanden)Entkopplung & Isolation

Ziele:

Eine robuste und fehlertolerante AnwendungDie beste Fehlerbehandlung ist die, von der der Nutzer nichts mitbekommt

ENTKOPPLUNG - USE CASE #1Berechnung von kundenspezifischen Empfehlungen durch ein entferntes

System

enge Integration in einen Großteil der SeitenFehler/Timeouts schlagen sofort auf diese Seiten durch

Lösung mit Hystrix: Alternative Empfehlungen auf Basis von statischen Regeln/einfachen

Filtern (kein entferntes System) im Fehlerfall

ERGEBNIS OHNE HYSTRIX@Inject RecoService recoClient;

public Movies getReco(long customerId) { return recoClient.getMovies(customerId); }

ERGEBNIS MIT HYSTRIXSTATISCHER FALLBACK

public class RecoForCustomerCommand extends HystrixCommand<Movies> { ... public RecoForCustomerCommand(RecoService recoClient, long customerId) { super( Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("Reco")) .andCommandKey(HystrixCommandKey.Factory.asKey("RecoForCustomer")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RecoTP"))); this.recoClient = recoClient; this.customerId = customerId; } @Override protected Movies run() { return recoClient.getMovies(customerId); } @Override protected Movies getFallback() { return Movies.EMPTY_LIST; } }

public Movies getReco(long customerId) { return new RecoForCustomerCommand(recoClient, customerId).execute(); }

ERGEBNIS MIT HYSTRIXDYNAMISCHER FALLBACK

public class RecoForCustomerCommand extends HystrixCommand<Movies> { ... public RecoForCustomerCommand(RecoService recoClient, long custId) { super( Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("Reco")) .andCommandKey(HystrixCommandKey.Factory.asKey("RecoForCustomer")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RecoTP"))); this.recoClient = recoClient; this.customerId = custId; } @Override protected Movies run() { return recoClient.getMovies(customerId); } @Override protected Movies getFallback() { return InternalRecoClient.getTopMovies(); } }

public Movies getReco(long customerId) { return new RecoForCustomerCommand(recoService, customerId).execute(); }

ENTKOPPLUNG - USE CASE #2Fehler-Kaskaden: Laufzeit-Probleme wirken sich auch auf aufrufende

Systeme aus

begrenzte Ressourcenwerden gebundenAufwand für Connection-Handling steigt rapideSeiteneffekte in andereSysteme

ENTKOPPLUNG - USE CASE #2Lösung mit Hystrix:

Direkte FehlerrückgabeVerhindert weitereLaufzeitprobleme

HYSTRIX PATTERNSImplementierung diverser Resilience Patterns

Graceful degradation (UseCase 1)Fail Fast (Use case 2)Fail silentBulkheadingCircuit Breaker

CIRCUIT BREAKER

PITFALLSWENN DA NICHT DIE PRAXIS WÄRE...

THREAD-POOLS ODER SEMAPHORENThread Pools

können zwischen mehreren Commands geshared werdenbessere Isolierungspürbarer Overhead (0-9ms, ø 1ms)Thread-Pools verhindern nicht, dass Aufrufe dauerha� blockieren

Java Threads können nicht gesichert beendet werdenKomplexer in der Implementierung

Timeouts setzen - immer und überallNicht blockierende APIs verwenden (Java NIO Channel, Netty)Thread.currentThread().isInterrupted() nutzen

THREAD-POOLS ODER SEMAPHORENSemaphoren

minimaler OverheadTimeouts ab Hystrix 1.4 ... Aber:

Ausführungsdauer wird nicht verringert (Caller-Thread)Fallback wird in einem extra Thread ausgeführt, wenn Timeouterreicht wirdFallback-Rückgabe erst mit Abschluss Caller-Thread

Empfehlung für Aufrufe mit minimaler Latenz

EINSCHRÄNKUNGEN DURCH THREADPOOLSThreadLocals stehen nicht zur Verfügung

MDC / NDC Log Kontexte(DB-)TransaktionsstatusAktueller HTTP Request / Security ContextThread gebundene DI-Scopes (RequestScope, ThreadScope)

LÖSUNG THREADLOCALCustom ConcurrencyStrategy

public class CustomStrategy extends HystrixConcurrencyStrategy { @Override public <T> Callable<T> wrapCallable(final Callable<T> callable) { final Map context = MDC.getContext(); final RequestAttributes attr = RequestContextHolder.get(); return super.wrapCallable(new Callable<T>() { @Override public T call() throws Exception { try { MDC.replaceAll(context); RequestContextHolder.set(attr); return callable.call(); } finally { RequestContextHolder.resetRequestAttributes(); MDC.clear(); } ...

HystrixPlugins.getInstance().registerConcurrencyStrategy(new CustomStrategy());

EINSATZ IN SERVLET-/JEE CONTAINERNDynamisches Deployment statt Container RestartPotentielle Resource Leaks

Lösung:

Hystrix.reset()Servlet listenerShutdown HookSingelton @PreDestroy-Methode

EXCEPTIONHANDLINGHystrix Standard: Unchecked Exceptions

Eigene werden in HystrixRuntimeExceptions gewrapptKompatibilität Legacy Code

Neue FehlerfälleTimeout durch HystrixOffener Circuit, Pool ausgelastet

Sonderfall HystrixBadRequestExceptionStellt fehlerha�e Anfrage darKein FallbackKeine Fehler-Statistik

EXCEPTIONS: ABWÄRTSKOMPATIBILITÄTpublic abstract class LegacyCommand extends HystrixCommand { ... public T executeWithCheckedEx() throws CheckedIOException { try { return super.execute(); } catch (RuntimeException e) { if (isCircuitBreakerOpen() || isResponseRejected() || isResponseTimedOut() || isResponseShortCircuited()) { throw new CheckedIOException("req. aborted:"+e.getMessage()); } else if (e.getCause() instanceof CheckedIOException) { throw (CheckedIOException) e.getCause(); } else { throw e; } } }

HYSTRIX-ANNOTATIONS: JAVANICAAspekt zur Erstellung von HystrixCommands zur Laufzeit

Interceptoren & andere Aspekte greifen nicht!Konfiguration per Annotation

Einschränkung zur Laufzeit & WiederverwendbarkeitImplizite Regeln (Convention over Configuration)

Exception-Handling andersExceptiontypen können ignoriert werden und werden ohne"Verpackung" zurückgeworfenExceptionhandling ansonsten identischÄnderung Exception Handling durch AnpassungHystrixCommandAspect

HYSTRIX/(NETFLIX)-INTEGRATION MITSPRING

Spring Cloud NetflixFeatures per Annotation in der JavaConfig aktivieren

Javanica Aktivierung & ShutdownHookHystrix Event StreamHystrix DashboardHystrix Turbine (auf Cloud Anwendung spezialisiert)

Integration mit weiteren Netflix Bibliotheken (Heureka, Zuul,Ribbon,..)

MICROSERVICES & HYSTRIXNetflix einer der großen Treiber der Microservice ArchitekturHystrix wird hauptsächlich in deren API Gateways eingesetzt

Reduzierung der RequestsHandling vieler AbhängigkeitenParallele Verarbeitung (RxJava)Weitere Optimierungen:

RequestCollapserRequestCache

DEMOJHipster Stack als API Gateway

Angular JS / BootstrapSpring BootHystrix

Drei simple REST Microservices(Spring Boot)Monitoring

Hystrix DashboardKibana Dashboard (ELK)

FAZIT & AUSBLICKHystrix

Entkopplung und Isolierung leicht gemachtNeue Version bringt weitere Features und VerbesserungenDennoch: Komplexität mit Hystrix ist nicht zu unterschätzen

Hystrix für MicroservicesBei Inter-Service KommunikationIntegration externer DiensteAPI Gateway

BILDERVERZEICHNISHystrix Patterns:https://www.flickr.com/photos/ramnaganat/7154180752Circuit Breaker: http://martinfowler.com/bliki/CircuitBreaker.htmlPitfalls: https://www.flickr.com/photos/nafmo/1488330724

VIELEN DANK FÜR EURE AUFMERKSAMKEIT!