장면
게시 마이크로 서비스의 작업은 일반적으로 새 코드 패키지를 입력하고 킬 IT가 실행중인 응용 프로그램을 삭제하고 새 패키지를 교체하고 시작하는 것입니다.
Eureka는 Spring Cloud의 레지스트리 센터로 사용되며, 이는 서비스 목록 데이터가 지연 될 수 있습니다. 즉, 응용 프로그램이 더 이상 서비스 목록에 있지 않더라도 클라이언트는 여전히 일정 기간 동안이 주소를 요청합니다. 그러면 요청이 나타나면 실패가 발생합니다.
서비스 목록 정보의 적시성을 향상시키기 위해 서비스 목록의 새로 고침 시간을 최적화합니다. 그러나 무엇이든, 데이터가 일치하지 않는 시간을 피하는 것은 불가능합니다.
그래서 우리가 생각한 한 가지 방법은 메커니즘을 다시 시도하는 것입니다. 기계 A가 다시 시작되면 동일한 클러스터의 B는 정상적으로 서비스를 제공 할 수 있습니다. 재 시도 메커니즘이있는 경우, 올바른 응답에 영향을 미치지 않고 위 시나리오에서 B를 재 시도 할 수 있습니다.
작동하다
다음 작업이 필요합니다.
리본 : readtimeout : 10000 ConnectTimeout : 10000 MaxAutoretries : 0 MaxAutoretRiesNexTserver : 1 OktoretryOnAllOperations : False
스프링 레트 크기 패키지 소개
<pectionency> <groupId> org.springframework.retry </groupid> <artifactid> 스프링 레트 레이트 </artifactid> </fectionency>
Zuul을 예로 들어 보면 다시 구성 및 활성화해야합니다.
zuul.retryable = true
문제가 발생했습니다
그러나 모든 것이 그렇게 매끄럽지 않았습니다. 테스트 재시도 메커니즘이 적용되었지만 상상했듯이 다른 건강한 기계를 요청하지 않았습니다. 그래서 나는 오픈 소스 코드로 이동해야했고 마침내 소스 코드 버그라는 것을 알았지 만 수정되어 버전이 업그레이드되었습니다.
코드 분석
사용 된 버전은입니다
Spring-Cloud-Netflix-Core : 1.3.6. Release
스프링 레트 : 1.2.1 release
스프링 클라우드 종속성 버전 :
<pectionencymanagement> <pectionilency> <pectinement> <groupId> org.springframework.cloud </groupId> <artifactid> spring-cloud-dependencies </artifactid> <bersion> $ {spring-cloud.version} </version> <type> pom </type> <coppe> import </dependency> </fexendence>재 시도가 활성화되어 있으므로 Retryableribbonloadbalancinghttpclient.execute 메소드가 응용 프로그램을 요청할 때 실행됩니다.
public ribbonapachehtttpresponse execute (최종 ribbonapachehttprequest request, final iclientconfig configoverride) 예외 {final requestConfig.builder builder = requestConfig.custom (); iclientConfig config = configoverRide! = null? configoverride : this.config; builder.setConnectTimeout (config.get (commentClientConfigkey.connectTimeout, this.connectTimeout)); builder.setsockettimeout (config.get (commentClientConfigKey.Readtimeout, this.ReadTimeout)); builder.setRedirectSenabled (config.get (commonClientConfigkey.FollerRedirects, this.FollowRedirects)); 최종 RequestConfig requestConfig = builder.build (); 최종 부하 BANCENCEDRETRYPOLICY retrypolicy = loadBalancedRetryPolicyFactory.create (this.getClientName (), this); retrycallback retrycallback = new retrycallback () {@override public ribbonapachehttttttttprespontry (retrycontext context) 예외 {// 정책은 서버를 선택하고 서버를 추출하고 요청을 작성하여 ribbonapachehtprequest = 요청; if (loadbalancedretrycontext) {serviceInstance service = ((LoadbalancedReTryContext) context) .getServiceInstance (); if (service! = null) {// 재 시도 컨텍스트에서 설정된 호스트 및 포트를 사용하여 요청 URI를 재구성합니다. NewRequest = newRequest.withNewuri (new uri (service.geturi (). newRequest.geturi (). getQuery (), newRequest.getUri (). getFragment ()); }} newRequest = getSecureRequest (요청, configoverride); httpurirequest httpurirequest = newRequest.torequest (requestConfig); 최종 httpresponse httpresponse = retryableribbonloadbalancinghtpclient.this.delegate.execute (httpurirequest); if (retrypolicy.retryableStatuscode (httpresponse.getStatusline (). getStatuscode ())) {if (closeblehtttpresponse.class.isinstance (httpresponse)) {((closeblehttpresponse) httpresponse) .close (); } 새로운 RETRYABLESTATUSCODEEXCEPTION을 던지십시오 (RetRyablerIbbonloadBalancinghttpclient.this.clientName, httpresponse.getStatusline (). getStatusCode ()); } 새로운 RibbonApacheHTTTPRESPONSE를 반환합니다 (httpresponse, httpurirequest.geturi ()); }}; ExecuTeWithRetry (요청, retrypolicy, retrycallback); }우리는 먼저 새로운 retrycallback을 새로 발견 한 다음 이것을 실행한다는 것을 발견했습니다.
RetryCallback.DowithRetry 코드는 실제 요청 된 코드라는 것을 분명히 알 수 있습니다. 이는이를 의미합니다. execuTeWithRetry 메서드는 결국 retrycallback.dowithRetry를 호출합니다.
보호 된 <t, e는 thrysable> t doexecute (retrycallback <t, e> retrycallback, recoverycallback <t> recoverycallback, retrystate state) e, patridedRetryexception {retrypolicy retrypolicy = this.retrypolicy; Backoffpolicy backoffpolicy = this.backoffpolicy; // 재 시도 정책이 초기화를 허용합니다 ... retryContext context = Open (retrypolicy, state); if (this.logger.istraceenabled ()) {this.logger.trace ( "retryContext 검색 :" + context); } // // it ... retrysynchronizationmanager.register (컨텍스트)가 필요한 클라이언트에 대해 전 세계적으로 컨텍스트를 사용할 수 있는지 확인하십시오. Throwable lastException = null; 부울 지친 = 거짓; {// 고객에게 컨텍스트를 향상시킬 수있는 기회를 제공합니다 ... boolean running = doopeninterceptors (retrycallback, 컨텍스트); if (! running) {Throw New TerminatedRetryException ( "첫 번째 시도 전에 인터셉터에 의해 비정상적으로 종료 된 재 시도"); } // 백 오프 컨텍스트를 얻거나 시작합니다 ... BackoffContext backoffcontext = null; Object Resource = context.getAttribute ( "BackoffContext"); if (backoffcontext의 resource instance) {backoffContext = (backoffContext) resource; } if (backoffContext == null) {backoffContext = backoffpolicy.start (context); if (backoffContext! = null) {context.setAttribute ( "backoffContext", BackoffContext); }} / * * 정책이나 컨텍스트가 이미 첫 번째 시도를 금지하면 전체 루프를 건너 뛸 수 있습니다. 이것은 콜백 처리없이 HandleretryExexhausted의 * 복구를 허용하기 위해 외부 재 시도의 경우 사용됩니다 (예외를 던질 수 있음). */ while (canretry (retrypolicy, context) &&! context.isexhaustedonly ()) {try {if (this.logger.isdebugenabled ()) { "retry : count =" + context.getRetrycount ()); } // 마지막 예외를 재설정하므로 성공하면 닫기 인터셉터가 실패했다고 생각하지 않습니다 ... lastException = null; retrycallback.dowitrretry (컨텍스트)를 반환합니다. } catch (Throwable e) {lastException = e; try {registerThrowable (retrypolicy, state, context, e); } catch (Exception Ex) {Throw New TerminateReatryException ( "등록 할 수 없음", Ex); } 마침내 {doErororinterceptors (retrycallback, context, e); } if (canretry (retrypolicy, context) &&! context.isexhaustedonly ()) {try {backoffpolicy.backoff (backoffcontext); } catch (BackoffinterruptedException ex) {lastException = e; // 다른 스레드에 의해 뒤로 물러서지 않았습니다. } ex 던지기; }} if (this.logger.isdebugenabled ()) {this.logger.debug ( "재고 검사 : count =" + context.getRetryCount ()); } if (if (retrypolicy, context, state)) {if (this.logger.isdebugenabled ()) {this.logger.debug ( "정책에 대한 재구드의 재고 : count =" + context.getRetryCount ()); } retrytemplate 던지기. <e> wrapifnecessary (e); }} / * * 재 시도 할 수있는 상태의 시도는 지금까지 예외를 재창조 할 수 있습니다. * 그러나 우리가 상태가 많은 재시도에서 멀리 떨어져 있으면 회로 차단기 나 롤백 분류기와 같은 이유가 있습니다. */ if (state! = null && context.hasattribute (global_state)) {break; }} if (state == null && this.logger.isdebugenabled ()) {this.logger.debug ( "레트리 실패 : count =" + context.getRetryCount ()); } Patred = true; return handleretryExhausted (recoveryCallback, Context, State); } catch (Throwable e) {retrytemplate. <e> wrapifnecessary (e); } 마침내 {닫기 (retrypolicy, context, state, state, lastException == null || 고기); docloseinterceptors (retrycallback, context, lastException); retrysynchronizationmanager.clear (); }}Roop Loop에서 재 시도 메커니즘을 구현하십시오. retrycallback.dowithretry (컨텍스트)가 실행될 때 예외가 발생하면 예외가 발생합니다. 그런 다음 retrypolicy를 사용하여 재 시도 여부를 결정하십시오. RegisterThrowable (retrypolicy, state, context, e)에 특별한주의를 기울입니다. 방법. 재 시도 여부를 결정할뿐만 아니라 재 시도의 경우 새 기계가 선택되어 상황에 맞게 진행되면 retrycallback.dowithretry (컨텍스트)가 실행되면 체인저가 다시 재 시도됩니다.
그러나 왜 내 구성이 전화를 변경하지 않았습니까? 디버깅 코드는 registerThrowable (retrypolicy, state, context, e)을 발견했습니다. 선택한 기계는 괜찮습니다. 새롭고 건강한 기계이지만 RetryCallback.DowithRetry (Context) 코드를 실행할 때 여전히 요청됩니다.
Retrycallback.dowithRetry (Context)의 코드를 자세히 살펴 보겠습니다.
이 코드 라인을 찾았습니다.
newRequest = getSecureRequest (요청, configoverride); 보호 된 ribbonapachehttprequest getSecureRequest (ribbonapachehttprequest 요청, iclientconfig configoverride) {if (issecure (configoverride)) {final uri secuburi = uicomponentsbuilder.fromuri (request.geturi ()). 반환 요청. } 반환 요청; }NewRequest는 이전 예에서 컨텍스트를 사용하여 구축되었습니다. 요청은 지난번에 요청 된 데이터입니다. 이 코드를 실행하는 한 요청에 따라 NewRequest가 항상 덮어 쓸 것임을 알게됩니다. 우리가 이것을 보았을 때, 우리는 그것이 소스 코드 버그라는 것을 알았습니다.
문제 주소 : https://github.com/spring-cloud/spring-cloud-netflix/issues/2667
요약
이것은 문제를 확인하는 매우 일반적인 과정입니다. 이 과정에서 구성이 내 기대를 충족시키지 못했다는 것을 알았을 때, 먼저 구성의 의미를 확인하고 성공하지 않고 여러 번 시도했습니다. 그래서 중단 점을 디버깅 한 후, 나는 중단 점이 비정상적이라는 것을 알았습니다. 장면에는 하나의 기계가 건강하고 하나의 기계가 오프라인 상태가되어야하므로 마침내이 코드 라인을 찾기 전에 수백 번 시뮬레이션했습니다. 오픈 소스 프로젝트조차도 훌륭한 프로젝트이며 필연적으로 미신적이거나 장님이 아닌 버그가 있습니다. 반면에 소스 코드를 읽는 기능은 문제를 해결하는 데 중요한 기능입니다. 예를 들어, 소스 코드 입구를 찾고 있으며 코드를 찾는 데 많은 시간이 걸립니다.
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.