저는 최근에 스프링 부팅 프로젝트입니다. 이 프로젝트에는 몇 가지 RateLimit 로그에서 앱이 원격 REST API에 연결할 때 오류가 발생했습니다. 이 앱도 동기식 차단을 사용하는 것으로 밝혀졌습니다. RestTemplate 클라이언트를 사용하여 API 호출을 수행할 수 있습니다. 웹 클라이언트는 내부적으로 Reactor API를 사용하고 있습니다.
Reactor와 일반적으로 반응형 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.스프링 프레임워크.웹.클라이언트.HttpClientErrorException$너무 많은 요청: 429 Too 많은 요청: [{"응답 유형":"ERROR","메시지":"요청 횟수가 1분 제한을 초과했습니다."}] 에서 org.스프링 프레임워크.웹.클라이언트.HttpClientErrorException.create(HttpClientErrorException.자바:137) ~[봄-웹-5.2.9.RELEASE.jar!/:5.2.9.릴리스] 에서 org.스프링 프레임워크.웹.클라이언트.기본 응답 오류 처리기.핸들 에러(기본 응답 오류 처리기.자바:184) ~[봄-웹-5.2.9.RELEASE.jar!/:5.2.9.릴리스] 에서 org.스프링 프레임워크.웹.클라이언트.기본 응답 오류 처리기.핸들 에러(기본 응답 오류 처리기.자바:125) ~[봄-웹-5.2.9.RELEASE.jar!/:5.2.9.릴리스] 에서 org.스프링 프레임워크.웹.클라이언트.응답 오류 처리기.핸들 에러(응답 오류 처리기.자바:63) ~[봄-웹-5.2.9.RELEASE.jar!/:5.2.9.릴리스] 에서 org.스프링 프레임워크.웹.클라이언트.RestTemplate.핸들 응답(RestTemplate.자바:782) ~[봄-웹-5.2.9.RELEASE.jar!/:5.2.9.릴리스] 에서 org.스프링 프레임워크.웹.클라이언트.RestTemplate.doExecute(RestTemplate.자바:740) ~[봄-웹-5.2.9.RELEASE.jar!/:5.2.9.릴리스] 에서 org.스프링 프레임워크.웹.클라이언트.RestTemplate.실행(RestTemplate.자바:674) ~[봄-웹-5.2.9.RELEASE.jar!/:5.2.9.릴리스] 에서 org.스프링 프레임워크.웹.클라이언트.RestTemplate.getForObject(RestTemplate.자바:315) ~[봄-웹-5.2.9.RELEASE.jar!/:5.2.9.릴리스] 에서 com.카우치베이스.교육.couchlms.저장소.Lms저장소.getCourseModules(Lms저장소.자바:102) ~[클래스!/:0.0.40-스냅샷] 에서 com.카우치베이스.교육.couchlms.서비스.Lms프로세서.프로세스 코스 모듈(Lms프로세서.자바:147) [클래스!/:0.0.40-스냅샷] 에서 com.카우치베이스.교육.couchlms.서비스.Lms프로세서.프로세스 코스(Lms프로세서.자바:91) [클래스!/:0.0.40-스냅샷] 에서 com.카우치베이스.교육.couchlms.구성.스케줄링 구성.예정된 코스 풀러(스케줄링 구성.자바:45) [클래스!/:0.0.40-스냅샷] 에서 sun.반영.네이티브 메서드 접근자 임플란트.invoke0(네이티브 방법) ~[na:1.8.0_252] 에서 sun.반영.네이티브 메서드 접근자 임플란트.호출(네이티브 메서드 접근자 임플란트.자바:62) ~[na:1.8.0_252] 에서 sun.반영.DelegatingMethodAccessorImpl.호출(DelegatingMethodAccessorImpl.자바:43) ~[na:1.8.0_252] 에서 자바.lang.반영.방법.호출(방법.자바:498) ~[na:1.8.0_252] 에서 org.스프링 프레임워크.스케줄링.지원.ScheduledMethodRunnable.실행(ScheduledMethodRunnable.자바:84) [봄-컨텍스트-5.2.9.RELEASE.jar!/:5.2.9.릴리스] 에서 org.스프링 프레임워크.스케줄링.지원.오류 처리 위임 실행 가능.실행(오류 처리 위임 실행 가능.자바:54) [봄-컨텍스트-5.2.9.RELEASE.jar!/:5.2.9.릴리스] 에서 자바.활용.동시.실행자$실행 가능한 어댑터.통화(실행자.자바:511) [na:1.8.0_252] 에서 자바.활용.동시.미래 작업.실행 및 재설정(미래 작업.자바:308) [na:1.8.0_252] 에서 자바.활용.동시.스케줄된 스레드 풀 실행기$예약된 미래 작업.액세스$301(스케줄된 스레드 풀 실행기.자바:180) [na:1.8.0_252] 에서 자바.활용.동시.스케줄된 스레드 풀 실행기$예약된 미래 작업.실행(스케줄된 스레드 풀 실행기.자바:294) [na:1.8.0_252] 에서 자바.활용.동시.스레드풀 실행자.런워커(스레드풀 실행자.자바:1149) [na:1.8.0_252] 에서 자바.활용.동시.스레드풀 실행자$Worker.실행(스레드풀 실행자.자바:624) [na:1.8.0_252] 에서 자바.lang.스레드.실행(스레드.자바:748) [na: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 |
.uri(ub -> ub.경로 세그먼트(uri).쿼리 매개변수(쿼리 매개변수).빌드()) .검색() .onStatus( HttpStatus.너무_많은_요청::같음, 응답 -> { 목록<String> 헤더 = 응답.헤더().헤더("재시도 후"); 정수 지연시간(초); 만약 (!헤더.isEmpty()) { 지연시간(초) = 정수.valueOf(헤더.get(0)); } else { 지연시간(초) = 60; } 반환 응답.bodyToMono(문자열.클래스).지도(msg -> new RateLimitException(msg, 지연시간(초))); }) .bodyToMono(문자열.클래스) |
이 코드는 GET 요청을 처리합니다. 웹클라이언트를 사용하면 요청의 응답을 살펴보고 적절하게 대응할 수 있습니다. onStatus 메서드를 호출합니다. 첫 번째 매개변수는 반환된 HTTP 상태 코드를 필터링하는 데 사용되는 부울입니다. 여기서는 상태 코드가 429이면 무언가를 수행합니다.
우리는 응답 헤더가 있는지 확인합니다. 재시도 후 헤더를 초기화하는 경우, 그렇다면 지연시간(초) 변수로 설정하고, 그렇지 않은 경우 기본값을 60으로 설정합니다. 그런 다음 응답 바디에 매핑된 RateLimitException. 본문 콘텐츠는 오류 메시지로 사용되며 지연 시간은 별도의 필드에서 사용할 수 있습니다. 자세한 내용은 예외 코드를 참조하세요:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
가져오기 자바.시간.기간; public 클래스 RateLimitException 확장 던지기 가능 { 비공개 int 재시도 후 지연 = 60; public RateLimitException(문자열 메시지) { super(메시지); } public RateLimitException(문자열 메시지, int 재시도 후 지연) { super(메시지); 이.재시도 후 지연 = 재시도 후 지연; } public int getRetryAfterDelay() { 반환 재시도 후 지연; } public 기간 getRetryAfterDelayDuration() { 반환 기간.의 초(재시도 후 지연); } } |
따라서 이 특정 오류를 포착한 다음 주어진 기간 후에 다시 시도하는 것이 필요합니다. Reactor는 다음을 제공하여 이를 쉽게 만듭니다. 재시도 전략. 여러분이 해야 할 일은 다시 시도할 때 메서드를 사용합니다:
|
1 2 3 4 5 6 |
.bodyToMono(문자열.클래스) .다시 시도할 때(다시 시도.withThrowable(throwableFlux -> { 반환 throwableFlux.필터(t -> t 인스턴스 오브 RateLimitException).지도(t -> { RateLimitException rle = (RateLimitException) t; 반환 다시 시도.고정 지연(1, rle.getRetryAfterDelayDuration()); }); |
다양한 다시 시도 메서드를 사용할 수 있지만, 여기서는 withThrowable() 빌더. 이 빌더는 플럭스에 RateLimitException. 따라서 먼저 필터를 적용하여 이를 확인합니다. 그런 다음 해당 예외를 실제 재시도 개체에 매핑합니다. 여기에는 Retry.fixedDelay 전략을 사용하여 최대 시도 횟수와 기간을 매개 변수로 사용합니다. 지속 시간은 RateLimitException 를 던졌습니다.
이를 통해 요청이 429를 반환할 때마다 클라이언트는 재시도할 때까지 적절한 시간을 기다리게 됩니다. 그리고 Reactor를 사용하는 것이 시도/잡기 스프링의 RestTemplate. 리액티브 프로그래밍이 처음에는 조금 두렵게 느껴질 수 있지만 HTTP 요청 및 응답과 같은 데이터 스트림을 관리하거나 다음과 같이 리액티브 프로그래밍을 지원하는 데이터베이스에 대한 연결을 관리할 수 있는 좋은 방법입니다. 카우치베이스.
더 많은 도움과 아이디어가 필요하신가요?