Scène
Le fonctionnement des microservices de publication consiste généralement à taper un nouveau package de code, à KILL IT LET L'application en cours, à remplacer le nouveau package et à le démarrer.
Eureka est utilisé comme centre de registre de Spring Cloud, qui permet un retard dans les données de la liste des services, c'est-à-dire, même si l'application n'est plus dans la liste des services, le client demandera toujours cette adresse pendant une période. Ensuite, la demande apparaîtra, entraînant une défaillance.
Nous allons optimiser le temps de rafraîchissement de la liste des services pour améliorer la rapidité des informations sur la liste des services. Mais quoi qu'il en soit, il est impossible d'éviter une période de temps où les données sont incohérentes.
Donc, une façon dont nous avons pensé est de réessayer le mécanisme. Lorsqu'une machine A est redémarrée, B dans le même cluster peut fournir des services normalement. S'il y a un mécanisme de réessayer, vous pouvez réessayer B dans le scénario ci-dessus sans affecter la bonne réponse.
fonctionner
Les opérations suivantes sont nécessaires:
Ribbon: Readtimeout: 10000 ConnectTimeout: 10000 MaxAutoretries: 0 MaxAutoreTRIESNextServer: 1 OktoretryOnalLoperations: False
Présentation du package printanier
<dependency> <proupId> org.springframework.retry </rombandid> <ArtefactId> printemps-retry </refactive> </dEpendency>
Prenant l'exemple de Zuul, vous devez également configurer et activer la réessayer:
zuul.retryable = true
Ont rencontré un problème
Cependant, tout n'était pas si lisse. Le mécanisme de réessayer de test est entré en vigueur, mais il n'a pas demandé une autre machine saine comme je l'imaginais. J'ai donc été obligé d'aller au code open source et j'ai finalement découvert qu'il s'agissait d'un bogue de code source, mais il avait été corrigé et la version a été mise à niveau.
Analyse du code
La version utilisée est
Spring-Cloud-Netflix-Core: 1.3.6.release
Spring-retry: 1.2.1.release
Version de dépendance à Spring Cloud:
<DependencyManagement> <Dependces> <Dependency> <ProupId> org.springframework.cloud </proncId> <ArtifActid> Spring-Cloud-Dependuces </ ArfactId> <Dersion> $ {Spring-Cloud.Version} </Dection> <pype> POM </pype> </ Scope> Import </cope> </dependency> </dépendance> </ Dependmanection>Étant donné que Retry est activé, la méthode RetryABleRiBbonloadBalancingHttpClient.Exécute est exécutée lors de la demande de l'application:
Public RibbonAPacheHttpResponse EXECUTE (Final RubbonAPacheHttpRequest Request, final iClientConfig configoverride) lève exception {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.setRedirectSenabled (config.get (CommonClientConfigkey.followRedirect, this.followRedirect)); Final requestConfig requestConfig = builder.build (); Final LoadBalancedRetryPolicy RetryPolicy = LoadBalancedRetryPolicyFactory.Create (this.getClientName (), this); RetryCallback RetryCallback = new RetryCallback () {@Override public rubbonapacheHttpResponse Dowithretry (ReryContext Context) lève une exception {// sur les réchauffages, la stratégie choisira le serveur et le définira dans le contexte // extraire le serveur et mettez à jour la demande étant faite rubBbonapachehttPrequest NewRequest = demande; if (context instanceof loyBalancedRetryContext) {ServiceInstance Service = ((loadBalancedRetryContext) Context) .getServiceInstance (); if (service! = null) {// Reconstruire la demande URI à l'aide de l'hôte et du port dans le contexte de réessayer NewRequest = newRequest.WithNewuri (New Uri (Service.Geturi (). GetScheme (), NewRequest.Getturi (). GetUserIrinfo (), Service.Gethost, Service.Getport (), NewReQuest.Get.GetUri (). newRequest.geturi (). getQuery (), newRequest.getUri (). getFragment ())); }} newRequest = getSecureRequest (request, configoverride); Httpurirequest httpurirequest = newRequest.torequest (requestConfig); HttpResponse final httpResponse = RetryableBbonloadBalancingHttpClient.this.delegate.execute (httpurirequest); if (retRyPolicy.RetryableStAruscode (httpResponse.getStatusline (). getStaturScode ())) {if (closablehttpResponse.class.isinstance (httpResponse)) {((closablehttponse) httpSonse) .close (); } lancez nouveau RetryABleStArusODEException (RetryableBbonloadBalancingHttpClient.this.clientName, httpResponse.getStatusline (). getSaturSCODE ()); } RETOUR NOUVEAU RIBBONAPACHEHTTPRESPONSE (HttpResponse, httpurirequest.geturi ()); }}; Renvoie this.executewithretry (demande, retRyPolicy, retRyCallback); }Nous avons constaté que nous avons d'abord nouveau un RetryCallback, puis d'exécuter ce.ExecuteWithretry (request, retRyPolicy, retRyCallback);
Nous constatons clairement que le code de retRyCallback.Dowithretry est le code demandé réel, ce qui signifie que cette méthode ExecuteWithretry appellera éventuellement RetryCallback.Dowithretry
Protégé <t, e étend le throwable> t doExecute (retRyCallback <t, e> rerycallback, recoveryCallback <t> recoverncallback, rerystate état) lève E, EspheseDretryException {RetryPolicy retryPolicy = this.retrypolicy; BackOffPolicy backoffPolicy = this.backoffpolicy; // Permettez à la politique de réessayer d'initialiser lui-même ... RetryContext Context = Open (RetryPolicy, State); if (this.logger.istraceenabled ()) {this.logger.trace ("retRyContext récupéré:" + context); } // Assurez-vous que le contexte est disponible dans le monde pour les clients qui ont besoin // ... RetrySynchronisationManager.Register (contexte); Lancier lastException = null; booléen épuisé = false; Essayez {// Donnez aux clients une chance d'améliorer le contexte ... Boolean Running = DoOpenInterceptors (RetryCallback, Context); if (! Running) {lancez New TerminateTreryException ("Retry terminée anormalement par intercepteur avant la première tentative"); } // Obtenez ou démarrez le contexte de redémarrage ... backoffContext backoffContext = null; Objet ressource = context.getAttribute ("backoffContext"); if (ressource instanceof backoffContext) {backoffContext = (backoffContext) Resource; } if (backoffContext == null) {backoffContext = backoffpolicy.start (context); if (backoffContext! = null) {context.setAttribute ("backoffContext", backoffContext); }} / * * Nous permettons à toute la boucle d'être ignorée si la politique ou le contexte prévoit déjà le premier essai. Ceci est utilisé dans le cas de la nouvelle tentative externe pour permettre une * récupération dans HandleretryExhusted sans le traitement de rappel (qui * lancerait une exception). * / while (canretry (retRyPolicy, Context) &&! context.isexhaUstEdOnly ()) {try {if (this.logger.isdebugeNabled ()) {this.logger.debug ("retry: count =" + context.getRetryCount ()); } // Réinitialisez la dernière exception, donc si nous réussissons //, les intercepteurs proches ne penseront pas que nous avons échoué ... LastException = null; return retRyCallback.DowithRetry (contexte); } catch (Throwable E) {LastException = E; essayez {registreThrowable (retRyPolicy, State, Context, E); } catch (exception ex) {throw new TerminyTreryException ("n'a pas pu enregistrer Thrownable", ex); } Enfin {DoonErrorInterceptors (RetryCallback, context, e); } if (canretry (retRyPolicy, Context) &&! context.isexhaUstEdOnly ()) {try {backoffpolicy.backoff (reackoffContext); } catch (backoffinterruptedException ex) {LastException = e; // Back Off a été empêché par un autre thread - échouer à la réessayer if (this.logger.isdebugeNabled ()) {this.logger .debug ("Abort rerym car interrompu: count =" + context.getRetryCount ()); } lancer ex; }} if (this.logger.isdebugeNabled ()) {this.logger.debug ("Vérification pour rethrow: count =" + context.getRetryCount ()); } if (ommétHrow (retRyPolicy, Context, State)) {if (this.logger.isdebugeNabled ()) {this.logger.debug ("rethrow in Retry for Policy: count =" + context.getRetryCount ()); } lancer RetryTemplate. <e> wearIfnessary (e); }} / * * Une tentative avec état qui peut réessayer peut repenser l'exception avant maintenant, * mais si nous arrivons aussi loin dans une réessayer avec état, il y a une raison à cela, * comme un disjoncteur ou un classificateur de recul. * / if (state! = null && context.hasattribute (global_state)) {break; }} if (state == null && this.logger.isdebugeNabled ()) {this.logger.debug ("Retry a échoué la dernière tentative: count =" + context.getRetryCount ()); } épuisé = true; return handletryetryExhusted (RecoveryCallback, Context, State); } Catch (Throwable E) {Throw RetryTemplate. <e> wearIfnessary (e); } Enfin {Close (RetryPolicy, Context, State, LastException == NULL || Épuisé); docloseInterceptors (retRyCallback, context, LastException); RetrySynchronisationManager.Clear (); }}Implémentez le mécanisme de réessayer dans une boucle de temps. Lorsqu'une exception se produit lorsqu'une retryCallback.Dowithretry (contexte) est exécutée, elle prendra une exception. Utilisez ensuite RetryPolicy pour déterminer s'il faut réessayer. Portez une attention particulière au registre (RetryPolicy, État, contexte, E); méthode. Non seulement il détermine s'il faut réessayer, mais dans le cas de la réessayer, une nouvelle machine sera sélectionnée et mise dans le contexte, puis elle sera apportée lorsque RetryCallback.
Mais pourquoi ma configuration n'a-t-elle pas changé le téléphone? Le code de débogage a révélé que RegisterThrowable (RetryPolicy, State, Context, E); La machine sélectionnée est bien, c'est une machine nouvelle et saine, mais lors de l'exécution du code RetryCallback.Dowithretry (context), il est toujours demandé.
Examinons donc de plus près le code de RetryCallback.Dowithretry (contexte):
Nous avons trouvé cette ligne de code:
newRequest = getSecureRequest (request, configoverride); Protégé RubbonapacheHttpRequest GetSeCureRequest (RibbonapacheHttpRequest, iclientConfig configoverride) {if (iSsecure (configoverride)) {final uri sécuris = uricomponentsbuilder.fromuri (request.geturi ()) .scheme ("https"). build (true). Retour demande.withnewuri (SecureUri); } requête de retour; }Le NewRequest a été construit en utilisant le contexte dans l'exemple précédent. La demande est les données demandées la dernière fois. Tant que vous exécutez ce code, vous constaterez que le NewRequest sera toujours écrasé par la demande. Lorsque nous avons vu cela, nous avons découvert qu'il s'agissait d'un bug de code source.
Adresse du numéro: https://github.com/spring-cloud/spring-cloud-netflix/issues/2667
Résumer
Il s'agit d'un processus très ordinaire de vérification des problèmes. Au cours de ce processus, lorsque j'ai constaté que la configuration ne répondait pas à mes attentes, j'ai d'abord vérifié la signification de la configuration et l'ai essayé plusieurs fois sans succès. Donc, après avoir débogué le point d'arrêt, j'ai constaté que le point d'arrêt était anormal. Parce que la scène nécessite qu'une machine soit en bonne santé et qu'une machine soit hors ligne, je l'ai simulée des centaines de fois avant que je n'ai finalement localisé cette ligne de code. Même un projet open source est un excellent projet, et il aura inévitablement des bugs, pas superstitieux ou aveugles. D'un autre côté, la possibilité de lire le code source est également une capacité importante à résoudre des problèmes. Par exemple, je recherche des entrées de code source, et il faut beaucoup de temps pour localiser le code.
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.