Eureka es una aplicación de gobierno de servicio descentralizada, y su característica distintiva es que se puede registrar con la dirección que está configurada como servidor y un servicio. Luego, en este artículo, discutamos el proceso de registro de Eureka.
1. Servidor de Eureka
La clase principal del lado del servidor de Eureka es Eurekabootstrap, que implementa un oyente de ServletContextListener. Por lo tanto, podemos concluir que Eureka se implementa en función de los contenedores de servlet. El código clave es el siguiente:
La clase pública Eurekabootstrap implementa servletContextListener {//...omit Código relacionado/*** Inicializa Eureka, incluida la sincronización con otros pares de Eureka y publicando el registro. * * @see * javax.servlet.servletContextListener#contextInitialized (javax.servlet.servletContextEvent) */ @Override public void contextInitialized (servletContextEvent Event) {try {initeurekaenvironment (); initeurekaserverContext (); ServletContext sc = event.getServletContext (); Sc.SetAttribute (eurekaserverContext.class.getName (), serverContext); } catch (Throwable e) {logger.error ("No se puede arrancar el servidor Eureka:", e); tirar nueva runtimeException ("no se puede arranque eureka servidor:", e); }} // omitir el código relacionado ......}Podemos ver que cuando se complete la inicialización de ServletContext, se inicializará el entorno Eureka y luego se inicializará el eurekaserverContext. Luego estamos echando un vistazo al método initeurekaserverContext:
/*** Gancho Init para el contexto del servidor. Anular la lógica personalizada. */ protegido void initeurekaserverContext () lanza la excepción {// ...... ApplicationInfomaner AplicationInfomanerger = null; if (eurekaclient == null) {eurekainstanceconfig instanceconfig = isCloud (configurationManager.getDePloymentContext ())? nuevo CloudInStancEconFig (): nuevo myDatAcenterInStancEconfig (); ApplicationInfomanager = new ApplicationInfomanager (InstanceconFig, new EurekaconfigBasedInstanceInfoprovider (Instanceconfig) .get ()); EurekaclientConfig eurekaclientConfig = new DefaulteureKaclientConfig (); eurekaclient = new DiscoveryClient (ApplicationInfomanager, eurekaclientConfig); } else {ApplicationInfomanager = eurekaclient.getApplicationInfomanager (); } Registro de PeeraWareInstanceRegistry; if (isaws (applicationInfomanager.getInfo ())) {Registry = new AwsInstanceRegistry (eurekaserverConfig, eurekaclient.geteurekaclientCig (), servercodecs, eurekaclient); AWSBinder = new AWSBinderDelegate (eurekaserverConfig, eurekaclient.geteureKaclientConfig (), Registry, ApplicationInfomanager); awsbinder.start (); } else {Registry = new PeerawareInstanceRegistryImpl (eurekaserverConfig, eurekaclient.geteurekaclientConfig (), serverCodecs, eurekaclient); } //...Omit parte del código}En este método, se crearán muchos objetos relacionados con el servicio Eureka. Aquí enumero dos objetos centrales, a saber, Eurekaclient y PeeraWareinstanceregistry. Hablaremos sobre la parte del cliente más tarde. Echemos un vistazo a para qué se usa PeerawareInstanceregistry. Aquí escribo un diagrama de clase sobre esta clase:
Según el diagrama de clases, podemos encontrar claramente que la interfaz de nivel superior de PeeraWareInstanceregistry es LeaseManager y LookupService, donde LookupService define el comportamiento del ejemplo de descubrimiento más básico, mientras que Leasemanager define el procesamiento de las operaciones de registro, renovación y cancelación del cliente. Entonces, en este artículo, centrémonos en la implementación de las interfaces relacionadas de LeasManager. Mirando hacia atrás, estamos mirando PeeraWareinstanceregistry. De hecho, esta clase se utiliza para copiar información relevante en múltiples nodos. Por ejemplo, si un nodo se registra para la renovación y fuera de línea, entonces la copia relevante (notificación) se copiará a cada nodo a través de esta clase. Veamos cómo maneja el registro del cliente:
/** * registra la información sobre el {@link instanceInfo} y las réplicas * esta información a todos los nodos de Eureka de pares. Si este es evento de replicación * de otros nodos de réplica, entonces no se replica. * * @param info * el {@link instanceInfo} a registrar y replicar. * @param isReplication * Verdadero Si este es un evento de replicación de otros nodos de réplica, * falso de lo contrario. */ @Override public void Register (Información final de InstanceInfo, ISREPRICACIÓN BOOLEA FINAL) {int leameduration = lease.default_duration_in_secs; if (info.getLeaseInfo ()! = null && info.getLeaseInfo (). getDurationInsecs ()> 0) {leeduration = info.getLeaseInfo (). getDuringInsecs (); } super.register (información, legrado, isreplicación); ReplicateTopeers (Action.Register, info.getAppName (), info.getId (), info, null, isReplication); }Podemos ver que llama al método de registro de la clase principal y luego replica el comportamiento correspondiente a otros nodos a través de REPLICATETOPERS. La replicación específica no se discutirá aquí. Centrémonos en el método de registro. Encontramos el método Registro () en la clase principal:
/*** registra una nueva instancia con una duración dada. * * @see com.netflix.eureka.lease.leasManager#registro (java.lang.object, int, boolean) */ public void register (InstanceInfo Registration, int Lasturation, boolean IsRplication) {try {read.lock (); Map <string, arrendamiento <instanceInfo>> gmap = registry.get (registrant.getAppName ()); Registrar.Increment (isReplication); if (gmap == null) {final concurrenthashmap <string, arrendamiento <instanceInfo>> gNewMap = new concurrenthashmap <string, arrendamiento <instanceInfo>> (); GMAP = Registry.putifabsent (Registrant.getAppName (), GNewMap); if (gmap == null) {gmap = gnewmap; }} Arrendamiento <instanceInfo> existenteLease = gmap.get (Registrant.getID ()); // Conserve la última marca de tiempo sucia sin sobrescribirlo, si ya hay un arrendamiento if (existente! = NULL && (exististLease.getholder ()! = Null)) {Long EXISTLASTDIRTYTIMESTAMP = EXISTLEASE.GETHOLDER (). GetLastDirTytimeStamp (); Long RegistrationLastDirtyTimestamp = registrado.getLastDirtyTimestamp (); logger.debug ("arrendamiento existente encontrado (existente = {}, proporcionado = {}", existenteLastDirtyTimestamp, RegistrationLastDirtytimeStamp); // Esto es un> en lugar de A> = porque si los Copices de Timestamps son iguales, todavía tomamos el remoto transmitido // InstanceInfo en lugar del servidor local. logger.warn ("Hay un arrendamiento existente y la marca de tiempo sucia del arrendamiento existente {} es mayor" + "que el que se está registrando {}", ExistlastDirtyTiMeStamp, RegistrationLastDirtytimeStamp) // El contrato de arrendamiento no existe y, por lo tanto, es un nuevo registro sincronizado (bloqueo) {if (this.ExpectedNumberOfRenewsPermin> 0) {// Dado que el cliente quiere cancelarlo, reducir el umbral // (1 // durante 30 segundos, 2 por un minuto) this. this.numberOfReneWSPERMINTHREST = (int) (this. NULL) {Lease.setserviceUptimestamp (existente.A través del código fuente, ordenemos brevemente el proceso:
1) Primero, obtenga algunas columnas de objetos de instancia de servicio basados en el nombre de la aplicación. Si es nulo, cree un nuevo mapa y agregue la información de aplicación registrada actual a este mapa. Hay un objeto de arrendamiento aquí. Esta clase describe los atributos de tiempo de T genérico, como el tiempo de registro, el tiempo de inicio del servicio, el tiempo de actualización final, etc. Puede prestar atención a su implementación:
/ * * Copyright 2012 Netflix, Inc. * * Licenciado bajo la licencia Apache, versión 2.0 (la "licencia"); * No puede usar este archivo, excepto de conformidad con la licencia. * Puede obtener una copia de la licencia en * * http://www.apache.org/licenses/license-2.0 * * A menos que sea requerido por la ley aplicable o acordado por escrito, el software * distribuido bajo la licencia se distribuye sobre una base "como es", * sin garantías o condiciones de ningún tipo, ya sea expresa o implícita. * Consulte la licencia para los permisos de gobernanza del idioma específicos y * limitaciones bajo la licencia. */paquete com.netflix.eureka.lease; import com.netflix.eureka.registry.abstractinstanceRegistry;/*** Describe una disponibilidad basada en el tiempo de un {@link t}. El propósito es evitar * la acumulación de instancias en {@link AbstractInstanceGistry} como resultado de apagados ingredientes * que no son infrecuentes en los entornos de AWS. * * Si un contrato de arrendamiento transcurre sin renovaciones, eventualmente caducará continuamente * Marcando el {@link t} asociado para el desalojo inmediato: esto es similar a * una cancelación explícita, excepto que no hay comunicación entre el * {@link t} y {@link Leasemanager}. * * @Author Karthik Ranganathan, Greg Kim */Public Class Lease <T> {enum Action {registrar, cancelar, renovar}; public static final int default_duration_in_secs = 90; titular de la T privado; Desalojo privado LongtimeStamp; Long RegistrationTimestamp privado; Service ServicePtimestamp privado; // Haz que sea volátil para que la tarea de vencimiento vea a este Volátil Privado más rápido de LastUpdateTimestamp; Larga duración privada; Arrendamiento público (t r, int DurationInsecs) {Holder = R; RegistrationTimestamp = System.CurrentTimemillis (); lastUpDateTimestamp = RegistrationTimestamp; duración = (duracióninsecs * 1000); } /** * Renovar el arrendamiento, use la duración de la renovación si fue especificado por * Associated {@link t} durante el registro, de lo contrario, la duración predeterminada es * {@link #default_duration_in_secs}. */ public void renovar () {lastupdateTimestamp = system.currentTimemillis () + duración; } /*** Cancela el arrendamiento actualizando el tiempo de desalojo. */ public void Cancel () {if (EvictionTimestamp <= 0) {EvictionTimestamp = System.CurrentTimemillis (); }} /*** Marque el servicio como UP. Esto solo tendrá en cuenta la primera vez llamadas, * se ignorarán las llamadas posteriores. */ public void ServiceUp () {if (ServiceUptImestamp == 0) {ServiceUptImestamp = System.CurrentTimemillis (); }} /*** Establezca el servicio de hojas Up TimeStamp. */ public void setServiceUptimestamp (Long ServiceUptimestamp) {this.ServiceUptimestamp = ServiceUptimestamp; } /*** verifica si el arrendamiento de un {@link com.netflix.appinfo.instanceInfo} ha expirado o no. */ public boolean isExpired () {return isEppired (0l); } /*** verifica si el arrendamiento de un {@link com.netflix.appinfo.instanceInfo} ha expirado o no. * * Tenga en cuenta que debido a renovar () haciendo la cosa 'incorrecta "y establecer lastupdateTiMestamp a +duración más que * lo que debería ser, el vencimiento será en realidad 2 * duración. Este es un error menor y solo debe afectar las instancias que no se apagan. boolean isExptied (Long AddreDAlleAsems) {return (desvictiontimeStamp> 0 || system.currentTimemillis ()> (lastupDateTimestamp + Duration + AddreDALLEASEMS);} / ** * Obtiene los millones de millones desde la época cuando el arrendamiento se registró. * * @@return los modions, ya que la Epoch cuando se registró el arrendamiento. * {RECRISIÓN DEL REGISTROMETO; Millions segundos desde que se desanimó el arrendamiento. Long getServiceUptimestamp () {return ServiceUptimestamp;2) Según la identificación registrada actualmente, si puede obtenerla en el mapa, haga lo siguiente:
2.1) De acuerdo con el tiempo de contacto del nodo actualmente existente y el tiempo de contacto del nodo registrado, si el tiempo anterior es posterior al último tiempo, la instancia actualmente registrada estará sujeta a la instancia existente.
2.2) De lo contrario, actualice su número de renovación esperado por minuto y su umbral
3) Guarde el nodo de registro actual en el mapa, y nuestro proceso de registro ha llegado a su fin básicamente
2. Cliente de Eureka
Cuando se inicializa el servidor ServletContext, se creará un descubrimiento. Los amigos que están familiarizados con Eureka deben estar familiarizados con estos dos atributos: Fetchregistry y Registrowitheureka. Cuando se ejecuta en el modo Independiente Eureka Integrado en SpringCloud, si estos dos valores no son falsos, entonces el inicio informará un error. ¿Por qué informa un error? De hecho, la respuesta radica en el constructor de DiscoveryClient:
@Inject DiscoveryClient (ApplicationInfomanager ApplicationInfomanager, eurekaclientConfig config, abstractDiscoveryClientOptionAlArgs args, Provider <StaupRegistry> BackupRegyRPrudeProvider) {//....omit Parte del código if (! Config. Debe lo que debería obtenerwitheureka () & & config. ni consulta para los datos "); Scheduler = nulo; HeartBeateExecutor = null; cacherefreshexecutor = null; eurekatransport = nulo; InstanceRegionChecker = new InstanceRegionChecker (nuevo PropertyBasedAzTorregionMapper (config), clientCig.getRegion ()); // Este es un poco de hack para permitir el código existente usando DiscoveryManager.getInstance () // para trabajar con Di'd DiscoveryClient DiscoveryManager.getInstance (). SetDiscoveryClient (esto); DiscoveryManager.getInstance (). SeteurekaclientConfig (config); initTimestampms = System.CurrentTimemillis (); logger.info ("Cliente de descubrimiento inicializado en TimeStamp {} con el recuento de instancias iniciales: {}", inittimestampms, this.getApplications (). size ()); devolver; // no es necesario configurar una tareas de red y hemos terminado} try {// tamaño predeterminado de 2 - 1 cada uno para Heartbeat y Cacherefresh Scheduler = Ejecutores.newscheduledThreadPool (2, New ThreadFactoryerilder () .SetNeMeFormat ("DiscoverLient-%DiMe DaMonDaMon (verdadero) .Build ()); HeartBeateExeCutor = new ThreadPoolExeCutor (1, ClientConfig.GetheartBeateExeCuTorthreadPoolSize (), 0, TimeUnit.Seconds, New SynChronqueuee <Runnable> (), New ThreadFactoryBuilder (). // Use el punto de salida directo CacherefreshexeCutor = new ThreadPoolexeCutor (1, ClientConfig.getCacherFreesHexeCuTorthReadPoolSize (), 0, TimeUnit.Seconds, New SynChroNousqueue <Runnable> (), New ThreadRoryBuilder (). .setdaemon (true) .Build ()); // use la transferencia directa eurekatransport = new eurekatransport (); ScheduleserverEndpointTask (Eurekatransport, Args); // zóbulo de algunos código InitsCheduledTasks (); // ....}Según el código fuente, podemos sacar las siguientes conclusiones:
1) Si ambos deberían registrarse con su frase y deben ser falsos, entonces regrese directamente.
2) Crear un grupo de hilos que envíe latidos y refrescos cachés
3) Inicializar las tareas cronometradas creadas
Luego, echemos un vistazo al siguiente código en el método inSCheduledTasks ():
// Heartbeat Timer Scheduler.
Aquí hay un hilo que desencadena una ejecución cronometrada, en segundos, y ejecuta un latido de envío de acuerdo con el valor de renovación IntervalInSecs. El hilo HeartBeatThread se ejecuta de la siguiente manera:
/*** La tarea del corazón que renueva el arrendamiento en los intervalos dados. */ Private Class HeartBeatThread implementos Runnable {public void run () {if (renovar ()) {dastSuccessfulHeartBeatTimestamp = System.CurrentTimemillis (); }}}Podemos ver que el método de ejecución es muy simple de ejecutar el método de renovación, y si el tiempo se registra con éxito. Método de renovación:
/ ** * Renovar con el servicio eureka haciendo la llamada de descanso apropiada */ boolean renovar () {eurekahttpresponse <instanceInfo> httpresponse; Pruebe {httpResponse = eurekatransport.registrationClient.sendHeartBeat (instanceInfo.getAppName (), instanceInfo.getId (), instanceInfo, null); logger.debug ("{} - Heartbeat Status: {}", prefijo + appathidentifier, httpesponse.getStatUscode ()); if (httpResponse.getStatUscode () == 404) {Reregister_counter.Increment (); logger.info ("{} - re -registrando aplicaciones/{}", prefijo + appathidentifier, instanceInfo.getAppName ()); Long Timestamp = instanceInfo.SetIsDirtyWithTime (); éxito booleano = registro (); if (éxito) {instanceInfo.unsetisDirty (timestamp); } return éxito; } return httpResponse.getStatUscode () == 200; } catch (Throwable e) {logger.error ("{} - ¡No pudo enviar latidos del corazón!", Prefijo + appPathIdentifier, e); devolver falso; }}Si el latido se envía aquí, si la devolución es 404, se realizará la operación de registro. Tenga en cuenta que, según el valor de retorno, httpResponse, podemos concluir que todas estas operaciones se basan en solicitudes HTTP. ¿Eso es cierto? Sigamos observando el método de registro:
/*** Regístrese con el servicio Eureka haciendo la llamada de descanso adecuada. */ Boolean Register () lanza Throwable {logger.info (prefijo + appPathIdentifier + ": registro de servicio ..."); Eurekahttpresponse <Void> httpResponse; intente {httpResponse = eurekatransport.registrationClient.register (instanceInfo); } catch (Exception e) {logger.warn ("{} - fallado en el registro {}", prefijo + appPathIdentifier, e.getMessage (), e); tirar E; } if (logger.isinfoEnabled ()) {logger.info ("{} - estado de registro: {}", prefix + appPathidentifier, httpesponse.getStatUscode ()); } return httpResponse.getStatUscode () == 204; }Aquí, se llama el método RegistrationClient en Eurekatransport:
Clase final estática privada Eurekatransport {private ClosablerEsolver bootstraPresolver; TransportClientFactory privado TransportClientFactory; privado eurekahttpclient RegistrationClient; privado eurekahttpClientFactory RegistrationClientFactory; Privado eurekahttpclient QueryClient; privado eurekahttpclientFactory QueryClientFactory; void shutdown () {if (RegistrationClientFactory! = NULL) {RegistrationClientFactory.shutdown (); } if (QueryClientFactory! = NULL) {QueryClientFactory.shutdown (); } if (RegistrationClient! = NULL) {RegistrationClient.shutdown (); } if (QueryClient! = NULL) {QueryClient.shutdown (); } if (transportClientFactory! = null) {transportClientFactory.shutdown (); }}}Aquí podemos ver que el cliente de Eureka usa la solicitud HTTP para registrar el servicio, lo que significa que cuando creamos DiscoveryClient, registraremos la instancia en el servidor.
3. Servicio de descanso proporcionado por el servidor
Ya hemos visto el código proporcionado por el servidor para manejar las solicitudes de registro del cliente. Dado que el cliente se registra a través del protocolo HTTP, el servidor debe tener una dirección para manejar esta solicitud HTTP. De hecho, el servidor Eureka utiliza el estándar JAX-RS para proporcionar un método REST para exponer el servicio. Podemos echar un vistazo al método AddInstance de este AplicationResource:
/** * registra información sobre una instancia particular para un * {@link com.netflix.discovery.shared.application}. * * @param info * {@link instanceInfo} Información de la instancia. * @param isReplication * Un parámetro de encabezado que contiene información si esto se replica desde otros nodos. */@Post @consumes ({"Application/Json", "Aplicación/XML"}) Respuesta pública AddInStance (InstanceInfo Info, @headerParam (PEEREURKANODE.HEADER_REPlication) String isReplication) {logger.debug ("registrar instancia} (replication =})", info.getid (), iseReReSeReR. // valida que el InstanceInfo contiene todos los campos necesarios necesarios si (isBlank (info.getId ())) {return respuesta.status (400) .Entity ("faltando instancia"). build (); } else if (isBlank (info.gethostName ())) {return response.status (400) .Entity ("faltando nombre de host"). build (); } else if (isBlank (info.getIpAddr ())) {return respuesta.status (400) .Entity ("Dirección IP faltante"). Build (); } else if (isBlank (info.getAppname ()))) {return respuesta.status (400) .Entity ("faltando appname"). build (); } else if (! AppName.equals (info.getAppName ()))) {return response.status (400) .Entity ("no coincidió AppName, esperando" + AppName + "pero fue" + info.getAppName ()). build (); } else if (info.getDataCenterInfo () == null) {return respuesta.status (400) .Entity ("faltando dataCenterInfo"). Build (); } else if (info.getDatAcenterInfo (). getName () == null) {return respuesta.status (400) .Entity ("faltando datacenterInfo"). build (); } else if (info.getDatAcenterInfo (). getName () == null) {return respuesta.status (400) .Entity ("Nombre de datos de datos faltante"). Build (); } // manejar casos en los que los clientes puedan registrarse con Bad DatacEnterInfo con datos faltantes de datos DatacenterInfo = info.getDataCenterInfo (); if (dataCenterInfo instanceo de uniqueidentifier) {String dataCenterInfoid = ((uniqueIdentifier) dataCenterInfo) .getId (); if (isBlank (dataCenterInfoid)) {boolean experimental = "true" .equalSignorecase (serverConfig.getExperimental ("Registration.Validation.DatAcenterInfoid")); if (experimental) {string entity = "dataCenterInfo of type" + datacenterInfo.getClass () + "debe contener una ID válida"; devolución de respuesta.status (400) .Entity (entidad) .Build (); } else if (dataCenterInfo instanceo de AmazonInfo) {AmazonInfo AmazonInfo = (AmazonInfo) DatacenterInfo; String EffectionId = AmazonInfo.get (AmazonInfo.Metadatakey.instanceId); if (efectivo == null) {amazonInfo.getMetMeData (). Put (AmazonInfo.Metadatakey.instanceid.getName (), info.getID ()); }} else {logger.warn ("registrar datacenterInfo of type {} sin una ID apropiada", dataCenterInfo.getClass ()); }}}} Registry.Register (info, "verdadero" .equals (isReplication)); devuelve respuesta.status (204) .build (); // 204 para ser compatible hacia atrás}Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.