저는 최근에 관리 업무를 맡게 되었습니다. 스프링 부트 프로젝트입니다. 이 프로젝트에는 몇 가지 RateLimit 앱이 원격 REST API를 호출할 때 로그에 오류가 발생했습니다. 이 앱은 동기식 블로킹 방식을 사용하고 있었던 것으로 밝혀졌습니다. RestTemplate 새로운 Spring 대신 클라이언트가 API 호출을 수행합니다. 웹 클라이언트, 이는 Reactor API를 사용하고 있는 상황에서 발생합니다.
그리고 Reactor과 일반적인 Reactive API 사용의 가장 좋은 점은 무엇인지 아시나요? 데이터 스트림을 활용한 프로그래밍을 매우 쉽게 만들어줍니다. 또한 재시도 전략의 구현도 더욱 쉬워지게 합니다.
먼저 로그에 있는 오류에 대해 이야기해 보겠습니다. 제가 확인한 내용은 다음과 같습니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
org.springframework.웹.클라이언트.HttpClientError$TooManyRequests: 429 너무 많은 요청사항: [{“response_type”:“오류”,“메시지”:“요청 수량이 1분 제한을 초과했습니다”}] at org.springframework.웹.클라이언트.HttpClientError.만들다(HttpClientError.자바:137) ~[봄–웹–5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at org.springframework.웹.클라이언트.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.자바:184) ~[봄–웹–5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at org.springframework.웹.클라이언트.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.자바:125) ~[봄–웹–5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at org.springframework.웹.클라이언트.ResponseErrorHandler.handleError(ResponseErrorHandler.자바:63) ~[봄–웹–5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at org.springframework.웹.클라이언트.RestTemplate.handleResponse(RestTemplate.자바:782) ~[봄–웹–5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at org.springframework.웹.클라이언트.RestTemplate.doExecute(RestTemplate.자바:740) ~[봄–웹–5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at org.springframework.웹.클라이언트.RestTemplate.실행하기(RestTemplate.자바:674) ~[봄–웹–5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at org.springframework.웹.클라이언트.RestTemplate.getForObject(RestTemplate.자바:315) ~[봄–웹–5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at com.카우치베이스.훈련.couchlms.리포지토리.LmsRepository.getCourseModules(LmsRepository.자바:102) ~[수업!/:0.0.40–스냅샷] at com.카우치베이스.훈련.couchlms.서비스.LmsProcessor.processCourseModules(LmsProcessor.자바:147) [수업!/:0.0.40–스냅샷] at com.카우치베이스.훈련.couchlms.서비스.LmsProcessor.processCourses(LmsProcessor.자바:91) [수업!/:0.0.40–스냅샷] at com.카우치베이스.훈련.couchlms.설정.SchedulingConfig.scheduledCoursesPuller(SchedulingConfig.자바:45) [수업!/:0.0.40–스냅샷] at 태양.반사하다.NativeMethodAccessorImpl.invoke0(원주민 방법) ~[나:1.8.0_252] at 태양.반사하다.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.자바:62) ~[나:1.8.0_252] at 태양.반사하다.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.자바:43) ~[나:1.8.0_252] at 자바.lang.반사하다.방법.invoke(방법.자바:498) ~[나:1.8.0_252] at org.springframework.일정 설정.지원.ScheduledMethodRunnable.실행(ScheduledMethodRunnable.자바:84) [봄–컨텍스트–5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at org.springframework.일정 설정.지원.DelegatingErrorHandlingRunnable.실행(DelegatingErrorHandlingRunnable.자바:54) [봄–컨텍스트–5.2.9.RELEASE.jar!/:5.2.9.RELEASE] at 자바.util.동시.유족$RunnableAdapter.전화(유족.자바:511) [나:1.8.0_252] at 자바.util.동시.FutureTask.runAndReset(FutureTask.자바:308) [나:1.8.0_252] at 자바.util.동시.ScheduledThreadPoolExecutor$ScheduledFutureTask.접근$301(ScheduledThreadPoolExecutor.자바:180) [나:1.8.0_252] at 자바.util.동시.ScheduledThreadPoolExecutor$ScheduledFutureTask.실행(ScheduledThreadPoolExecutor.자바:294) [나:1.8.0_252] at 자바.util.동시.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.자바:1149) [나:1.8.0_252] at 자바.util.동시.ThreadPoolExecutor$노동자.실행(ThreadPoolExecutor.자바:624) [나:1.8.0_252] at 자바.lang.줄.실행(줄.자바:748) [나:1.8.0_252] |
항상 그렇듯이 스택 추적의 흥미로운 부분은 맨 위에 있습니다. 오류는 429 요청이 너무 많음, 그리고 메시지는 '있다'라고 말합니다. 1분 속도 제한. 이를 분석하면 HTTP 상태는 반환된 코드는 429입니다.. 이는 속도 제한 오류입니다. 즉, API가 호출자에게 너무 많은 요청을 보냈다고 알려주는 것입니다. 일반적으로 이 문제는 잠시 기다리면 해결될 수 있으며, 심지어 요청을 다시 보내는 것도 가능합니다. 재시도 후 응답에 헤더가 표시되어 얼마나 기다려야 하는지 알려줍니다.
Spring의 WebClient를 통해 그 정보를 어떻게 얻을 수 있는지 살펴보겠습니다:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.유리(UB -> UB.pathSegment(유리).쿼리 매개변수(쿼리 매개변수).빌드()) .회수하다() .onStatus( HTTP 상태 코드.TOO_MANY_REQUESTS::같다, 응답 -> { 목록<String> 헤더 = 응답.헤더().헤더(“Retry-After”); 정수 delayInSeconds; 만약 (!헤더.isEmpty()) { delayInSeconds = 정수.valueOf(헤더.얻다(0)); } 그 외 { delayInSeconds = 60; } 반환 응답.bodyToMono(문자열.클래스).지도(msg -> 새로운 RateLimitException(msg, delayInSeconds)); }) .bodyToMono(문자열.클래스) |
이 코드는 다음을 전송합니다. GET request. WebClient를 사용하면 요청의 응답을 살펴보고 적절히 반응할 수 있습니다. onStatus 방법. 첫 번째 매개변수는 반환된 HTTP 상태 코드를 기준으로 필터링하는 데 사용되는 논리자입니다. 여기서 상태 코드가 429인 경우 우리는 작업을 수행합니다.
우리는 그들을 살펴본다. 답변 헤더, 있는지 확인하세요 재시도 후 헤더, 그렇다면 우리는 delayInSeconds 변수와 함께 사용하면 그렇지 않으면 기본값인 60을 설정합니다. 그런 다음 다시 전송할 수 있습니다. 답변 몸을 매핑하여 RateLimitException. 본문 내용은 오류 메시지로 사용되며 delayInSeconds는 별도의 필드에 제공됩니다. 자세한 내용은 Exception 코드를 참고하세요:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
가져오기 자바.시간.시간; 공공의 클래스 RateLimitException 확장합니다 던질 수 있는 것 { 사적인 int retryAfterDelay = 60; 공공의 RateLimitException(문자열 메시지) { 굉장히(메시지); } 공공의 RateLimitException(문자열 메시지, int retryAfterDelay) { 굉장히(메시지); 이것.retryAfterDelay = retryAfterDelay; } 공공의 int getRetryAfterDelay() { 반환 retryAfterDelay; } 공공의 시간 getRetryAfterDelayDuration() { 반환 시간.ofSeconds(retryAfterDelay); } } |
그러므로 해야 할 일은 특정 오류를 잡고 주어진 기간 후에 다시 시도하는 것입니다. Reactor는 이를 쉽게 구현합니다. 재시도 전략. 당신이 해야 할 일은 전화하는 것뿐입니다. retryWhen 방법:
|
1 2 3 4 5 6 |
.bodyToMono(문자열.클래스) .retryWhen(재시도.Throwable과 함께(투하형 Flux -> { 반환 투하형 Flux.필터(t -> t instanceof RateLimitException).지도(t -> { RateLimitException rle = (RateLimitException) t; 반환 재시도.fixedDelay(1, rle.getRetryAfterDelayDuration()); }); |
다양한 종류가 있습니다. 재시도 방법들, 여기에서는 이들을 사용할 수 있습니다. withThrowable() builder. 이 플럭스는 다음을 포함해야 합니다. RateLimitException. 따라서 이를 확인하기 위해 필터를 적용해 시작합니다. 그런 다음 해당 예외를 실제 Retry 객체에 매핑합니다. 여기서는 이 예외가 재시도. fixedDelay 전략은 최대 시도 수와 지속 시간을 파라미터로 설정합니다. 지속 시간은 다음에서 파생됩니다. RateLimitException 그것은 이전에 던져졌습니다.
그렇다면, 요청이 429를 반환할 때마다 클라이언트는 다시 시도할 때까지 적절한 시간을 기다립니다. 그리고 Reactor를 사용하는 것보다 이를 구현하는 것이 훨씬 더 쉬웠습니다. try/catch 봄의 RestTemplate. 반응형 프로그래밍은 처음에 조금 무서운 것 같지만, HTTP 요청과 응답 같은 데이터 스트림을 관리하거나, 반응형 프로그래밍을 지원하는 데이터베이스와의 연결을 관리하는 훌륭한 방법입니다. 카우치베이스.
더 많은 도움과 아이디어를 원하십니까?

댓글 남기기
댓글을 달기 위해서는 로그인해야합니다.