RESTFUL API 서비스를 제작하면서 가장 고민하고 있는 부분이다.
chat gpt로 추천을 받기 위해서는 1번의 request가 필요.
jmeter로 100번 요청 보냈더니 벌써 gpt가 toManyRequest를 뱉으며 뻗어버렸다.
너무 많은 요청을 받게 하면 안 되는데...
그래서 생각했던 방법들
Synchronize
그래서 싱크로나이즈를 쓸까 생각을 했다. 그러나 Synchronize를 쓰면 임계영역에 하나의 스레드만 가능하다.
(즉 100개의 요청이 들어오면 하나씩 처리해야 해서 너무 오래 걸린다)
응답 시간을 측정했을 때, RestTemplate 기준 하나의 처리에 3초가 걸렸다. 이게 100명의 유저한테 온다면 마지막 유저는 300초는 기다려야 한다.(이게 말인가?)
결국 탈락
WebClient
GPT로 HTTP 요청을 보내는데 현재는 RestTemplate를 사용했다. (편의상 R이라고 부름)
R은 Spring 3부터 지원하고 있으며, 응답을 기다리는 동기 특징을 지니고 있다.
반면에 WebClient는 스프링 5에서 지원하기 시작했으며, 비동기를 지원한다는 특징이 있다.
그래서 사용해보려고 했으나... 아래와 같은 이유로 사용을 포기했다
- 하나의 응답 시간이 3초라 생각보다 엄청 길지 않음.
- 클라이언트 측에서 생성 중일 때 아무것도 못하게 화면을 막아버림
그래서 다른 방법을 찾아야 하는데, 그러다가 찾은 방법이 세마포어(Semaphore)를 이용하는 방법이었다.
세마포어
하나의 자원에 제한된 스레드나 프로세스만 접근하도록 만드는 방법.
이게 딱 내가 원하던 방법이었다!!! 멀티 스레드의 요청은 허용하지만, 싱크로나이즈처럼 하나만 점유하는 것이 아니라, 내가 설정한 제한만큼의 스레드만 접근이 가능한 것.(캬)
세마포어의 작동 원리는 상호 배제 알고리즘에 기반한다.
세마포어는 원자적(Atomic)으로 제어되는 정수 변수.
세마포어의 값이 0이면 자원에 접근하지 못하게 블락(Block).
세마포어의 값이 1 이상이면 접근하게 해 주고, 세마포어를 1 감소.
스레드가 종료하고 나갈 때 세마포어를 반환해 주어서 세마포어 1 증가.
(즉 스레드 종료 시점에 꼭 세마포어를 반납해줘야 한다.)
그래서 나는 5개의 스레드만 동시접근을 가능하게 만들고, 나머지는 대기하도록 만들었다.
private final Semaphore semaphore = new Semaphore(5);
우선 세마포어의 값을 5개로 생성하고
try{
semaphore.acquire();
try-catch-finally를 사용해서 세마포어를 얻은 뒤
finally{
semaphore.release();
}
사용이 끝나면 반납하도록 만들었다.
실제 jmeter로 테스트를 진행해 보았고
100번의 요청이 들어왔을 때 5개씩 끊어서 처리하는 걸 확인할 수 있다.(많은 부하를 어떻게 보면 분산시킨 거라 현재 입장에서는 다행이다)
단 세마포어를 쓸 때 인터럽트 예외가 발생할 수 있다.
어떤 스레드가 세마포어를 획득하기 위해 대기 중인데, 다른 스레드가 와서 해당 스레드한테 "너 작업 멈춰!" 하는 것이다.
그래서 인터럽트 예외를 처리해줘야 한다.
catch (InterruptedException e){
throw new GPTException(GPTExceptionInfo.FAIL_INTERRUPTED, e.getMessage());
}
나는 간단하게 인터럽트 익셉션이 발생하면 커스텀 예외로 돌리게 만들었다.
세마포어라는 개념을 책에서만 봤는데 이렇게 직접 자바에서 쓰니깐 뭔가 신기하네...
'프로젝트 > RESTAPI 추천 서비스' 카테고리의 다른 글
50만개 데이터를 어떻게 페이징을 해볼까 (2) | 2024.04.07 |
---|---|
jpa환경에서 repository 테스트코드 작성해보자 (0) | 2024.04.04 |
RESTFUL API 서비스에서 쿠폰을 발행하자(동시성 문제 해결) (1) | 2024.03.30 |
동료들의 불편함을 개선할 RESTFUL API 추천 서비스 (0) | 2024.03.24 |