O Eureka é um aplicativo de governança de serviço descentralizado, e seu recurso distinto é que ele pode ser registrado com o endereço que você configurou como um servidor e um serviço. Então, neste artigo, vamos discutir o processo de registro de Eureka.
1. Servidor de Eureka
A classe principal do lado do servidor de Eureka é o EurekABootStrap, que implementa um ouvinte do ServletContextListener. Portanto, podemos concluir que o Eureka é implementado com base em contêineres de servlet. O código -chave é o seguinte:
classe pública Eurekabootstrap implementa o servletContextListener {//...omit código relacionado/*** inicializa Eureka, incluindo a sincronização com outros pares Eureka e publicando o registro. * * @See * javax.servlet.servletContextListener#contextinitialized (javax.servlet.servletContextevent) */ @Override public void contextinitialized (evento servletContextevent) {try {initerekekaenVonment (); initerekaserverContext (); ServletContext sc = event.getServletContext (); sc.setAttribute (EurekaserverContext.class.getName (), ServerContext); } catch (throwable e) {Logger.error ("Não é possível bootstrap eureka server:", e); lançar a nova RunTimeException ("Não é possível inicializar o servidor Eureka:", e); }} // omita código relacionado ......}Podemos ver que, quando a inicialização do servletContext for concluída, o ambiente Eureka será inicializado e o EurekaserverContext será inicializado. Então estamos dando uma olhada no método initerekaserverContext:
/*** Init gancho para o contexto do servidor. Substitua a lógica personalizada. */ Void protegido initerekaserverContext () lança exceção {// ...... ApplicationInfomanager ApplicationInfomanager = null; if (eurekaclient == null) {eurekainstanceconfig instanceconfig = isCloud (ConfigurationManager.getDeploymentContext ())? new CloudInstanceconfig (): new MyDatacenterInstanceconfig (); ApplicationInfomanager = new ApplicationInfomanager (Instanceconfig, New EurekaconfigBasedInstanceInFoprovider (Instanceconfig) .get ()); EurekaclientConfig EurekaclientConfig = new DefaultEureKaclientConfig (); Eurekaclient = new Discoveryclient (ApplicationInfomanager, EurekaclientConfig); } else {ApplicationInfomanager = Eurekaclient.getApplicationInfomanager (); } PeeraWareInstanceregistry Registry; if (iSaws (ApplicationInfomanager.getInfo ())) {Registry = new AwsInstanceregistry (EurekaserverConfig, Eurekaclient.GeteKaclientConfig (), ServerCodecs, Eurekaclient); awsbinder = novo awsbinderDelegate (eurekaserverconfig, eurekaclient.geteurekaclientConfig (), registro, applicationInfomanager); awsbinder.start (); } else {Registry = new PeeraWareInstanceregistryImpl (EurekaserverConfig, Eurekaclient.geteureKaclientConfig (), ServerCodecs, Eurekaclient); } //...Omit parte do código}Neste método, muitos objetos relacionados ao serviço Eureka serão criados. Aqui, listo dois objetos principais, como Eurekaclient e PeeraWareInstanceregistry. Falaremos sobre a parte do cliente mais tarde. Vamos dar uma olhada no que o PeeraWareInstanceregistry é usado. Aqui escrevo um diagrama de classe sobre esta aula:
De acordo com o diagrama de classes, podemos descobrir claramente que a interface de nível superior do PeeraWareInstanceregistry é Leasemanager e LookupService, onde o LookupService define o comportamento do exemplo de descoberta mais básico, enquanto o Leasemanager define o processamento de registro de clientes, renovação e cópia. Portanto, neste artigo, vamos nos concentrar na implementação das interfaces relacionadas do Leasemanager. Olhando para trás, estamos olhando para o PeeraWareInstanceregistry. De fato, esta classe é usada para copiar informações relevantes em vários nós. Por exemplo, se um nó se registrar para renovação e offline, a cópia relevante (notificação) será copiada para cada nó através desta classe. Vamos ver como isso lida com o registro do cliente:
/** * Registra as informações sobre o {@link instantaInfo} e replicas * essas informações para todos os nós do Eureka. Se este for um evento de replicação * de outros nós de réplica, ele não será replicado. * * @param info * o {@link instantaInfo} a ser registrado e replicado. * @param isReplication * true se este for um evento de replicação de outros nós de réplica, * false caso contrário. */ @Override Public void Register (Informações finais da instância final, IsReplication final Boolean) {int LENTAÇÃO = LEASE.DEFAULL_DURATUR_IN_SECS; if (info.getLeaseInfo ()! = null && info.getLeaseInfo (). getDurationInsecs ()> 0) {LASEASTION = info.getLeaseInfo (). getDurationInsecs (); } Super.register (info, locação, isreplicação); replicateTopeers (action.register, info.getAppName (), info.getId (), informações, nulo, isreplication); }Podemos ver que ele chama o método do registro da classe pai e, em seguida, replica o comportamento correspondente a outros nós através do Replicatetopeers. A replicação específica não será discutida aqui. Vamos nos concentrar no método de registro. Encontramos o método Register () na classe pai:
/*** Registra uma nova instância com uma determinada duração. * * @see com.netflix.eureka.lease.leasemanager#registro (java.lang.object, int, boolean) */ public void Register (registro de instanceInfo, intragem intreplication boolean) {try {read.lock (); Mapa <string, lease <instânciainfo>> gmap = Registry.get (Registrant.getAppName ()); Register.Increment (IsReplication); if (gmap == null) {final ConcurrentHashMap <string, lease <temlowInfo>> gnewmap = new concursonthashmap <string, lesse <tremotingInfo>> (); gmap = Registry.putifabsent (registrante.getAppName (), gnewmap); if (gmap == null) {gmap = gnewmap; }} Lease <StaneInfo> existingLease = gmap.get (registrante.getId ()); // Mantenha o último registro de data e hora sujo sem substituí -lo, se já houver um arrendamento se (existinglease! = Null && (existingLease.getholder ()! = Null)) {existingLastDirtyTiTiMestamp = existingLease.GETHOLDER (). Long RegistrationLastDirtyTimestamp = Register.getLastDirtyTimestamp (); Logger.debug ("Lease existente encontrado (existente = {}, fornecido = {}", existingLastDirtyTimestamp, RegistrationLastDirtyTimestamp); // Este é a> em vez de a> = porque os timestamps são iguais, ainda assumimos o transmissão // em vez do servidor. RegistrationLastDirtyTimestamp) {Logger.warn ("Existe um arrendamento existente e o timestamp sujo do arrendamento existente {} é maior" + "do que o que está sendo registrado {}", existtdirtyTimestamp, o registrationLastdiriTimestamp); registro = existlease.getholder (); isto.extedNumberOfrenewsPermin + 2; Lease <Extementedinfo> (Registro);Através do código -fonte, vamos resolver brevemente o processo:
1) Primeiro, obtenha algumas colunas de objetos de instância de serviço com base no nome da AppName. Se for nulo, crie um novo mapa e adicione as informações atuais do aplicativo registrado a este mapa. Há um objeto de arrendamento aqui. Esta classe descreve os atributos de tempo do T genérico, como tempo de registro, horário de inicialização do serviço, tempo de atualização final etc. Você pode prestar atenção à sua implementação:
/ * * Copyright 2012 Netflix, Inc. * * licenciado sob a licença Apache, versão 2.0 (a "licença"); * Você não pode usar esse arquivo, exceto em conformidade com a licença. * Você pode obter uma cópia da licença em * * http://www.apache.org/license/license-2.0 * *, a menos que exigido pela lei aplicável ou acordada por escrito, o software * distribuído sob a licença seja distribuído em uma base "como é" *, sem garantia ou condição de qualquer tipo, seja expresso ou implícito. * Consulte a licença para as permissões de governança de idiomas específicas e * limitações sob a licença. */package com.netflix.eureka.lease; importar com.netflix.eureka.registry.abstractInstanceregistry;/*** descreve uma disponibilidade baseada no tempo de um {@link t}. O objetivo é evitar * acumulação de instâncias em {@link abstractInstanceregistry} como resultado de desligamentos não graciosos * que não são incomuns em ambientes da AWS. * * Se um arrendamento passar sem renovações, ele acabará por expirar continuamente * marcando o {@link t} para despejo imediato - isso é semelhante a * um cancelamento explícito, exceto que não há comunicação entre o * {@link t} e {@link leasemanager}. * * @Author Karthik Ranganathan, Greg Kim */Public Class Lease <T> {Enum Action {Register, Cancelar, renovar}; public static final int default_duration_in_secs = 90; Titular T Privado; Private Long DevoTimestamp; Private Long RegistrationTimestamp; private Long ServiceUptimestamp; // Torne -o volátil para que a tarefa de expiração visse essa mais rápida private volátil LastUpDatetimestamp; Duração de longa duração privada; arrendamento público (t r, int durationInsecs) {holder = r; RegistrationTimestamp = System.currenttimemillis (); lastUpDateTimestamp = RegistrationTimestamp; duração = (DurationInsecs * 1000); } /** * Renove o arrendamento, use a duração da renovação se for especificada pelo * associado {@link t} durante o registro; caso contrário, a duração padrão é * {@link #default_duration_in_secs}. */ public void renew () {lastUpDateTimestamp = System.currenttimemillis () + duração; } /*** cancela o arrendamento atualizando o tempo de despejo. */ public void cancel () {if (despettionTimestamp <= 0) {despettionTimestamp = System.currenttimEmillis (); }} /*** Marque o serviço como up. Isso só afetará a primeira vez que as chamadas subsequentes serão ignoradas. */ public void ServiceUp () {if (ServiceUtImestamp == 0) {ServiceUptimestamp = System.CurrentTimEmillis (); }} /*** Defina as folhas de serviço no timestamp. */ public void SetServiceUptimestamp (Long ServiceUtimestamp) {this.ServiceUptimestamp = ServiceUtImestamp; } /*** Verifica se o arrendamento de um dado {@link com.netflix.appinfo.instanceInfo} expirou ou não. */ public boolean isexpired () {return isexpired (0l); } /*** Verifica se o arrendamento de um dado {@link com.netflix.appinfo.instanceInfo} expirou ou não. * * Observe que, devido ao renew () fazer a coisa 'errada "e definir mais o que deve ser, o expiração será realmente 2 * duração. Este é um inseto menor e deve afetar apenas * instâncias que não são interrompidas. O booleano é expirado (adição de adição de longa data) {return (despettionTimestamp> 0 || System.CurrentTimEmillis ()> (LastUpDateTimestamp + Duração + AddicialEASEMS);} / ** * Publica os MillionseConds desde que o LEASE foi registrado. getRegistringtimestamp () {Retorno RegistroTimeTamp; LastUpdatetim, o MillionSends, desde que o arrendamento foi despejado. Epoch Quando o serviço para o arrendamento foi marcado como UP.2) De acordo com o ID atualmente registrado, se você puder obtê -lo no mapa, faça o seguinte:
2.1) De acordo com o tempo de toque do nó atualmente existente e o tempo de toque do nó registrado, se o horário anterior for posterior à última vez, a instância atualmente registrada estará sujeita à instância existente.
2.2) Caso contrário, atualize seu número de renovação esperado por minuto e seu limite
3) Salvar o nó de registro atual no mapa, e nosso processo de registro chegou basicamente ao fim
2. Cliente Eureka
Quando o servidor servletContext for inicializado, um Discoveryclient será criado. Amigos familiarizados com Eureka devem estar familiarizados com esses dois atributos: Fetchregistry e Registerwitheureka. Ao ser executado no modo Integrado Eureka Independent no SpringCloud, se esses dois valores não forem falsos, a inicialização relatará um erro. Por que ele relata um erro? De fato, a resposta está no construtor do Discoveryclient:
@Inject Discoveryclient (ApplicationInfomanager ApplicationInfomanager, EurekaclientConfig Config, AbstractDiscoveClientarOptionArgs args, provedor <Comupregistry> backupRegistryProVer) {//PertTert do código se (! Config. para não se registrar nem consultar dados. "); agendador = nulo; HeartBeatExecutor = NULL; Cacherefreshexecutor = null; eurekatransport = null; InstanceReGinchecker = new InstanceregioChecker (new PropertyBasedAzTeGionMapper (config), clientconfig.getregion ()); // Isso é um pouco de hack para permitir o código existente usando o DiscoveryManager.getInstance () // para trabalhar com o Discoveryclient DiscoveryManager.getInstance (). SetDiscoveryclient (this); DiscoveryManager.getInstance (). SeteureKaclientConfig (config); inittImestampms = System.currenttimemillis (); Logger.info ("Cliente Discovery inicializado no Timestamp {} com as instâncias iniciais contagem: {}", inittImestampms, this.getApplications (). size ()); retornar; // Não é necessário configurar tarefas de rede e terminamos} tente {// tamanho padrão de 2 - 1 cada para parto e cacherefresh scheduler = executores.newscheduledThreadpool (2, new ThreadFactoryBuilder () .SetNameFormat ("Discoveryclient -%"). HeartBeatExector = new ThreadPoolExecutor (1, clientconfig.getheartBeatExecutorthReadPoolSize (), 0, timeUnit.Seconds, new SynchronsoSqueue <drunnable> (), new ThreadFactoryBuilder () .setNameFormat ("Discoverycient>. // use hands hands cacherefreshexecutor = new threadpoolExecutor (1, clientconfig.getcacherefreshexecutorthReadPoolSize (), 0, timeUnit.Seconds, novo syncrosQueue <drunnable> (), new threadBuilder (). .construir() ); // Use a transferência direta eurekatransport = new eurekatransport (); ScheduLeservendPointTask (Eurekatransport, args); // ...Mit algum código initScheduledTasks (); // ....}Com base no código -fonte, podemos tirar as seguintes conclusões:
1) Se ambos devem registrar o registro e o FetchGististry forem falsos, retorne diretamente diretamente.
2) Crie um pool de tópicos que envie batimentos cardíacos e refresca os caches
3) Inicialize as tarefas cronometradas criadas
Então vamos dar uma olhada no código a seguir no método initScheduledTasks ():
// Scheduler Timer Scheduler.Schedule (novo TimedSupervisortask ("Heartbeat", Scheduler, HeartBeatExecutor, RENEWALInterValinsecs, TimeUnit.SeConds, ExpbackOffBound, New HeartbeatThread ()), renewalintervalinsecS, timeUnit.SecOnds);Aqui está um encadeamento que desencadeia uma execução cronometrada, em segundos, e executa um batimento cardíaco de envio de acordo com o valor de renowalintervalinsecs. O thread do HeartBeatThread é executado da seguinte maneira:
/*** A tarefa de batimentos cardíacos que renova o contrato de arrendamento nos intervalos dados. */ classe privada HeartBeatThread implementa runnable {public void run () {if (renew ()) {lastSuccessfulHeartBeatTimestamp = System.currenttimEmillis (); }}}Podemos ver que o método de execução é muito simples de executar o método de renovação e se o tempo for registrado com sucesso. Método de renovação:
/ ** * Renove com o serviço Eureka, fazendo a chamada de repouso apropriada */ boolean renow () {eurekahttpResponse <costaseInfo> httpResponse; tente {httpResponse = eurekatransport.registrationclient.sendheartbeat (instanceInfo.getAppName (), instanceInfo.getId (), instanceInfo, null); Logger.debug ("{} - status do coração: {}", prefixo + appPathIdentifier, httproPense.getStatuscode ()); if (httpResponse.getStatuscode () == 404) {reregister_counter.incrent (); Logger.info ("{} - registrando novamente os aplicativos/{}", prefixo + apppathIdentifier, instanceInfo.getAppName ()); Timestamp longo = instanceInfo.setIsDirtyWithTime (); sucesso booleano = registro (); if (success) {instanceInfo.UnSetIsDirty (Timestamp); } retornar sucesso; } return httpResponse.getStatuscode () == 200; } catch (throwable e) {Logger.error ("{} - não conseguiu enviar batimentos cardíacos!", prefixo + appPathIdentifier, e); retornar falso; }}Se o batimento cardíaco for enviado aqui, se o retorno for 404, a operação de registro será executada. Observe que, com base no valor de retorno HTTPRESPONS, podemos concluir que todas essas operações são baseadas em solicitações HTTP. Isso é verdade? Vamos continuar analisando o método do registro:
/*** Registre -se no serviço Eureka, fazendo a chamada de repouso apropriada. */ boolean Register () lança o Throwable {Logger.info (prefixo + appPathIdentifier + ": Serviço de registro ..."); EurekahttpResponse <Void> httpResponse; tente {httpResponse = eurekatransport.registrationClient.register (instanceInfo); } Catch (Exceção e) {Logger.warn ("{} - Registro falhou {}", prefixo + appPathIdentifier, e.getMessage (), e); jogar e; } if (logger.isinfoEnabled ()) {Logger.info ("{} - status de registro: {}", prefixo + apppathIdentifier, httproPSOnse.getStatuscode ()); } retornar httpResponse.getStatuscode () == 204; }Aqui, o método de registro em Eurekatransport é chamado:
Classe final estática privada Eurekatransport {private ClosableResolver bootstrapresolver; TransportclientFFactory de transporte privado Faciente client; private eurekahttpclient RegistrationClient; private EurekahttpClientFactory RegistrationClientFactory; Privado EurekahttpClient Queryclient; Eurekahttpclientffactory de eurekahttpclientfFactory; 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 (); }}}Aqui podemos ver que o cliente da Eureka usa a solicitação HTTP para registrar o serviço, o que significa que, quando criarmos o Discoveryclient, registraremos a instância no servidor.
3. Serviço de descanso fornecido pelo servidor
Já vimos o código fornecido pelo servidor para lidar com solicitações de registro do cliente. Como o cliente se registra através do protocolo HTTP, o servidor deve ter um endereço para lidar com essa solicitação HTTP. De fato, o servidor Eureka usa o padrão Jax-RS para fornecer o método REST para expor o serviço. Podemos dar uma olhada no método Addinstance deste ApplicationResource:
/** * Registra informações sobre uma instância específica para um * {@link com.netflix.discovery.shared.application}. * * @param info * {@link instantaInfo} informações da instância. * @param isReplication * Um parâmetro de cabeçalho que contém informações, seja * replicado de outros nós. */@Post @Consumes ({"Application/json", "Application/Xml"}) AddInstance de resposta pública (Informações da InstanceInfo, @HeaderParam (peereurekanode.header_replication) string); // valida que o instanceInfo contém todos os campos necessários se (isblank (info.getId ())) {return Response.status (400) .Entity ("Missing Instância"). build (); } else if (isblank (info.gethostName ())) {return Response.status (400) .Entity ("Missing hostName"). Build (); } else if (isblank (info.getipaddr ())) {return Response.status (400) .Entity ("Endereço IP ausente"). build (); } else if (isblank (info.getAppName ()))) {return Response.status (400) .Entity ("ausente appName"). build (); } else if (! AppName.equals (info.getAppName ()))) {retorna resposta.status (400) .Entity ("AppName incompatível, esperando" + appName + ", mas foi" + info.getAppName ()). build (); } else if (info.getdatacenterinfo () == null) {retorna resposta.status (400) .Entity ("ausente datacenterinfo"). build (); } else if (info.getDatacenterinfo (). getName () == null) {return Response.status (400) .Entity ("ausente datacenterinfo"). build (); } else if (info.getDatacenterinfo (). getName () == null) {return Response.status (400) .Entity ("ausente do nome do datacenterinfo"). build (); } // Lidar com os casos em que os clientes podem estar se registrando com datacenterinfo ruim com dados de dados de dados ausentes datacenterinfo = info.getDatacenterinfo (); if (datacenterinfo instanceof ukingIdentifier) {string datacenterinfoid = ((uncelIdentifier) datacenterinfo) .getId (); if (isblank (datacenterinfoid)) {boolean experimental = "true" .equalsignorecase (serverconfig.getexPerimental ("Registration.validation.datacenterinfoid"); if (experimental) {string entity = "datacenterinfo do tipo" + datacenterinfo.getclass () + "deve conter um id válido"; RETORNO RESPOSTO.STATUS (400) .ENTITY (entidade) .Build (); } else if (datacenterinfo instância de amazoninfo) {AmazonInfo AmazonInfo = (AmazonInfo) datacenterinfo; String effetyId = AmazonInfo.get (AmazonInfo.metadatakey.instanceId); if (eficaz == null) {amazoninfo.getMetadata (). put (amazoninfo.metadatakey.instanceid.getName (), info.getId ()); }} else {Logger.warn ("Registrando datacenterinfo do tipo {} sem um ID apropriado", datacenterinfo.getClass ()); }}}} Registry.register (info, "true" .equals (isreplication)); resposta de retorno.status (204) .build (); // 204 para ser compatível com versões anteriores}O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.