Eureka est une application de gouvernance de service décentralisée, et sa fonctionnalité distinctive est qu'elle peut être enregistrée avec l'adresse que vous avez configurée en tant que serveur et service. Ensuite, dans cet article, discutons du processus d'enregistrement d'Eureka.
1. Serveur d'Eureka
La classe principale du côté serveur d'Eureka est Eurekabootstrap, qui implémente un écouteur de ServletContextListener. Par conséquent, nous pouvons conclure que Eureka est implémentée en fonction des conteneurs de servlet. Le code clé est le suivant:
classe publique EurekabootStrap implémente ServletContextListener {//...omit Code associé / ** * Initialise Eureka, y compris la synchronisation avec d'autres pairs Eureka et la publication du registre. * * @see * javax.servlet.servletContextListener # contexteInitialialized (javax.servlet.servletContexTevent) * / @Override public void contextInitialized (ServletContexTevent Event) {try {initeurekingenvironment (); initeurekaserverContext (); ServletContext sc = event.getServletContext (); sc.setAttribute (eurekaservercontext.class.getName (), serverContext); } catch (Throwable E) {logger.Error ("Impossible de serveur Bootstrap eureka:", e); Jetez un nouveau RuntimeException ("Impossible de serveur Bootstrap Eureka:", E); }} // omettre le code connexe ......}Nous pouvons voir que lorsque l'initialisation de ServletContext est terminée, l'environnement Eureka sera initialisé, puis l'EurekaserverContex sera initialisé. Ensuite, nous jetons un œil à la méthode initeurekaservercontext:
/ ** * Init Hook pour le contexte du serveur. Remplacer la logique personnalisée. * / Protected void inIteurekaserverContext () lève une exception {// ...... ApplicationInFomanager ApplicationInFomanager = NULL; if (eurekaclient == null) {eurekainstanceConfig instanceConfig = isCloud (configurationManager.getDeploymentContext ())? Nouveau CloudInStanceConfig (): Nouveau MyDatacenteRinStanceConfig (); applicationInfomanager = new ApplicationInfomanager (InsaneConfig, new eurekaconfigbasedInstanceInfoprovider (instanceConfig) .get ()); EurekaclientConfig eurekaclientConfig = new defaulTeurekaclientConfig (); eurekaclient = new DiscoveryClient (applicationInfomanager, eurekaclientConfig); } else {applicationInfomanager = eurekaclient.getApplicationInfomanager (); } Registre PeerawareInStanceRegistry; if (iSaws (applicationInfomanager.getInfo ())) {registry = new awsinstanceRegistry (eurekaserverconfig, eurekaclient.geteurekaclientconfig (), servercodecs, eurekaclient); awsbinder = new awsbinderdelegate (eurekaserverconfig, eurekaclient.getEurekaclientConfig (), registre, applicationInfomanager); awsbinder.start (); } else {registry = new PeerawareInStanceRegistryIMPl (eurekaserverconfig, eurekaclient.getEurekaclientConfig (), servercodecs, eurekaclient); } //...Omit partie du code}Dans cette méthode, de nombreux objets liés au service Eureka seront créés. Ici, je répertorie deux objets principaux, à savoir eurekaclient et peerrawareinstanceRegistry. Nous parlerons de la partie client plus tard. Jetons un coup d'œil à quoi est utilisé PEERAWAREInStanceregistry. Ici, j'écris un diagramme de classe sur cette classe:
Selon le diagramme de la classe, nous pouvons clairement constater que l'interface de niveau supérieur de PeerawareInstanceRegistry est LeasEmanager et LookupService, où Lookupservice définit le comportement de l'exemple de découverte le plus élémentaire, tandis que LeasEnanager définit le traitement de l'enregistrement, du renouvellement et des opérations de récitation des clients. Donc, dans cet article, concentrons-nous sur la mise en œuvre des interfaces connexes de LeasEmanager. Avec le recul, nous examinons PeerawareInstanceRegistry. En fait, cette classe est utilisée pour copier des informations pertinentes sous plusieurs nœuds. Par exemple, si un nœud enregistre pour le renouvellement et la ligne hors ligne, la copie pertinente (notification) sera copiée dans chaque nœud via cette classe. Voyons comment il gère l'inscription des clients:
/ ** * enregistre les informations sur les {@link instanceInfo} et répliques * ces informations à tous les nœuds de pair eureka. S'il s'agit d'un événement de réplication * à partir d'autres nœuds de répliques, il n'est pas reproduit. * * @param info * le {@link instanceInfo} à enregistrer et à reproduire. * @param isReplication * true s'il s'agit d'un événement de réplication des autres nœuds de répliques, * faux autrement. * / @Override public void Register (final instanceInfo info, final booléan isReplication) {int leeduration = lease.default_duration_in_secs; if (info.getLeaseInfo ()! = null && info.getLeaseInfo (). GetDurationInSecs ()> 0) {leedrée = info.getLeaseInfo (). GetDurationInSecs (); } super.register (info, loué, isReplication); réplicateTopeers (action.register, info.getAppName (), info.getId (), info, null, isReplication); }Nous pouvons voir qu'il appelle la méthode de registre de la classe parent, puis reproduit le comportement correspondant aux autres nœuds via des réplicatetopeers. La réplication spécifique ne sera pas discutée ici. Concentrons-nous sur la méthode d'enregistrement. Nous trouvons la méthode Register () dans la classe parent:
/ ** * Enregistre une nouvelle instance avec une durée donnée. * * @see com.netflix.eureka.lease.leeSemanager # registre (java.lang.object, int, boolean) * / public void registre (inscription en inscription, int leedrée, booléen isReplication) {try {read.lock (); Map <string, lease <instanceInfo >> gmap = registry.get (registrant.getAppName ()); Registre.increment (isReplication); if (gmap == null) {final concurrenthashmap <string, lease <instanceInfo>> gnewmap = new concurrenthashmap <string, lease <instanceInfo>> (); gmap = registry.putifabsent (registrant.getAppName (), gnewmap); if (gmap == null) {gmap = gnewmap; }} Lease <stanceInfo> existantLease = gmap.get (registrant.getId ()); // conserve le dernier horodatage sale sans l'écraser, s'il y a déjà un bail si (existantLease! = Null && (existantLease.Getholder ()! = Null)) {Long existantlastDirtytimestamp = existantLease.Getholder (). GetLastDirtyTimestamp (); Long RegistrationlastDirTytimeStamp = enregistred.getlastDirtyTitreAmp (); Logger.debug ("Location existante Found (existant = {}, fournie = {}", existantlastDirtyTimeStamp, RegistrationlastDirtytimestamp); // Il s'agit d'un> au lieu d'un> = parce que si les horodatages sont égaux, nous prenons toujours le transport à distance // instance INSTRESTRAL Logger.warn ("Il y a un bail existant et le temps de bail existant {} est plus grand" + "que celui qui est enregistré {}", existantdirtytimestamp, enregistrement de l'enregistrement); // Le bail n'existe pas et il s'agit donc d'un nouvel enregistrement synchronisé (verrouillage) {if (this.expectecdNumberofrenewspermin> 0) {// puisque le client souhaite l'annuler, réduisez le seuil // (1 // pendant 30 secondes, 2 pendant une minute) this.ExpectedNumberOfrewspermin = this.expectédNumberoFumberOfrenewspermin + 2; this.numberofrenewsperminthreshold = (int) (this.expectedNumberofrenewspermin * ServerConfig.getRerewalperCenThreshold (); ! = null) {lease.setserviceUpTiMed (existant.getServiceUptimestamp ();Grâce au code source, trier brièvement le processus:
1) Tout d'abord, obtenez certaines colonnes d'objets d'instance de service basés sur l'application. S'il est nul, créez une nouvelle carte et ajoutez les informations d'application enregistrées actuelles à cette carte. Il y a un objet de location ici. Cette classe décrit les attributs de temps de T générique, tels que le temps d'inscription, le temps de démarrage du service, le temps de mise à jour final, etc. Vous pouvez prêter attention à sa mise en œuvre:
/ * * Copyright 2012 Netflix, Inc. * * Licencié sous la licence Apache, version 2.0 (la "licence"); * Vous ne pouvez pas utiliser ce fichier sauf conforme à la licence. * Vous pouvez obtenir une copie de la licence à * * http://www.apache.org/licenses/license-2.0 * * sauf si la loi applicable ou convenu par écrit, le logiciel * distribué sous la licence est distribué sur une base "en tant que", * sans garantie ou conditions de toute nature, exprimée ou impliquée. * Voir la licence pour les autorisations de gouvernance linguistique spécifiques et les * limitations sous la licence. * / package com.netflix.eureka.lease; import com.netflix.eureka.registry.abstractinstanceRegistry; / ** * décrit une disponibilité basée sur le temps d'un {@link t}. Le but est d'éviter * l'accumulation d'instances dans {@link abstractInstanceRegistry} en conséquence de fermetures ingrat * qui ne sont pas rares dans les environnements AWS. * * Si un bail s'écoule sans renouvellement, il expirera finalement en continu * marquant les {@link t} associés pour une expulsion immédiate - ceci est similaire à * une annulation explicite sauf qu'il n'y a pas de communication entre le * {@link t} et {@link leasmanager}. * * @author Karthik Ranganathan, Greg Kim * / classe publique Lease <T> {ENUM Action {registre, annuler, renouveler}; public static final int default_Duration_in_secs = 90; Private T Private; EvictionTictionTampe privé; Long d'enregistrement privétimestamp; Private Long ServiceUptimestamp; // Rendez-le volatile afin que la tâche d'expiration verra cette longue durée de la maintumetimetimetimestamp plus rapide plus rapide; longue durée privée; Location publique (T R, int DuréeInSecs) {Holder = R; EnregistrementtImStAmp = System.CurrentTimemillis (); LassUpDateTimeStamp = EnregistrementTiTamp; durée = (durée dessecs * 1000); } / ** * Renouveler le bail, utiliser la durée de renouvellement si elle a été spécifiée par le * associé {@link t} pendant l'inscription, sinon la durée par défaut est * {@link #default_dugration_in_secs}. * / public void renouvent () {LassUpDateTimeStamp = System.Currenttimemillis () + Durée; } / ** * annule le bail en mettant à jour le temps d'expulsion. * / public void annule () {if (epictionTimeStamp <= 0) {evictionTimeStamp = System.currenttimemillis (); }} / ** * Marquez le service comme up. Cela ne prendra que l'affect de la première fois que les appels suivants seront ignorés. * / public void ServiceUp () {if (ServiceUptimestamp == 0) {ServiceUptimestamp = System.CurrentTimeMillis (); }} / ** * Définissez l'horodatage du service de feuilles. * / public void setServiceUpTiTaMP (long ServiceUptimestamp) {this.serviceupTimeStamp = ServiceUptimestamp; } / ** * vérifie si le bail d'un {@link com.netflix.appinfo.instanceinfo {a expiré ou non. * / public boolean isExpired () {return isExpired (0l); } / ** * vérifie si le bail d'un {@link com.netflix.appinfo.instanceinfo {a expiré ou non. * * Notez qu'en raison de renouveler () faire la «mauvaise» chose et de définir plus de durée de latupdatetimetimestamp à + ce qu'il devrait être, l'expiration sera en fait 2 * durée. Il s'agit d'un bogue mineur et ne devrait affecter que * des instances qui ne feront pas face. Boolean est expiré (longue AdditionalAleSems) {return (evictionTimestamp> 0 || System.currenttimemillis ()> (LastupDatetimestamp + Durée + AdditionAlselems);} / ** * obtient les milliers de personnes depuis l'époque lorsque le bale a été enregistré. {Return Registrationtimestamp;} / ** * obtient les MIMONSECTS depuis l'époque lorsque le bail a été renouvelé. MILLIONSECTIONS depuis l'époque lorsque le bail a été expulsé. Long GetServiceUptimestamp () {return ServiceUptimestamp;} / ** * Renvoie le titulaire du bail.2) Selon l'ID actuellement enregistré, si vous pouvez l'obtenir sur la carte, procédez comme suit:
2.1) Selon le temps de contact du nœud actuel existant et le temps de contact du nœud enregistré, si l'ancien temps est plus tard que le dernier temps, l'instance actuellement enregistrée est soumise à l'instance existante.
2.2) Sinon, mettez à jour son numéro de renouvellement attendu par minute et son seuil
3) Enregistrez le nœud d'enregistrement actuel dans la carte, et notre processus d'enregistrement a essentiellement pris fin
2. Client Eureka
Lorsque le serveur ServletContext est initialisé, un DiscoveryClient sera créé. Les amis qui connaissent Eureka doivent être familiarisés avec ces deux attributs: FetchRegistry et Registrewitheureka. Lors de l'exécution en mode indépendant Eureka intégré dans SpringCloud, si ces deux valeurs ne sont pas fausses, le démarrage rapportera une erreur. Pourquoi signale-t-il une erreur? En fait, la réponse réside dans le constructeur de DiscoveryClient:
@Inject DiscoveryClient (ApplicationInfomanager ApplicationInfomanager, EurekaclientConfig Config, AbstractDiscoveryClientOptionalArgs args, fournisseur <AutainupRegistry> BackupRegistryProvider) {//....oMit Part du code if (! Config. ShoulgisterWithereka () &&! Config. ni enregistrer ni requête pour les données. "); planificateur = null; HeartbeateExEcutor = null; cacheRefreshexecutor = null; eurekatransport = null; INSCENEGIONCHECKER = NOUVEAU INSCENERGIONCHERCHER (NEW PROMPTESSIONBASTAZTOREGIONMAPPER (config), clientConfig.getRegion ()); // C'est un peu de piratage pour permettre le code existant en utilisant DiscoveryManager.getInstance () // pour travailler avec DiscoveryClient DiscoveryManager.getInstance (). SetDiscoveryClient (this); DiscoveryManager.getInstance (). SETEUREKACLIENTCONFIG (config); IniTTIMESTAMPMS = System.CurrentTimeMillis (); Logger.info ("Discovery Client initialisé sur TimeStamp {} avec les instances initiales Count: {}", inittimestampms, this.getApplications (). size ()); retour; // pas besoin de configurer des tâches de réseau et nous avons terminé} Essayez {// Taille par défaut de 2 - 1 chacun pour Heartbeat et CacheRefresh Scheduler = EMICRORS.NEWSCHEDULEDTHERRENDPOOL (2, New ThreadFactoryBuilder () .SetNameFormat ("DiscoveryClient-% d") .setDaemon (true) .build (); HeartbeateExEcutor = new ThreadPoolExecutor (1, clientConfig.getheartbeateExEcutorthReadpoolSize (), 0, timeunit.seconds, new SynchronousQueue <Runnable> (), New ThreadFactoryBuilder () .SetNameformat ("DiscoveryClient-HeartBeatexEcutor-% d"). )); // Utilisez le transfert direct cacherefreshexecutor = new ThreadPoolExecutor (1, clientConfig.getCachereFreshexeCutorthReadpoolSize (), 0, TimeUnit.Seconds, New Synchronousqueee <Runnable> (), New ThreadFactoryBuilder (). .setDaemon (true) .build ()); // Utiliser le transfert direct eurekatransport = new eurekatransport (); ScheduleServerendPointTask (Eurekatransport, args); //....OMit certains code initscheduledTasks (); // ....}Sur la base du code source, nous pouvons tirer les conclusions suivantes:
1) Si les deux devraient registrewitheureka et devraient fetchRegistry sont faux, revenez directement.
2) Créez une piscine de fil qui envoie des battements cardiaques et rafraîchisse les caches
3) Initialiser les tâches chronométrées créées
Voyons ensuite le code suivant dans la méthode initscheduledTasks ():
// HeartBeat Timer Scheduler.Schedule (New TimeSuperVisortask ("Heartbeat", Scheduler, HeartbeateExecutor, Renewalintervalinsecs, TimeUnit.seconds, Expbackoffbound, New HeartbeatThread ()), RenewalIntervalinsecs, TimeUnit.seconds);Voici un fil qui déclenche une exécution chronométrée, en secondes, et exécute un rythme cardiaque envoyant en fonction de la valeur de renouvelleurvalinsecs. Le thread HeartBeatThread s'exécute comme suit:
/ ** * La tâche du rythme cardiaque qui renouvelle le bail dans les intervalles donnés. * / class private heartbeatthread implémente runnable {public void run () {if (renouvel ()) {LastSuccessfulHeartBeattimeStamp = System.currenttimemillis (); }}}Nous pouvons voir que la méthode d'exécution est très simple pour exécuter la méthode de renouvellement et si le temps est enregistré avec succès. Méthode de renouvellement:
/ ** * renouveler avec le service eureka en effectuant l'appel de repos approprié * / boolean renouveler () {eurekahttpResponse <instanceInfo> httpResponse; essayez {httpResponse = eurekatransport.RegistrationClient.SendHeartBeat (instanceInfo.getAppName (), instanceInfo.getID (), instanceInfo, null); Logger.Debug ("{} - Statut Heartbeat: {}", Prefix + AppPathIdentifier, httpResponse.getStaturScode ()); if (httpResponse.getStaturScode () == 404) {rerengister_counter.increment (); Logger.info ("{} - Réenregistrer les applications / {}", Prefix + AppPathIdentifier, instanceInfo.getAppName ()); long himestamp = instanceInfo.setSidirTyWithTime (); Boolean Success = Register (); if (Success) {instanceInfo.unSetIsDirty (TimeStamp); } Retour succès; } return httpResponse.getStaturScode () == 200; } catch (Throwable E) {logger.error ("{} - n'a pas pu envoyer de battement de cœur!", Prefix + AppPathIdentifier, E); retourne false; }}Si le rythme cardiaque est envoyé ici, si le retour est de 404, l'opération d'enregistrement sera effectuée. Notez que sur la base de la valeur de retour httpResponse, nous pouvons conclure que toutes ces opérations sont basées sur les demandes HTTP. Est-ce vrai? Continuons à examiner la méthode du registre:
/ ** * Inscrivez-vous au service Eureka en effectuant l'appel de repos approprié. * / booléen registre () lance Throwsable {Logger.info (prefix + apppathIdentifier + ": Service d'enregistrement ..."); EurekahttpResponse <void> httpResponse; try {httpResponse = eurekatransport.RegistrationClient.Register (instanceInfo); } catch (exception e) {logger.warn ("{} - Enregistrement a échoué {}", prefix + appatAntiDifier, e.getMessage (), e); jeter e; } if (logger.isinfoenabled ()) {Logger.info ("{} - Statut d'enregistrement: {}", Prefix + AppPathIdentifier, httpResponse.getStaturScode ()); } return httpResponse.getStaturScode () == 204; }Ici, la méthode d'enregistrement Client dans Eurekatransport est appelée:
classe finale statique privée Eurekatransport {Frivate ClosableResolver BootstrapResolver; Private TransportClientFactory TransportClientFactory; Private EurekahttpClient Enregistrement Client; Private EurekahttpClientFactory Enregistrement CLIENTFACTORY; requête privée eurekahttpclient; Private eurekahttpclientFactory QueryClientFactory; void shutdown () {if (RegistrationClientFactory! = null) {RegistrationClientFactory.shutdown (); } if (queryClientFactory! = null) {queryClientFactory.shutdown (); } if (EnregistrementClient! = null) {RegistrationClient.shutdown (); } if (queryClient! = null) {queryClient.shutdown (); } if (transportClientFactory! = null) {TransportClientFactory.shutdown (); }}}Ici, nous pouvons voir que le client d'Eureka utilise la demande HTTP pour enregistrer le service, ce qui signifie que lorsque nous créons DiscoveryClient, nous enregistrerons l'instance avec le serveur.
3. Service de repos fourni par le serveur
Nous avons déjà vu le code fourni par le serveur pour gérer les demandes d'enregistrement des clients. Étant donné que le client s'inscrit via le protocole HTTP, le serveur doit avoir une adresse pour gérer cette demande HTTP. En fait, le serveur Eureka utilise la norme JAX-RS pour fournir une méthode de repos pour exposer le service. Nous pouvons jeter un œil à la méthode Addinstance de cette application Resource:
/ ** * enregistre des informations sur une instance particulière pour un * {@link com.netflix.discovery.shared.application}. * * @param info * {@link instanceInfo} Informations de l'instance. * @param isReplication * Un paramètre d'en-tête contenant des informations si cela est * reproduit à partir d'autres nœuds. * / @Posost @Consumes ({"Application / JSON", "Application / XML"}) Public Response Addinstance (instanceInfo Info, @headerParam (peereurekanode.header_replication) String isReplication) {logger.debug ("enregistrement instance {} (réplication = {})", info.getid (), Isreplication); // valider que l'instanceInfo contient tous les champs requis nécessaires if (isBlank (info.getId ())) {return réponse.status (400) .entity ("manquer instanceId"). build (); } else if (isBlank (info.GethostName ())) {return réponse.status (400) .entity ("nom hôte manquant"). build (); } else if (isBlank (info.getIpAddr ())) {return réponse.status (400) .entity ("Adresse IP manquante"). build (); } else if (isBlank (info.getAppName ()))) {return réponse.status (400) .Entity ("Appname manquant"). build (); } else if (! appname.equals (info.getAppName ()))) {return réponse.status (400) .entity ("APPNAMADED APPNAME, attendant" + appname + "mais était" + info.getAppName ()). build (); } else if (info.getDatacEnteRinfo () == null) {return réponse.status (400) .entity ("manquant datacenteRinfo"). build (); } else if (info.getDatacEnteRinfo (). getName () == null) {return réponse.status (400) .entity ("manquant datacenteRinfo"). build (); } else if (info.getDatacEnteRinfo (). getName () == null) {return réponse.status (400) .entity ("manquant datacenteRinfo name"). build (); } // gérer les cas où les clients peuvent s'inscrire auprès de Bad DataCenteRinfo avec des données manquantes DatacenteRinfo DatacenteRinfo = info.getDatacenteRinfo (); if (datacenteRinfo instanceof UniqueIdentifier) {String dataCenteRinfoid = ((unique identifier) datacenteRinfo) .geTID (); if (isBlank (datacenteRinfoid)) {boolean expérimental = "true" .equalsignorecase (serverconfig.getExperimental ("Enregistrement.validation.datacenteRinfoid")); if (expérimental) {string entity = "datacenteRinfo de type" + datacenteRinfo.getClass () + "doit contenir un ID valide"; retour réponse.status (400) .Entité (entité) .build (); } else if (datacenterinfo instanceof amazoninfo) {amazoninfo amazoninfo = (amazoninfo) datacenteRinfo; String EffectiveId = AmazonInfo.get (Amazoninfo.MetAdatakey.instanceId); if (efficaceId == null) {amazoninfo.getMetAdata (). put (amazoninfo.metadatakey.instanceId.getName (), info.getId ()); }} else {logger.warn ("Enregistrement de DatacenteRinfo de type {} sans ID approprié", DatacenteRinfo.getClass ()); }}}} registry.register (info, "true" .equals (isReplication)); retour réponse.status (204) .build (); // 204 pour être en arrière compatible}Ce qui précède est tout le contenu de cet article. J'espère que cela sera utile à l'apprentissage de tous et j'espère que tout le monde soutiendra davantage Wulin.com.