Spring Cloud에서 HystrixCommand를 적용한 소스이다.
호출방식은 Controller -> Service로 실행이 되고 Service에서 실행이 실패하여 fallback으로 빠져서 Exception을 던지는 형태이다.
예제 1) 1명의 사용자만 다운로드 가능 (SEMAPHORE 이용)
@RestController
@RequestMapping("download")
@RequiredArgsConstructor
public class DownloadController {
private final DownloadService downloadService;
@GetMapping
public String download() {
try {
return downloadService.download(UUID.randomUUID().toString());
} catch (HystrixRuntimeException e) {
if (e.getFailureType() == HystrixRuntimeException.FailureType.REJECTED_SEMAPHORE_EXECUTION) {
throw new DownloadFailedException(e.getFallbackException().getMessage());
} else {
throw e;
}
}
}
}
@Slf4j
@Service
public class DownloadService {
// SEMAPHORE 방식을 적용
@HystrixCommand(
groupKey = "downloadService", // 기본값은 클래스명이다.
commandKey = "downloadCommandKey", // 따로 설정되지 않으면 메소드명으로 설정되고 동일한 메소드명이 있으면 오동작할 수 있으니 반드시 설정하자. 또한 application.yml에 설정을 추가할 때 사용한다.
threadPoolKey = "downloadThreadPoolKey", // threadPool별로 다르게 설정을 사용할 경우에 적용
fallbackMethod = "downloadFailed", // fallback이 실행될 메소드 명
ignoreExceptions = { RuntimeException.class }, // exception으로 제외할 클래스
commandProperties = {
// application.yml에도 commandProperties의 설정이 있으면 application.yml의 설정이 우선한다.
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "20000"), // thread가 해당 시간 이상 실행이 되면 timeout이 발생하여 fallback으로 빠진다
@HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE"), // semaphore 방식을 이용
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "2") // semaphore 방식을 이용할 경우 최대 동시 요청 수
}
)
public String download(String id) {
String threadName = Thread.currentThread().getName();
HystrixThreadPoolMetrics instance = HystrixThreadPoolMetrics.getInstance(() -> "downloadThreadPoolKey");
log.debug("Active thread: {}", instance.getCurrentActiveCount());
log.debug("Core pool size: {}", instance.getCurrentCorePoolSize());
log.debug("Maximum pool size: {}", instance.getCurrentMaximumPoolSize());
log.debug("Current task count: {}", instance.getCurrentTaskCount());
log.debug("[" + threadName + "] DownloadService#download start");
execute(id);
log.debug("[" + threadName + "] DownloadService#find stop");
return "success";
}
private boolean execute(String id) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return true;
}
// service 메소드의 파라미터와 리턴타입이 같아야 한다. ==> String download(String id)
private String downloadFailed(String id, Throwable throwable) {
// throwable : java.lang.RuntimeException: could not acquire a semaphore for execution
// exception을 던지면 해당 메소드를 호출하면 쪽으로 전달 (DownloadController)
throw new DownloadFailedException("concurrent request exceeded");
}
}
application.yml에서 특정 메소드에 대한 설정을 하려면 commandKey를 활용하여 추가하면 된다.
- hystrix.command.{commandKey}.execution.isolation.strategy : ISOLATION 전략 => THREAD (default), SEMAPHORE
- hystrix.command.{commandKey}.execution.isolation.thread.timeoutInMilliseconds : Thread의 Timeout 값
default : 1000(ms) - hystrix.command.{commandKey}.execution.timeout.enabled : Timeout 사용여부 => default : true
- hystrix.command.{commandKey}.execution.isolation.semaphore.maxConcurrentRequests : 세마포어 사용시 동시 호출량 제한 => default : 10
위의 DownloadController의 download 메소드의 commandProperties를 applicatoin.yml에 적용하면 아래와 같다.
application.yml
hystrix.command.downloadServiceCommandKey.execution.isolation.strategy: SEMAPHORE
hystrix.command.downloadServiceCommandKey.execution.isolation.thread.timeoutInMilliseconds: 10000
hystrix.command.downloadServiceCommandKey.execution.timeout.enabled: true
hystrix.command.downloadServiceCommandKey.execution.isolation.semaphore.maxConcurrentRequests : 2
예제 2) 실행시간 제한 (5초)
@Slf4j
@Service
public class TimeoutService {
// THREAD 방식을 적용
@HystrixCommand(
groupKey = "timeoutService", // 기본값은 클래스명이다.
commandKey = "timeoutCommandKey", // 따로 설정되지 않으면 메소드명으로 설정되고 동일한 메소드명이 있으면 오동작할 수 있으니 반드시 설정하자. 또한 application.yml에 설정을 추가할 때 사용한다.
threadPoolKey = "timeoutThreadPoolKey", // threadPool별로 다르게 설정을 사용할 경우에 적용
fallbackMethod = "failed", // fallback이 실행될 메소드 명
ignoreExceptions = { RuntimeException.class }, // exception으로 제외할 클래스
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"), // thread가 해당 시간 이상 실행이 되면 timeout이 발생하여 fallback으로 빠진다
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"), // timeout 발생 시 thread를 interrupt 시킬지 여부
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD") // thread 방식을 이용
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "5"), // thread 수
@HystrixProperty(name = "maxQueueSize", value = "0") // 최대 thread queue 크기
}
)
public String check(String id) {
String threadName = Thread.currentThread().getName();
log.debug("[" + threadName + "] timeoutService#download start");
execute(id);
log.debug("[" + threadName + "] timeoutService#find stop");
return "success";
}
private boolean execute(String id) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return true;
}
// service 메소드의 파라미터와 리턴타입이 같아야 한다. ==> String check(String id)
private String failed(String id, Throwable throwable) {
// exception을 던지면 해당 메소드를 호출하면 쪽으로 전달 (TimeoutController)
throw new ExecutionTimeoutException("timeout occurred");
}
}
threadPool 설정
키 | 설명 | 기본값 |
---|---|---|
coreSize | ExecutorService가 유지하는 worker thread의 최소수 | 10 |
maximumSize | ExecutorService가 사용할 수 있는 worker thread의 최대 수 | 10 |
maxQueueSize | ExecutorService에서 대기하는 최대 수 | -1 |
allowMaximumSizeToDivergeFromCoreSize | true로 해야 maximumSize가 적용될 수 있다. true로 하면 coreSize와 maximumSize 중 큰값으로, false로 하면 coreSize가 적용된다. | false |
allowMaximumSizeToDivergeFromCoreSize의 설정에 따른 가용 thread 수
allowMaximumSizeToDivergeFromCoreSize | coreSize | maximimSize | 가용 thread 수 |
---|---|---|---|
true | 1 | 2 | 2개 |
true | 2 | 1 | 2개 |
false | 1 | 2 | 1개 |
false | 2 | 1 | 2개 |
반응형