Articolul prezintă o soluție tehnică pentru conceptul Descoperirii Serviciilor folosind Netflix Eureka, o tehnologie dezvoltată în Java de către Netflix, expusă open-source în cadrul Netflix Open Source Software (OSS).
Exemplele de cod și configurări prezentate nu folosesc Eureka direct. Folosim Spring Cloud, o inițiativă de la Spring care reunește cele mai bune soluții tehnice pentru a construi un sistem distribuit modern. Spring Cloud este construit peste Spring Boot și are suport pentru integrarea Netflix OSS.
Descoperirea Serviciilor este unul dintre conceptele cheie în construirea sistemelor distribuite orientate pe servicii.
Un Serviciu A pentru a apela un Serviciu B, trebuie să găsească o instanță fizică a Serviciului B.
Configurările statice nu mai sunt o soluție viabilă într-un sistem elastic, dinamic, unde instanțele de servicii sunt pornite și oprite frecvent (planificat și neplanificat) sau unde problemele de rețea pot fi dese (Cloud). Găsirea unei instanțe a Serviciului B nu mai este un lucru banal.
Descoperirea Serviciilor implică un mecanism unde:
Serviciile nu au cunoștințe anterioare despre locații fizice ale instanțelor altor servicii.
Instanțele Serviciilor publică informații despre apariția și dispariția lor.
Serviciile sunt capabile să găsească instanța altor servicii pe baza informațiilor publicate de către acestea din urmă.
Anomaliile la nivel de instanță de serviciu sunt detectate, iar acestea nu mai sunt instanțe valide.
Arhitectura Netflix Eureka constă în două componente, Serverul și Clientul. Serverul este o aplicație de sine stătătoare și este responsabilă de:
gestiunea unui registru de instanțe de servicii;
punerea la dispoziție a mijloacelor prin care instanțele de servicii pot fi înregistrate, șterse sau descoperite în registru;
Clientul face parte din ecosistemul Serviciului propriu zis și are următoarele responsabilități:
anunță Serverul despre existenta și dispariția Instantei de Serviciu;
implementează un mecanism de transmitere de heartbeat-uri către Server;
Eureka lucrează cu Applications și Instances. Căutarea se face după id-ul serviciului, iar rezultatele constau în informații despre instanțele Serviciului care se regăsesc în registru de instanțe.
Netflix Eureka este construit pentru High Availability. În termenii teoremei CAP, Eureka favorizează Availability în defavoarea Consistenței.
Accentul e pus pe faptul ca serviciile să se găsească în cazul partiționărilor de rețea sau problemele de Eureka Server.
High Availability este obținut la două niveluri:
Cluster de Servere. În acest nivel, setup-ul de producție va include mai multe instanțe de Eureka Server configurate în cluster.
Eureka a fost construită pentru a fi funcțională în Cloud-ul Amazon (AWS). În consecință, folosește o terminologie specifică Amazon: regiuni, zone, etc. .Exemplele prezentate în acest articol folosesc setări implicite, regiunea us-east-1 și zona defaultZone.
Serverul este ceea ce numim Serviciul de Descoperire într-un sistem SOA.
Începeți prin a clona repo-ul Spring Cloud Eureka de pe github.com.
Este simplu de pornit o aplicație Eureka Server folosind Spring Cloud. Orice aplicație Spring Boot devine un Server Eureka folosind adnotarea \@EnableEurekaServer. Folosiți exemplele următoare pentru development pe mediul local.
Exemplu aplicație Java:
@SpringBootApplication
@EnableEurekaServer
@EnableDiscoveryClient
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
server:
port: 8761
security:
user:
password: ${eureka.password}
eureka:
password: ${SECURITY_USER_PASSWORD:password}
server:
waitTimeInMsWhenSyncEmpty: 0
enableSelfPreservation: false
client:
preferSameZoneEureka: false
---
spring:
profiles: devlocal
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://user:${eureka.password:${SECURITY_USER_PASSWORD:password}}@localhost:8761/eureka/
mvn spring-boot:run -Drun.jvmArguments="-Dspring.profiles.active=devlocal"
Setările registerWithEureka și fetchRegistry au valoarea false, ceea ce înseamnă ca Serverul nu face parte dintr-un cluster.
Serverele Eureka sunt configurate în mod cluster pentru a asigura disponibilitatea în cazul în care unele instanțe au probleme.
Clienții eureka nu au nevoie de afinitate pentru Server și se pot conecta transparent la oricare Server din Cluster.
Serverele au nevoie de referințe către alte instanțe de Server.
Exista diferite modalități pentru a obține aceste referințe, descrise mai jos.
Netflix folosește DNS pentru a gestiona lista de referințe Eureka Server într-un mod dinamic. Clienții Eureka nu au nevoie de repornire pentru a obține noile configurări, ci le obțin printr-o nouă interogare DNS.
Aceasta este configurarea recomandată de producție.
Să vedem un exemplu cu un cluster de două servere Eureka, dsc01 și dsc02.
Se poate folosi Bind sau alt server DNS. Aici găsiți instrucțiuni pentru a instala și configura Bind.
Configurație DNS:
$TTL 604800
@ IN SOA ns.eureka.local. hostmaster.eureka.local. (
1024 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS ns.eureka.local.
ns IN A 10.111.42.10
txt.us-east-1 IN TXT "defaultZone.eureka.local"
txt.defaultZone IN TXT "dsc01" "dsc02"
;
Configurația aplicației:
eureka:
client:
registerWithEureka: true
fetchRegistry: true
useDnsForFetchingServiceUrls: true
eurekaServerDNSName: eureka.local
eurekaServerPort: 8761
eurekaServerURLContext: eureka
Configurarea clusterului prezentat anterior:
eureka:
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://dsc01:8761/eureka/,http://dsc02:8762/eureka/ Orice schimbare a listei necesită o repornire a Clientului Eureka pentru a obține lista actualizată.
În cazul în care se dorește pornirea mai multor instanțe de Server pe aceeași mașină, trebuie folosite hostname-uri diferite pentru fiecare instanță. Eureka identifică pe baza combinației hostname și port. Hostname-urile trebuie să rezolve către localhost. în Windows OS se folosește fișierul hosts.
eureka:
instance:
hostname: server1
Eureka Server oferă în interfața grafică pentru a observa statusul și detalii despre Instantele înregistrate.
Noi avem un mediu în care evaluăm tehnologii. Inițial am pornit cu exemplele Spring Cloud și le-am modificat pentru nevoile noastre.
Exemplul de mai sus prezintă un Server Eureka din mediul nostru. Sunt prezentate următoarele:
Replici ale Discovery Server (DS): lista celorlalte Servere din cluster, cu mențiunea celor disponibile și celor nedisponibile;
Această interfața prezintă mai multe detalii, http://dsc02:8761/eureka/apps (copy&paste din browser):
<?xml version="1.0"?>
-<applications>
<versions__delta>1
</versions__delta>
<apps__hashcode>UP_11_
</apps__hashcode>
+<application>
+<application>
+<application>
-<application>
<name>BESTPRICES</name>
+<instance>
-<instance>
<hostName>clj-lcpdevsrv10
</hostName>
<app>BESTPRICES</app>
<ipAddr>10.111.42.71</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN
</overriddenstatus>
<port enabled="true">8000</port>
<securePort enabled="true">443
</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
-<leaseInfo>
<renewalIntervalInSecs>30
</renewalIntervalInSecs>
<durationInSecs>90</durationInSecs>
<registrationTimestamp>
1426271956166
</registrationTimestamp>
<lastRenewalTimestamp>
1427199350993
</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>1426157045251
</serviceUpTimestamp>
</leaseInfo>
<metadata class="java.util.Collections$EmptyMap"/>
<appGroupName> MYSIDECARGROUP</appGroupName>
<homePageUrl>http://clj-lcpdevsrv10:8000/
</homePageUrl>
<statusPageUrl>http://clj-lcpdevsrv10:8001/info
</statusPageUrl>
<healthCheckUrl>http://clj-lcpdevsrv10:8001/health
</healthCheckUrl>
<vipAddress>bestprices</vipAddress>
<isCoordinatingDiscoveryServer>false
</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1426271956166
</lastUpdatedTimestamp>
<lastDirtyTimestamp>1426271939882
</lastDirtyTimestamp>
<actionType>ADDED</actionType>
</instance>
</application>
+<application>
+<application>
+<application>
</applications>
Elementul "instance" prezintă informații complete despre o instanță înregistrată.
Majoritatea detaliilor sunt clare și prezintă informații precum locația fizica a instanței, momentul ultimei înnoiri, etc. .
URL-urile de healthcheck poți fi folosite de către tool-uri externe de monitorizare.
Informații proprii pot fi adăugate cu rolul de metadata.
Clientul Eureka face parte din ecosistemul unei Instanțe de Serviciu business.
Poate fi folosit în mod embedded sau ca și process atasat (logic).
Netflix sugerează ca modul embedded să fie folosit pentru servicii Java și modul atașat pentru servicii scrise în limbaje non-JVM.
Clientul este configurat cu o listă de Servere Eureka. Configurațiile anterioare din cazul Eureka Server se aplică și la Client.
Orice aplicație Spring Boot devine un Client Eureka folosind adnotarea @EnableDiscoveryClient și având Eureka în Classpath:
@SpringBootApplication
@EnableDiscoveryClient
public class SampleEurekaClientApp extends RepositoryRestMvcConfiguration {
public static void main(String[] args) {
SpringApplication.run(CustomerApp.class, args);
}
}
Clientul și Serverul implementează un protocol de heartbeat-uri. Clientul trebuie să trimită mesaje de heartbeat regulat către Server. Serverul așteaptă aceste mesaje pentru a menține instanța de serviciu în registru sau pentru a reînnoi informații despre aceasta. În cazul în care aceste mesaje nu sunt primite, instanța este scoasă din registru.
Mesajele heartbeat pot specifica și un status pentru instanța: UP, DOWN, OUT_OF_SERVICE, cu consecințe imediate pentru rezultatele de descoperire returnate.
Eureka Server are un mod protecție: în cazul în care un anumit număr de instanțe nu mai trimit mesaje heartbeat într-un anumit interval de timp, serverul blochează registrul și nu va șterge automat aceste instanțe. Consideră că a avut loc o partiționare a rețelei fizice și așteaptă ca aceste instanțe să revină când problema fizică se rezolvă. Această capabilitate este utilă în cazul Cloud și poate fi oprită în cazul data center-elor private.
Una dintre capacitățile speciale ale Eureka este Cach-ul local Client. Clientul obține regulat informații din registru Eureka Server și le stochează local. La un moment dat poate avea aceleași informații ca și Server-ul. În cazul în care toate serverele devin indisponibile sau clientul este izolat de servere printr-o problemă fizică de rețea, acest Client poate funcționa în continuare cu rezultate bune până când acest cache local devine învechit.
Cache-ul local îmbunătățește performanța comunicării între servicii pentru că nu mai este nevoie de a trece peste încă un hop intermediar cu scopul de a face balansarea apelurilor.
Clientul Eureka poate fi folosit în mod direct sau cu ajutorul altor librării ce se integrează cu Eureka.
În continuare sunt prezentate două opțiuni de folosire a clientului Eureka pentru a apela un serviciu, în cazul acesta serviciu bestprices din mediul nostru de evaluare:
...
@Autowired
private DiscoveryClient discoveryClient;
...
private BestPrice findBestPriceWithEurekaclient(final String productSku) {
BestPrice bestPrice = null;
// get hold of a service instance from Eureka
ServiceInstance instance = null;
// "bestprices" is the name of the service in Eureka
List instances = discoveryClient
.getInstances("bestprices");
if (instances != null && instances.size() > 0) {
instance = instances.get(0); //could be random
// Invoke server based on host and port.
// Example using RestTemplate.
URI productUri = URI.create(String
.format("http://%s:%s/bestprices/" + productSku,
instance.getHost(), instance.getPort()));
bestPrice = restTemplate.getForObject(productUri,
BestPrice.class);
}
return bestPrice;
}
Ribbon este un client HTTP și un load balancer software de la Netflix și care se integrează cu Eureka:
...
@Autowired
private LoadBalancerClient loadBalancerClient;
...
private BestPrice findBestPriceWithLoadBalancerClient(final String productSku) {
BestPrice bestPrice = null;
// "bestprices" is the name of the service in
// Eureka, as well as of the Ribbon LoadBalancer
// which gets created automatically.
ServiceInstance instance = loadBalancerClient
.choose("bestprices");
if (instance != null) {
// Invoke server, based on host and port.
// Example using RestTemplate.
URI productUri = URI.create(String
.format("http://%s:%s/bestprices/" + productSku,
instance.getHost(), instance.getPort()));
bestPrice = restTemplate.getForObject(productUri,
BestPrice.class);
}
return bestPrice;
}
Acest aspect nu va fi prezentat extensiv în acest articol și poate fi subiectul altuia.
Exista două abordări:
REST API - Eureka expune un API RESt prin care alte aplicații pot face interogări de discovery. Partea negativă este ca toate aceste aplicații trebuie să își implementeze mecanismele și protocoalele pe care Clientul Eureka Java le are deja.
Eureka este o soluție dedicată pentru Service Discovery.
Eureka este o soluție simplă și robustă.
Eureka e High Available.
Eureka implementează Cache Local Client.
Eureka e open source și menținută de Netflix.