シーン
通常、マイクロサービスの公開の操作は、新しいコードパッケージを入力し、実行中のアプリケーションを削除し、新しいパッケージを交換して起動することです。
EurekaはSpring Cloudのレジストリセンターとして使用されます。これにより、サービスリストデータの遅延が可能になります。つまり、アプリケーションがサービスリストにない場合でも、クライアントはこのアドレスを一定期間リクエストします。その後、リクエストが表示され、失敗します。
サービスリストの更新時間を最適化して、サービスリスト情報の適時性を向上させます。しかし、何があっても、データが一貫していない期間を避けることは不可能です。
したがって、私たちが考えた1つの方法は、メカニズムを再試行することです。マシンAが再起動されると、同じクラスター内のBは正常にサービスを提供できます。再試行メカニズムがある場合、正しい応答に影響を与えることなく、上記のシナリオでBを再試行できます。
動作します
次の操作が必要です。
リボン:ReadTimeOut:10000 ConnectTimeOut:10000 Maxautoretries:0 MaxautoretriesNextServer:1 oktoretryOnalloperations:false
Spring-Retryパッケージの紹介
<Dependency> groupId> org.springframework.retry </groupid> <artifactid> spring-retry </artifactid> </dependency>
Zuulを例にとると、再試行を設定して有効にする必要があります。
zuul.retryable = true
問題に遭遇しました
しかし、すべてがそれほどスムーズではありませんでした。テスト再試行メカニズムは有効になりましたが、私が想像したように別の健康なマシンを要求しませんでした。そのため、私はオープンソースコードに行くことを余儀なくされ、最終的にソースコードのバグであることがわかりましたが、修正され、バージョンがアップグレードされました。
コード分析
使用されるバージョンはです
Spring-Cloud-Netflix-Core:1.3.6.Release
スプリングレトリ:1.2.1.Release
スプリングクラウド依存関係バージョン:
<DependencyManagement> <Dependencies> <Dependency> <groupId> org.springframework.cloud </groupid> <artifactid> spring-cloud-dependencies </artifactid> <version> $ {spring-cloud.version} </version> <type> pom </type> <scopRetryが有効になっているため、アプリケーションを要求するときにretryableribbonloadbalancinghttpclient.executeメソッドが実行されます。
public ribbonapachehttpresponse execute(final ribbonapachehttprequest request、final iclientconfig configoverride)スロー例外{final requestconfig.builder builder = requestconfig.custom(); iclientConfig config = cufientoverride!= 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.FollowRedirects、this.followRedirects)); final requestconfig requestconfig = builder.build(); final loadbalancedretrypolicy retrypolicy = loadbalancedretrypolicyfactory.create(this.getClientName()、this); retrycallback retrycallback = new retrycallback(){@Override public ribbonapachehtttttpresponse dowithretry(retrycontextコンテキスト)スロー例外{//ポリシーがコンテキストを選択し、サーバーを抽出してリクエストを更新するリボンパッケスト=リクエスト=リクエストのリクエストを再生すると、ポリテンが選択します。 if(context instance of loadbalancedretryContext){serviceInstance service =((loadbalancedretrycontext)context).getServiceInstance(); if(service!= null){// retryコンテキストで設定されたホストとポートを使用してリクエストを再構築しますnewRequest.withNewuri(new Uri(service.geturi()。getScheme()、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(httpresponse.getStatusline()。getStatusCode())){if(closeablehttpresponse.class.isInstance(httpresponse))){((colousaeablehttpresponse)httsponse).close(); } new retryablestatuscodeexcection(retryableribbonloadbalancinghttpclient.this.clientname、httpresponse.getStatusline()。getStatusCode()); }新しいribbonapachehttpresponse(httpresponse、httpurirequest.geturi()); }}; this.executewithretry(request、retrypolicy、retrycallback)を返します。 }最初にretrycallbackを新しいものにしてから、これを実行することがわかりました。
retrycallback.dowithretryのコードが実際の要求されたコードであることが明確にわかります。つまり、これは最終的にretrycallback.dowithretryを呼び出すことを意味します
保護された<t、e extends throwable> t doexecute(retrycallback <t、e> retrycallback、RecoverCallback <T> RecoverCallback、Retrystate State)Slows E、ExhoteDretryException {retrypolicy retrypolicy = this.retrypolicy; back offpolicy back offpolicy = this.backoffpolicy; // retryポリシーを初期化するようにします... retryContextコンテキスト= open(retrypolicy、state); if(this.logger.istraceEnabled()){this.logger.trace( "retrycontext取得:" + context); } // //必要なクライアントのためにコンテキストがグローバルに利用可能であることを確認してください... RetrysynchronizationManager.register(Context);スロー可能なlastException = null; boolean排気= false; try {//クライアントにコンテキストを強化する機会を与えます... boolean running = doopeninterceptors(retrycallback、context); if(!running){新しいTerminatedRetryException( "最初の試行前にインターセプターによって異常に終了したretry"); } //バックオフコンテキストを取得または起動... back offcontext back offcontext = null;オブジェクトリソース= context.getAttribute( "backoffcontext"); if(backoffcontextのリソースインスタンス){backOffContext =(backOffContext)resource; } if(backoffcontext == null){backOffContext = backOffpolicy.start(context); if(backoffcontext!= null){context.setattribute( "backoffcontext"、back offcontext); }} / * *ポリシーまたはコンテキストが既に最初の試行を禁じている場合、ループ全体をスキップすることを許可します。これは、外部再試行の場合に使用され、コールバック処理なしでハンドレトリックエクスポリートの *回復を可能にします(これは例外がスローされます)。 */ while(canretry(retrypolicy、context)&&!context.isexaxtedonly()){try {if(this.logger.isdebugenabled()){this.logger.debug( "retry:count =" + context.getretrycount()); } //最後の例外をリセットするため、成功した場合//閉じるインターセプターが失敗したとは思わない... lastexception = null; RetryCallback.dowithretry(コンテキスト)を返します。 } catch(throwable e){lastexception = e; try {RegisterThrowable(Retrypolicy、State、Context、e); } catch(Exception ex){新しいTerminatedRetryExceptionをスロー(「登録できませんでした」、Ex); }最後に{doonerrorInterceptors(retrycallback、context、e); } if(canretry(retrypolicy、context)&&!context.isexaxtedonly()){try {back offpolicy.backoff(backoffcontext); } catch(backoffinterruptedexception ex){lastexception = e; //バックオフは別のスレッドによって防止されました - retryを失敗します(this.logger.isdebugenabled()){this.logger .debug( "redurted:count =" + context.getretrycount()); } exを投げる; }} if(this.logger.isdebugenabled()){this.logger.debug( "rethrow:count =" + context.getretrycount()); } if(shouldRethrow(retrypolicy、context、state)){if(this.logger.isdebugenabled()){this.logger.debug( "rethrow in retry for policy:count =" + context.getretrycount(); } retryTemplateを投げます。 }} / * *再試行できるステートフルな試みは、これまで以前に例外をrethるかもしれません *しかし、ステートフルな再試行でこれまでに到達した場合、 *サーキットブレーカーやロールバック分類器のような理由があります。 */ if(state!= null && context.hasattribute(global_state)){break; }} if(state == null && this.logger.isdebugenabled()){this.logger.debug( "retry failed last tire:count =" + context.getretrycount()); } exoured = true; return handleretretyexed(RecoveryCallback、Context、State); } catch(throwable e){throw retrytemplate。<e> wrapifn decessary(e); }最後に{close(retrypolicy、context、state、lastexception == null || exhared); docloseInterceptors(retrycallback、context、lastException); retrysynchronizationmanager.clear(); }}しばらくループで再試行メカニズムを実装します。 retrycallback.dowithretry(コンテキスト)が実行されたときに例外が発生すると、例外がキャッチされます。次に、Retrypolicyを使用して、再試行するかどうかを判断します。登録可能性(Retrypolicy、State、Context、e)に特に注意してください。方法。再試行するかどうかを決定するだけでなく、再試行の場合、新しいマシンが選択されてコンテキストに入れられ、RetryCallback.DowithRetry(コンテキスト)が実行されると、チェンジャーが再試行されます。
しかし、なぜ私の構成は電話を変えなかったのですか?デバッグコードでは、レジスタショー可能(retrypolicy、state、context、e)があることがわかりました。選択したマシンは問題ありません。これは新しい健康的なマシンですが、RetryCallback.DowithRetry(コンテキスト)コードを実行すると、まだ要求されています。
それでは、retrycallback.dowithretry(コンテキスト)のコードを詳しく見てみましょう。
このコードラインを見つけました:
newRequest = getSecureRequest(request、configoverride); Protected Ribbonapachehttprequest getSecureRequest(ribbonapachehttprequest request、iClientConfig configoverride){if(issecure(configoverride)){final uri secureuri = uricomponentsbuilder.fromuri(request.geturi()).s.scheme()。 return request.withnewuri(secureuri); }リクエストを返します。 }NewRequestは、前の例でコンテキストを使用して構築されています。リクエストは、前回要求されたデータです。このコードを実行する限り、NewRequestは常にリクエストによって上書きされることがわかります。これを見たとき、それがソースコードのバグであることがわかりました。
問題アドレス:https://github.com/spring-cloud/spring-cloud-netflix/issues/2667
要約します
これは、問題をチェックする非常に普通のプロセスです。このプロセス中に、構成が私の期待を満たしていないことがわかったとき、私は最初に構成の意味をチェックし、成功せずに何度も試しました。そのため、ブレークポイントをデバッグした後、ブレークポイントが異常であることがわかりました。シーンでは1つのマシンを健康で、1つのマシンをオフラインにする必要があるため、最終的にこのコードラインを見つけた前に何百回もシミュレートしました。オープンソースプロジェクトでさえ優れたプロジェクトであり、迷信や盲目ではなく、必然的にバグがあります。一方、ソースコードを読む機能も問題を解決する重要な能力です。たとえば、ソースコードのエントランスを探していますが、コードを見つけるのに多くの時間がかかります。
上記はこの記事のすべての内容です。みんなの学習に役立つことを願っています。誰もがwulin.comをもっとサポートすることを願っています。