Cena
A operação de publicação de microsserviços é geralmente digitar um novo pacote de código, matar, ele solta o aplicativo em execução, substitua o novo pacote e inicie -o.
O Eureka é usado como centro de registro em Spring Cloud, que permite atraso nos dados da lista de serviços, ou seja, mesmo que o aplicativo não esteja mais na lista de serviços, o cliente ainda solicitará esse endereço por um período de tempo. Em seguida, a solicitação aparecerá, resultando em falha.
Otimizaremos o tempo de atualização da lista de serviços para melhorar a pontualidade das informações da lista de serviços. Mas não importa o quê, é impossível evitar um período de tempo em que os dados sejam inconsistentes.
Então, uma maneira de pensar é tentar novamente o mecanismo. Quando uma máquina A é reiniciada, B no mesmo cluster pode fornecer serviços normalmente. Se houver um mecanismo de tentativa, você poderá tentar novamente B no cenário acima sem afetar a resposta correta.
operar
As seguintes operações são necessárias:
Fita: ReadTimeout: 10000 ConnectTimeout: 10000 MaxaUtoretries: 0 MaxaUtorriesNextServer: 1 OkTetryOnalOperations: False
Introdução do pacote de retração de primavera
<Depencency> <PuerpId> org.springframework.retry </frupid> <ArtifactId> Spring-retristion
Tomando Zuul como exemplo, você também precisa configurar e ativar a tentativa:
zuul.retryable = true
Encontrei um problema
No entanto, tudo não era tão suave. O mecanismo de tentativa de teste entrou em vigor, mas não solicitou outra máquina saudável como eu imaginava. Por isso, fui forçado a ir ao código de código aberto e finalmente descobri que era um bug do código -fonte, mas ele foi corrigido e a versão foi atualizada.
Análise de código
A versão usada é
Spring-Cloud-Netflix-Core: 1.3.6.Release
Recurência da mola: 1.2.1.Release
Versão de dependência da nuvem da primavera:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>Como a tentativa está ativada, o método RETRYABLERIBBONLOADBALAINCINGHTTPCLIENT.EXECUTE é executado ao solicitar o aplicativo:
public ribbonapachehttpResponse Execute (solicitação final RibBonapacheHttPRequest, final do iClientConfig Configuroverride) lança exceção {Final RequestConfig.Builder Builder = requestConfig.custom (); IClientConfig config = configoverride! = Null? configoverride: this.config; builder.setConnectTimeout (config.get (CommonClientConfigKey.ConnectTimeout, this.ConnectTimeout)); builder.setsockettimeout (config.get (CommonClientConfigKey.readTimeout, this.readTimeout)); builder.setRredirectSEnabled (config.get (CommonClientConfigKey.FollowDirects, this.followredirects)); final requestConfig requestconfig = builder.build (); Final LoadBalancedRetRypolicy Retrypolicy = loadBalancedRetRyPolicyFactory.create (this.getClientName (), isto); RETRYCALLBACK RETRYCALLBACK = new RETRYCALLBACK () {@Override public ribbonapachehttpSponse dowithretry (contexto RERRYCONTEXT) lança exceção {// em tentativas, a política escolherá o servidor e o definirá no contexto // extrair o servidor e atualizar a solicitação que está sendo feita de ribbonapachehttrest. if (Instância do contextof loadBalancedRetRetContext) {ServiceInstance Service = ((LoadBalancedRetRetContext) context) .getServiceInstance (); if (service! = null) {// Reconstrua a solicitação URI usando o host e a porta definida no contexto da tentativa NewRequest = newRequest.withNewuri (new Uri (Service.geturi (). newRequest.geturi (). getQuery (), newRequest.geturi (). getFragment ())); }} newRequest = getSecureRerequest (solicitação, configuroverride); Httpurirequest httpurirequest = newRequest.toreQuest (requestconfig); Final httpResponse httpResponse = retryableRibbonloadbalancinghttpclient.this.delegate.execute (httpurirequest); if (retrypolicy.retryablestatuscode (httproponse.getStatusline (). getStatuscode ())) {if (fecheHttpSponse.class.isInsInstance (httproPSOnsion)) {((ClosableHttpResponse) htTepSoPSOnse). } lança a nova represelablestatuscodeexception (RETRYABLERIBBONLOADBALAINCINGHTTPCLIENT.THIS.CLIENTNAME, httpResponse.getStatusline (). getStatuscode ()); } Retorne novo RibBonApacheHttProPSOnsion (httpResponse, httpurirequest.geturi ()); }}; Retorne this.executeWithRetry (solicitação, Retropolicia, RETRYCALLBACK); }Descobrimos que primeiro é um novo A RetryCallback e depois o executamos.ExecuteWithRetry (solicitação, Repropolicia, RenTryCallback);
Vemos claramente que o código de RetryCallback.DowithRetry é o código solicitado real, o que significa que este.
Protegido <t, e estende o jogável> t Doexecute (RETRYCALLBACK <t, e> retrycallback, recuperyCallback <T> recuperyCallback, estado de repetição) lança E, exaustaRetryException {retrópolicia retropolicy = this.retrypolicy; BackOffpolicy backOffpolicy = this.backoffpolicy; // Permite que a política de tentativa de novo se inicialize ... REPRYCONTEXT CONTEXT = OPEN (REPRYPOLICY, ESTADO); if (this.logger.istraceEnabled ()) {this.logger.Trace ("RETRYCONTEXT RECUTRIDADO:" + Context); } // Verifique se o contexto está disponível globalmente para clientes que precisam // ele ... RETRYSYNCHRONIZATIONMANAGER.Register (Context); Lançável lastException = null; booleano esgotado = false; tente {// dê aos clientes a chance de aprimorar o contexto ... Boolean Running = DoopenInterceptores (RETRYCALLBACK, CONTEXT); if (! Running) {lança new TerminEnRetryException ("Renasse terminou anormalmente pelo interceptador antes da primeira tentativa"); } // Obtenha ou inicie o contexto de retirada ... BackoffContext backOffContext = null; Objeto recurso = context.getAttribute ("backoffContext"); if (Instância do recurso de backoffContext) {backOffContext = (backOffContext) Resource; } if (backOffContext == null) {backOffContext = backOffpolicy.start (contexto); if (backOffContext! = null) {context.setAttribute ("backOffContext", backoffContext); }} / * * Permitimos que todo o loop seja ignorado se a política ou o contexto já * proibir a primeira tentativa. Isso é usado no caso de tentativa externa para permitir uma * recuperação em Handleretryexhhausted sem o processamento de retorno de chamada (que * lançaria uma exceção). */ while (canRrety (retrypolicy, context) &&! context.isexhaustedonly ()) {try {if (this.logger.isdebugenabled ()) {this.logger.debug ("REMY: count =" + context.getRetryCount ()); } // Redefina a última exceção; portanto, se tivermos sucesso // os interceptores próximos não pensarão que falhamos ... LastException = null; return retrycallback.dowithretry (contexto); } catch (throwable e) {lastException = e; tente {registringwroundable (retypolicy, estado, contexto, e); } catch (Exceção ex) {tiro new terminEnretryException ("não pôde se registrar lançável", ex); } finalmente {DoonerrorInterceptores (RETRYCALLBACK, CONTEXT, E); } if (canRretry (retrypolicy, context) &&! context.isexhaustedonly ()) {try {backoffpolicy.backoff (backOffContext); } catch (backoffinterrupedException ex) {lastException = e; // O retorno foi evitado por outro thread - falhe na tentativa se (this.logger.isdebugenabled ()) {this.logger .debug ("abortar a tentativa porque interrompida: count =" + context.getRetryCount ()); } jogue ex; }} if (this.logger.isdebugenabled ()) {this.logger.debug ("Verificando o RETHROW: count =" + context.getRetRyCount ()); } if (deve -serem (retirpolicia, contexto, estado)) {if (this.logger.isdebugenabled ()) {this.logger.debug ("repensa em repetição da política: count =" + contextRetRetRyCount ()); } arremesso de reprytemplate. <e> wrapifneCessary (e); }} / * * Uma tentativa de estado que pode voltar a repetir a exceção antes agora, * mas se chegarmos tão longe em uma tentativa de novo, há uma razão para isso, * como um disjuntor ou um classificador de reversão. */ if (estado! = null && context.hasattribute (global_state)) {break; }} if (state == null && this.logger.isdebugEnabled ()) {this.logger.debug ("Repetir falhou na última tentativa: count =" + context.getRetryCount ()); } exausto = true; Retorno Handleretryexhausted (recuperyCallback, contexto, estado); } catch (throwable e) {arremesso de reprytemplate. <e> wrapifneCessário (e); } finalmente {feche (retypolicy, contexto, estado, lastException == null || exausta); docloseInterceptores (RETRYCALLBACK, CONTEXT, LASTEXCECTION); RetrysynchronizationManager.clear (); }}Implemente o mecanismo de tentativa em um loop de tempo. Quando ocorre uma exceção quando um RETRYCALLBACK.DOWITHRERTRY (Contexto) é executado, ele capturará uma exceção. Em seguida, use a Repropolicia para determinar se deve tentar novamente. Preste atenção especial ao registro em contorno (Repropolicia, Estado, Contexto, E); método. Não apenas determina se deve tentar novamente, mas, no caso de tentar novamente, uma nova máquina será selecionada e colocada no contexto, e então será trazida quando o REPRYCALLBACK.DOWITHRETRY (Contexto) for executado, para que o trocador seja repetido.
Mas por que minha configuração não mudou o telefone? O código de depuração constatou que o registro conturbável (Repropolicia, Estado, Contexto, E); A máquina selecionada é boa, é uma máquina nova e saudável, mas, ao executar o código RETRYCALLBACK.DOWITHRETRY (Contexto), ainda é solicitado.
Então, vamos dar uma olhada mais de perto no código de RelryCallback.dowithretry (contexto):
Encontramos esta linha de código:
newRequest = getSecureReQuest (solicitação, configoverride); RibBonApacheHttPrequest protegido GetSeCureReQuest (RibBonApacheHttPrequest Solicy, ICLIENTCONFIG CONFIGGOVERRIDE) {if (ISSSECURE (configoverride)) {Final Uri Secureuri = URICOMENTSBUILDER.FromURI (solicitação (). solicitação de retorno.WithNewuri (seguro); } solicitação de retorno; }O NewRequest foi construído usando o contexto no exemplo anterior. A solicitação são os dados solicitados da última vez. Enquanto você executar esse código, você descobrirá que o NewRequest sempre será substituído pela solicitação. Quando vimos isso, descobrimos que era um bug de código -fonte.
Endereço de edição: https://github.com/spring-cloud/spring-cloud-netflix/issues/2667
Resumir
Este é um processo muito comum de verificar problemas. Durante esse processo, quando descobri que a configuração não atendeu às minhas expectativas, verifiquei o significado da configuração e a tentei muitas vezes sem sucesso. Então, depois de depurar o ponto de interrupção, descobri que o ponto de interrupção era anormal. Como a cena exige que uma máquina seja saudável e uma máquina esteja offline, simulei centenas de vezes antes de finalmente localizar essa linha de código. Mesmo um projeto de código aberto é um excelente projeto, e inevitavelmente terá bugs, não supersticioso ou cego. Por outro lado, a capacidade de ler o código -fonte também é uma capacidade importante de resolver problemas. Por exemplo, estou procurando entradas de código -fonte e leva muito tempo para localizar o código.
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.