Escena
El funcionamiento de la publicación de microservicios suele ser escribir un nuevo paquete de código, matarlo deja caer la aplicación en ejecución, reemplazar el nuevo paquete y iniciarlo.
Eureka se utiliza como Centro de Registro en Spring Cloud, que permite el retraso en los datos de la lista de servicios, es decir, incluso si la aplicación ya no está en la lista de servicios, el cliente aún solicitará esta dirección por un período de tiempo. Luego aparecerá la solicitud, lo que resulta en una falla.
Optimizaremos el tiempo de actualización de la lista de servicios para mejorar la puntualidad de la información de la lista de servicios. Pero pase lo que pase, es imposible evitar un período de tiempo cuando los datos son inconsistentes.
Entonces, una forma en que pensamos es volver a intentar el mecanismo. Cuando se reinicia una máquina A, B en el mismo clúster puede proporcionar servicios normalmente. Si hay un mecanismo de reintento, puede volver a intentar B en el escenario anterior sin afectar la respuesta correcta.
funcionar
Se requieren las siguientes operaciones:
cinta: lectura de tiempo: 10000 ConnecttimeOut: 10000 maxautoretries: 0 maxautoretriesNextServer: 1 OkToretryOnalLoperations: FALSO
Introducción del paquete de primavera-retradía
<Spendency> <MoupRoMID> org.springframework.retry </groupid> <artifactId> spring-retry </artifactid> </pendency>
Tomando Zuul como ejemplo, también debe configurar y habilitar volver a intentarlo:
zuul.cretryable = verdadero
Han encontrado un problema
Sin embargo, todo no era tan suave. El mecanismo de reintento de prueba entró en vigencia, pero no solicitó otra máquina sana como imaginé. Así que me vi obligado a ir al código de código abierto y finalmente descubrí que era un error del código fuente, pero se había solucionado y la versión se actualizó.
Análisis de código
La versión utilizada es
Spring-Cloud-Netflix-Core: 1.3.6.
Spring-Retry: 1.2.1.
Versión de dependencia de la nube de primavera:
<SpendencyManagement> <Spendencies> <Spendency> <MoupRoMID> org.springframework.cloud </groupId> <artifactId> Spring-Cloud-Dependencies </artifactId> <versión> $ {springcloud.version} </versión> <pom> pom </type> <appope> import </pope> </pendency> </ ////dependencymanagement>Debido a que el reintento está habilitado, el método RetryableribbonLoadBalancingHttpClient.ExCute se ejecuta al solicitar la aplicación:
Public RibbonApacheHttPResponse Execute (solicitud final de RibbonApacheHttpRequest, final IClientConfig ConfigOverride) lanza la excepción {request CONDECONFIG.BUIREDER builder = requestConfig.custom (); IclientConfig config = configoverride! = Null? configoverride: this.config; Builder.SetConnectTimeOut (config.get (CommonClientConfigKey.ConnectTimeOut, this.ConnectTimeOut)); builder.setsocketTimeout (config.get (comúnClientConfigKey.ReadTimeOut, this.readTimeout)); builder.setRedirectSenabled (config.get (CommonClientConfigKey.FollowDirects, this.followedirects)); Request CONDECCONFIG Final requestConfig = Builder.Build (); Final LoadBalancedRryPolicy RetryPolicy = LoadBalancedRetryPolicyFactory.Create (this.getClientName (), this); RetryCallback retryCallback = new RetryCallback () {@Override public RibbonApacheHttPResponse Dowithcry (contexto de retryContext) lanza la excepción {// en reintentos La política elegirá el servidor y lo establecerá en el contexto // extraer el servidor y actualizar la solicitud que se hace ribbonapachehttPrequest Newrequest = solicitud; if (context instancia de loadBalancedRetryContext) {ServiceInStance Service = ((LoadBalancedRetryContext) context) .getServiceInstance (); if (Service! = Null) {// Reconstruya el URI de solicitud utilizando el host y el puerto establecido en el contexto de reintento NewRequest = NewRequest.WithNewuri (new URI (Service.geturi (). GetScheme (), NewRequest.geturi (). GetUserInfo (), Service.gethostT (), Service.getPort (), NewRequest.geturi (). NewRequest.geturi (). getQuery (), newRequest.geturi (). getFragment ())); }} newRequest = getSeCureRequest (request, configoverride); Httpurirequest httpurirequest = newRequest.torequest (requestConfig); final httpResponse httpResponse = retryableribbonloadbalancinghttpclient.this.delegate.execute (httpurirequest); if (retrypolicy.retryableStatUscode (httpesponse.getStatUsline (). getStatUscode ())) {if (cerrableHttPesponse.class.isInstance (httpResponse)) {((cierreHTTTTPRESPONSE) httPesponse) .close (); } Lanzar nueva RetryableStatUsCodeeException (RetryableribbonLoadBalancinghttpclient.This.ClientName, httpResponse.getStatusline (). getStatUscode ()); } return new RibbonApacheHttPResponse (httpResponse, httpurirequest.geturi ()); }}; devolver esto.executewithretry (solicitud, retrypolicy, retryCallback); }Encontramos que primero nuevo un retrycallback, y luego ejecutamos esto.
Vemos claramente que el código de retryCallback.dowithretry es el código solicitado real, lo que significa que este método de EXECUTEWITHRY finalmente llamará a retryCallback.dowithretry
Protegido <t, e extiende lando> t doExecute (retryCallback <t, e> retryCallback, RecoveryCallback <T> RecoveryCallback, Retryrystate State) lanza E, ExpotheredrycryException {retrypolicy retrypolicy = this.cratryPolicy; Backoffpolicy backoffpolicy = this.backoffpolicy; // Permitir que la política de reintento se inicialice ... RetryContext context = Open (RitryPolicy, State); if (this.logger.istraceEnabled ()) {this.logger.trace ("retryContext Recupered:" + context); } // Asegúrese de que el contexto esté disponible a nivel mundial para los clientes que lo necesitan // ... LastException de lanzamiento = nulo; booleano agotado = falso; intente {// dar a los clientes la oportunidad de mejorar el contexto ... boolean running = doopenInterceptors (retryCallback, context); if (! running) {throw new terminegryRyException ("reintentar terminado anormalmente por interceptor antes del primer intento"); } // Get o inicie el contexto de retroceso ... backoffContext backoffContext = null; Objeto recurso = context.getAttribute ("backoffContext"); if (Resource InstanceOf BackoffContext) {backoffContext = (backoffContext) recurso; } if (backoffContext == null) {backoffContext = backoffpolicy.start (context); if (backoffcontext! = null) {context.setTribute ("backoffContext", backoffContext); }} / * * Permitamos que todo el bucle se omita si la política o el contexto ya * prohíben el primer intento. Esto se usa en el caso del reintento externo para permitir una * recuperación en la manejadora, la expulsión sin el procesamiento de devolución de llamada (que * lanzaría una excepción). */ while (canretry (retrypolicy, context) &&! context.isexhaustedonly ()) {try {if (this.logger.isDebugeNabled ()) {this.logger.debug ("retry: count =" + context.getcryCount ()); } // restablecer la última excepción, por lo que si tenemos éxito // los interceptores cerrados no pensarán que fallamos ... lastException = null; return retryCallback.dowithretry (contexto); } catch (lanzable e) {lastException = e; intente {registreThrowable (retrypolicy, estado, contexto, e); } Catch (Exception Ex) {Throw New TermineDryRyException ("no se pudo registrar lanzamiento", ex); } Finalmente {doonErrorInterceptors (retryCallback, context, e); } if (canretry (retrypolicy, context) &&! context.isexhaustedonly ()) {try {backoffpolicy.backoff (backoffContext); } Catch (backoffFinterruptedException ex) {lastException = e; // El retroceso se evitó por otro hilo: falla el reintento if (this.logger.isDebugeNabled ()) {this.logger .debug ("abort reinty porque interrumpió: count =" + context.getcryCount ()); } tirar ex; }} if (this.logger.isDebugeNabled ()) {this.logger.debug ("Comprobación de Rethrow: count =" + context.getRetryCount ()); } if (deberretRethRow (retrypolicy, context, state)) {if (this.logger.isDebugeNabled ()) {this.logger.debug ("Rethrow in Right for Policy: Count =" + context.getRetryCount ()); } tirar retryryMplate. <E> wrapifnelessary (e); }} / * * Un intento con estado que puede volver a intentar puede volver a revisar la excepción antes, * pero si llegamos tan lejos en un reintento con estado, hay una razón para ello, * como un interruptor de circuito o un clasificador de reversión. */ if (state! = null && context.hasattribute (global_state)) {break; }} if (state == null && this.logger.isDebugeNabled ()) {this.logger.debug ("Retinty Failed Last intento: count =" + context.getRetryCount ()); } agotado = true; return handleretryExhausted (RecoveryCallback, contexto, estado); } catch (Throwable E) {Throw retryrryMplate. <E> wrapifnecessary (e); } Finalmente {Close (RedryPolicy, Context, State, LastException == NULL || agotado); DoCloseInterceptors (retryCallback, context, LastException); RetrySynChronizationManager.Clear (); }}Implemente el mecanismo de reintento en un bucle de tiempo. Cuando se produce una excepción cuando se ejecuta un retryCallback.dowithretry (contexto), captará una excepción. Luego use RetryPolicy para determinar si volver a intentarlo. Preste especial atención a registro de registro (retrypolicy, estado, contexto, e); método. No solo determina si se debe volver a intentar, sino que en el caso del reintento, se seleccionará y se colocará una nueva máquina y se colocará en el contexto, y luego se introducirá cuando se ejecute retryCallback.
Pero, ¿por qué mi configuración no cambió el teléfono? El código de depuración encontró que RegisterThrowable (RetryPolicy, State, Context, E); La máquina seleccionada está bien, es una máquina nueva y saludable, pero al ejecutar el código retryCallback.dowithretry (contexto), todavía se solicita.
Así que echemos un vistazo más de cerca al código de retrycallback.dowithretry (contexto):
Encontramos esta línea de código:
newRequest = getSeCureRequest (solicitud, configoverride); Ribbonapachehttttprequest protegido getSeCureRequest (RibbonApacheHttpRequest Solicitud, iClientConfig configOverride) {if (issecure (configoverride)) {final uri seguro = usricomponentsBuilder.fromuri (request.getUri () .Scheme ("https"). Build (verdadero).).). solicitud de devolución. WithNewuri (Secureuri); } solicitud de retorno; }El NewRequest se ha construido utilizando el contexto en el ejemplo anterior. La solicitud son los datos solicitados la última vez. Mientras ejecute este código, encontrará que la solicitud siempre se sobrescribirá la solicitud. Cuando vimos esto, descubrimos que era un error de código fuente.
Dirección de emisión: https://github.com/spring-cloud/spring-cloud-netflix/issues/2667
Resumir
Este es un proceso muy ordinario de verificación de problemas. Durante este proceso, cuando descubrí que la configuración no cumplía con mis expectativas, primero verificé el significado de la configuración y la probé muchas veces sin éxito. Entonces, después de depurar el punto de interrupción, descubrí que el punto de ruptura era anormal. Debido a que la escena requiere que una máquina esté sana y una máquina fuera fuera de línea, la simulé cientos de veces antes de finalmente localizar esta línea de código. Incluso un proyecto de código abierto es un proyecto excelente, e inevitablemente tendrá errores, no supersticiosos o ciegos. Por otro lado, la capacidad de leer el código fuente también es una capacidad importante para resolver problemas. Por ejemplo, estoy buscando entradas del código fuente, y lleva mucho tiempo localizar el código.
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.