Das Projekt Spring Cloud Netflix integriert nur Spring Boot mit den Netflix Open Source Bibliotheken und bindet diese in die bekannte Auto-Konfiguration und das Binding von Spring Boot mit ein. Mit Hilfe von Spring Cloud Netflix ist es möglich durch wenige, einfache Annotationen die entsprechenden Komponenten von Netflix zu integrieren, konfigurieren und zu nutzen. Im Rahmen des Artikels geben wir ihnen einen Überblick über die Anbindung von Eureka (Service Discovery), Zuul (Intelligentes Routing), Ribbon (Client-seitiges Load-Balancing) und Hystrix (Circuit Breaker).
Eureka
Eine Microservice-Architektur beruht unter anderem darauf, dass die Services technisch unabhängig lauffähig sind. Dies bezieht auch mit ein, dass es mehrere Instanzen einzelner Services verteilt geben kann. Um nun die technische Kommunikation zwischen den verschiedenen Diensten zu ermöglichen, müssen sie natürlich wissen, unter welcher Netzwerk-Adresse die jeweilige Gegenseite zu erreichen ist. Dies könnte mit viel Aufwand und in Abhängigkeit der aktuellen Infrastruktur (manuell) konfiguriert werden. Die Alternative dazu ist eine sogenannte Service Discovery: ein eigener Dienst, dessen Aufgabe eine zentrale Adress-Verwaltung für Services darstellt. Im Netflix-Stack übernimmt Eureka diese Aufgabe, bestehend aus zwei Komponenten: dem Eureka Server und dem Eureka Client. Derzeit liegt Eureka in Version 1.0 vor, welche von Spring Cloud Netflix in den beiden Eureka-Starter-Projekten eingebunden werden.
Eureka Server
Der Eureka Server wird als eigenständiger Dienst innerhalb einer Microservice-Architektur betrieben und stellt die (de-)zentrale Anlaufstelle für die Eureka Clients dar. Im Gegensatz zu anderen Diensten empfiehlt sich hier eine feste Adresse pro Instanz. Dies vereinfacht die Konfiguration der registrierenden Dienste bzw. die Anfragen nach Service-Adressen. Mehrere Eureka-Server lassen sich so auch einfach zu einem Cluster zusammenschließen, da jeder Eureka Server selbst per Konvention auch Client ist und somit die gleichen Methoden (Registry-Fetch von den Peers) nutzen kann.
Als Basis für einen eigenen Eureka Server kann man sich an dem von Spring Cloud [1] bereitgestellten Projekt orientieren. Der Service kommt fertig konfiguriert und andere Dienste können sich einfach an der Adresse anmelden. Zum Umfang von Eureka gehört auch ein Dashboard (Abbildung 1), welches Auskunft über angemeldete Dienste und deren Status und Server-Details wie Replikationen, Speicherverbrauch und Uptime gibt.
Eureka Client
Die Aufgaben des Eureka Clients [2] lassen sich durch die typische Architektur ableiten: zum einen gibt es Application Clients, also Dienste, die andere Services kontaktieren möchten. Hier ist der Eureka Client dafür verantwortlich, Netzwerk-Adressen von den Eureka Servern zu erfragen und dem ausführenden Code zur Verfügung zu stellen. Da derzeit die komplette Registry bei Anfragen übertragen wird, werden die Antworten per Konvention client-seitig gecached. Ein Poll-Mechanismus erkundigt sich nach Aktualisierungen und fordert diese ggf. ein.
Auf der anderen Seite stehen die Application Services, welche selbst ihre Dienste anbieten. Der Eureka Client registriert den einzelnen Service am Eureka Server mittels einer REST-Anfrage (Operationen sind unter [3] beschrieben). Dieser gestartete Heartbeat-Prozess signalisiert in regelmäßigen Abständen dem oder den konfigurierten Eureka Server(n) den aktuellen Status. Bleiben die Heartbeats über längere Zeit aus, wird der Dienst als inaktiv markiert und nach einiger Zeit aus der Service Liste entfernt. Oft wird es hier zu Überschneidungen kommen, so dass ein Dienst sowohl als Client als auch als Service auftritt. Daher gibt es für den Eureka Client auch keine weitere Trennung, es kann aber festgelegt werden, ob sich ein Dienst am Eureka Server registrieren möchte oder nicht. Auch hier stellt Spring Cloud ein Demo-Projekt bereit, welches die Anmeldung am Eureka Server (s.o.) übernimmt. Es kann unter [4] abgerufen werden.
Ausblick Eureka 2.0
Als Motivation für die nächste Generation von Eureka werden von den Netflix-Entwicklern hauptsächlich [5] drei Gründe genannt:
- Anfragen des Eureka Clients werden derzeit mit der gesamten Registry beantwortet. Der Speicherbedarf könnte minimiert werden, wenn die Antwort nur nur das angefragte Subset enthielte.
- Derzeit werden Aktualisierungen vom Client angefragt (Polling). Zukünftig soll es möglich sein, sich für Änderungen einzuschreiben und die Updates zu empfangen (Push).
- Das aktuelle Replikations-Modell besteht aus der Client-Replikation der Daten innerhalb des Eureka Server Clusters (Peer-To-Peer) und dem Heartbeat, der an die konfigurierten Dienste sendet. Der aus dieser Replikation entstehende Traffic soll reduziert werden, was letztlich auch zu einer besseren Skalierbarkeit führt.
Derzeit wird bei Netflix an der Version 2.0 gearbeitet. Es gibt allerdings kein offizielles Release Date, so dass es aktuell nicht absehbar, ob (wobei wir fest davon ausgehen) und wann die aktualisierte Komponente über das Spring Cloud Projekt einsatzfähig ist.
Hystrix
Hat man mit Eureka einen Lookup auf Services umgesetzt so kommt Hystrix ins Spiel, die Aufrufe zu den externen Services abzusichern. Hystrix ist die Netflx Implementierung des Circuit Breaker Patterns. Da es in Microservice-Umgebungen Gang und Gäbe ist, mehrschichtige Service Calls zu haben kommt dem Library Hystrix eine zentrale Bedeutung zu. Es sorgt nämlich dafür, dass Fehler auf unteren Ebenen des Callstacks nicht nach oben kaskadieren. So schließt Hystrix beispielsweise bei zwanzig Fehlern in fünf Sekunden die Verbindung. Diesen Default kann man natürlich noch anpassen und zudem auf eine sogenannte Fallback-Implementierung umleiten. Zentrale Annotationen in Spring Cloud Netflix hierfür sind @EnableCircuitBreaker
auf Ebene der Anwendungskonfiguration und @HystrixCommand
auf Ebene von Methoden, deren Aufrufe man mit Hilfe des Circuit Breaker Patterns absichern will. Die Lösung von Spring sieht zudem noch eine Propagierung des Spring Security Contexts und die Nutzung von Spring Scopes vor. Mit Hilfe des @HystrixCommand
Attributs commandProperties
können sehr feingranulare Anpassungen an Hystrix konfiguriert werden.
Hystrix-Dashboard & Turbine
Wie beschrieben ist Hystrix die Implementierung des Circuit Breaker Patterns [6]. Das Monitoring der Funktionsfähigkeit von Diensten ist ein zentraler Erfolgsfaktor für Microservice-Architekturen. Aus der Hystrix-Bibliothek werden zu diesem Zweck Echtzeit-Metriken auf den Hystrix EventStream
geschrieben, zum einen für die einzelnen HystrixCommands
und zum anderen für die zugehörigen HystrixThreadPools
. Die Daten beinhalten dabei den Status des Circuit Breakers, erfolgreiche und fehlgeschlagene Operationen, Durchsatz in Anfragen pro Sekunde, die durchschnittlichen Antwort-Zeiten samt Perzentilen sowie Daten zum Thread Pool wie Größe und der Anzahl aktuell in der Queue befindlichen Ausführungen.
Das Hystrix-Dashboard [7] ist eine von Netflix entwickelte Komponente zur grafischen Visualisierung der Daten aus dem EventStream
. Abbildung 2 zeigt das Dashboard im Einsatz mit einem Mock-Stream aus den Beispielen von Spring Cloud [8]. Eine Einschränkung gilt für das Dashboard: es kann nur einen einzelnen Stream verarbeiten.
An dieser Stelle kommt Turbine [9] ins Spiel: dieser Teil des Netflix-Stacks aggregiert die Daten von multiplen, homogenen Streams zu einem Cluster und bietet diesen wiederum selbst als Event-Stream an. Das Hystrix-Dashboard ist darauf vorbereitet und zeigt unter anderem die Anzahl der aggregierten Hosts an.
Für beide Komponenten gibt es jeweils ein Spring-Cloud-Starter-Projekt [10] mit einer Beispiel-Implementierung, so dass die Integration ähnlich leichtfällt wie bei den übrigen Komponenten.
Clientseitiges Load Balancing: Ribbon
Ribbon ist ein clientseitiger Load Balancer, der es ermöglicht viel Kontrolle über das Verhalten von HTTP- und TCP-Clients auszuüben. Zentrales Konzept bei Ribbon ist der so genannte „Named Client“. In dessen Rahmen ist jeder Load-Balancer Teil einer Menge von Komponenten, die im Verbund einen entfernten Server kontaktieren. Dieser Verbund hat einen konkreten Namen und somit hat jeder Verbund seinen eigenen Application Context. Dies wird mit Hilfe der @RibbonClientConfiguration
bereitgestellt.
Will man den RibbonClient
über die defaults hinaus konfigurieren so stellt Spring Cloud Netflix folgende Beans bereit, die in der Konfiguration des RibbonClients
angepasst werden können:
Funktion | Bean/Interface | Default–Wert |
---|---|---|
IClientConfig | ribbonClientConfig | DefaultClientConfigImpl |
IRule | ribbonRule | ZoneAvoidanceRule |
IPing | ribbonPing | NoOpPing |
ServerList |
ribbonServerList | ConfigurationBasedServerList |
ServerListFilter |
ribbonServerListFilter | ZonePreferenceServerListFilter |
ILoadBalancer | ribbonLoadBalancer | ZoneAwareLoadBalancer |
Deklarative REST Clients: Feign
Feign hilft uns Entwicklern dabei Clients für RESTful Web Services einfacher umzusetzen. Feign basiert hierbei auf annotierten Interfaces, wobei die Annotationen aus Feign-spezifischen und JAX-RS Annotationen basieren. Spring Cloud Netflix erweitert das Feature-Set von Feign um die bekannten Spring MVC Annotationen. Bei der Verwendung von Feign, welches mit Hilfe von @EnableFeignClients
aktiviert wird, nutzt Spring Cloud Netflix automatisch Ribbon und Eureka um automatisch einen HTTP Client mit Load-Balancing bereitzustellen.
HTTP-Clients werden mit der Annotation @FeignClient
versehen. Die Defaults von Feign können jederzeit mit Hilfe einer dedizierten Konfiguration, welche als Parameter and die @FeignClient
Annotation übergeben werden kann, überschrieben werden. Dabei stellt Spring Cloud Netflix folgende Beans und Interfaces für Anpassungen bereit:
Funktion | Bean/Interface | Default |
---|---|---|
Decoder | feignDecoder | ResponseEntityDecoder |
Ecoder | feignEncoder | Spring Encoder |
Logger | feignLogger | Slf4jLogger |
Contract | feignContract | SpringMvcContract |
Feign.Builder | feignBuilder | HystrixFeign.Builder |
Weiterhin werden folgende Beans nicht automatisch von Spring Cloud Netflix im ApplicationContext
bereitgestellt. Allerdings werden Typen dieser Beans beim Hochfahren des FeignClients
im ApplicationContext
gesucht:
- Logger.Level
- Retryer
- ErrorDecoder
- Request.Options
- Collection
Gateway-Service: Zuul
Als Gateway- bzw. Edge-Service wird Zuul als Eintrittspunkt zu einer Microservice-Architektur genutzt, um den internen Aufbau (Service-Schnitt und Skalierung) zu verbergen und nach außen hin als geschlossenes System auftreten zu lassen. Diese Routing-Funktionalität von Zuul basiert auf einer Regelmaschine, die es erlaubt Regeln und Filter in jeder verfügbaren JVM-Sprache zu implementieren. Von Haus aus ist Unterstützung für Groovy und Java vorgesehen. Groovy-basierte Filter bieten die Besonderheit, dass sie zur Laufzeit hinzugefügt und geändert werden können, während in Java implementierte Filter leider ein Redeployment erfordern.
Durch intelligente Verteilung von eingehenden Requests, etwa nach geographischen Aspekten und im Round-Robin-Verfahren, und durch die Filter- und Routing-Regeln wird das server-seitige Load-Balancing erreicht und die Robustheit einzelner Services (siehe Hystrix) unterstützt. Aus architektonischer Sicht bietet es sich also an, diese Edge-Services ausreichend stark zu skalieren, was somit der Robustheit des Gesamtsystems zu Gute kommt.
Darüber hinaus werden dieser Software-Komponente weitere Aufgaben zuteil bzw. werden üblicherweise an dieser Stelle realisiert, u.a.:
- Sicherheit
- Authentifizierung
- Insights
- Service Migration
- Stress Testing
- Canary Testing
- Load Shedding
- Statisches Response Handling
- Active / Active Traffic Management
Zuul Embedded Reverse Proxy
Spring Cloud liefert einen Embedded Reverse Proxy auf Basis von Zuul, der es ermöglicht, dass eine UI-Anwendungen Aufrufe zu mehreren nachgelagerten Diensten durch einen Proxy ausführt. Aktiviert wird dieser Proxy durch die Annotation @EnableZuulProxy
in der Konfiguration der Anwendung. Dieser Proxy verwendet Ribbon um die entsprechende Instanz zu lokalisieren und führt sämtliche Ausrufe gleich via Hystrix aus. Verwendet man @EnableZuulProxy
zusammen mit einem Spring Boot Actuator wird automatisch ein weiterer HTTP Endpoint namens /routes bereitgestellt. Mit Hilfe eines GET
-Aufrufs kann man hierüber die abgebildeten Routen des Reverse Proxys aufrufen. Ein POST
forciert hingegen einen Refresh der bestehenden Routen.
Eine weitere, populäre Herangehensweise in Microservice-Landschaften ist das Ausphasen alter Service-Endpoints, auch Service-Strangulation genannt. Hierbei wird eine neue Implementierung eines Dienstes parallel zur bestehenden, alten Implementierung produktiv gesetzt und Aufrufe zur neuen Implementierung werden erst peu á peu hochgefahren. So würden Fehler, die sich gegebenenfalls im neuen Deployment befinden nicht sofort global zuschlagen sondern können frühzeitig bei wenigen Nutzern entdeckt werden. Der Zuul Proxy ist hierfür gut geeignet, da er in seiner Konfiguration mehrere Routen vorsieht.
Zuul für Spring Cloud wird mit einigen Zuul Filtern geliefert, die durch @EnableZuulProxy
automatisch aktiviert werden. Diese Filter können in der Konfiguration jederzeit durch zuul.<EinfacherKlassenName>.<filterTyp>.disable=true
deaktiviert werden.
Support für Polyglotte Umgebungen mit Sidecar
Arbeitet man in Umgebungen, welche neben Java oder Groovy auch nicht-JVM Sprachen verwenden so kann man mit Hilfe von Spring Cloud Netflix Sidecar dennoch Dienste wie Eureka, Ribbon oder den Config Server verwenden. Dies ist für allem dann interessant wenn man Legacy Dienste einbinden will, die nicht auf Spring Cloud Netflix basieren und für die man nicht selbst den Registriereungsprozess bzw. den Heartbeat implementieren möchte. Sidecar ist basierend auf den Ideen Netflix Bibliothek Prana entwickelt worden. Sidecar wir in der Konfiguration der Spring Anwendung mit @EnableSidecar
aktiviert. Dadurch wird eine einfache HTTP API aktiviert mit deren Hilfe sämtliche Instanzen bestimmter Services zugänglich gemacht werden. Diese Annotation aktiviert zugleich Hystrix (@EnableCircuitBreaker
), einen Zuul Proxy (@EnableZuulProxy
) und den Discovery Client von Eureka (@EnableDiscoveryClient
).