REST 기반의 간단한 분산 트랜잭션 구현 - 4편 REST Retry
이 글은 읽기 전에 이전 글까지 모두 읽을 필요는 없습니다. 다만 맥락을 이해하기 위해서 1편은 미리 읽는 편이 좋습니다.
- REST 기반의 간단한 분산 트랜잭션 구현 -1편 TCC 개관
- REST 기반의 간단한 분산 트랜잭션 구현 - 2편 TCC Cancel, Timeout
- REST 기반의 간단한 분산 트랜잭션 구현 - 3편 TCC Confirm(Eventual Consistency)
- REST 기반의 간단한 분산 트랜잭션 구현 - 4편 REST Retry
지난 글까지는 REST 기반의 분산 트랜잭션 구현 방법 중 하나인 TCCTry-Confirm/Cancel를 다루었다. 분산 환경에서는 네트워크 오류나 일시적인 서비스 중지 등으로 인해 일시적으로 REST 요청이 실패할 수 있다. 이러한 문제는 REST 요청을 재시도Retry하여 보안할 수 있다.

이번 글은 스프링 Retry 라이브러리를 사용하여 위의 TCC 시나리오에서 REST 요청 실패 시 재시도 하도록 구현한다.
스프링 Retry
스프링 Retry는 스프링 애플리케이션에서 작업이 실패하였을 때 자동으로 재 시도하는 메커니즘을 제공한다. 최초에 스프링 배치Spring Batch에서 시작되었으며, 현재는 분리되어 스프링 배치와 상관없이 독립적으로 사용할 수 있게 되었는데, 주로 템플릿/콜백 패턴Template/Callback Pattern[1]을 구현한 RetryTemplate을 제공하여 프로그래밍할 수 있게 했다.
// Set the max attempts including the initial attempt before retrying
// and retry on all exceptions (this is the default):
SimpleRetryPolicy policy = new SimpleRetryPolicy(5, Collections.singletonMap(Exception.class, true));
// Use the policy...
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(policy);
template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// business logic here
}
});RetryTemplate 이외에도 Java 어노테이션 방식도 지원한다.
@Service
class Service {
@Retryable(RemoteAccessException.class)
public void service(String str1, String str2) {
// ... do something
}
@Recover
public void recover(RemoteAccessException e, String str1, String str2) {
// ... error handling making use of original args if required
}
}이 글에서는 RetryTemplate를 사용한다.
OrderService
REST 호출 주체가 OrderService이기 때문에 스프링 Retry를 OrderService에 적용한다. 먼저 스프링 Retry를 사용하기 위해 Maven POMProject Object Model에 의존성을 추가한다.[2]
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
// ...
<dependencies>
// ...
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
</dependencies>
</project>그리고 RetryTemplate을 Spring Bean으로 등록한다.
@Configuration
public class AppConfig {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
// 재시도 간격을 2초로 설정
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000l);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
// 재시도 최대 횟수를 5번으로 설정
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(5);
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
}OrderService에서 REST 연결을 책임지는 객체는 TccRestAdapterImpl이다. TccRestAdapterImpl은 스프링 RestTemplate을 사용하여 REST를 호출하고 있다. REST 호출하는 부분을 RetryTemplate로 감싸서 예외 발생 시 재시도 하도록 작성한다.
@Component
public class TccRestAdapterImpl implements TccRestAdapter {
// ...
@Autowired
private RetryTemplate retryTemplate;
// ...
@Override
public void confirmAll(List<ParticipantLink> participantLinks) {
participantLinks.forEach(participantLink -> {
try {
retryTemplate.execute((RetryCallback<Void, RestClientException>) context -> {
restTemplate.put(participantLink.getUri(), null);
return null;
});
} catch (RestClientException e) {
log.error(String.format("TCC - Confirm Error[URI : %s]", participantLink.getUri().toString()), e);
}
});
}
}위의 코드에서는 REST PUT 호출(restTemplate.put()) 시 RestClientException이 발생하면 2초 간격으로 재 시도하며 최대 5번 시도한다.
마치며
이전 글에서 결과적 일관성을 아파치 카프카Apache Kafka를 사용하여 구현하였는데, 이 경우 메시지 중복이 문제가 될 수 있다고 언급하였다. 다음 글은 이를 보안하는 멱등성Idempotence에 대해 다룬다.
GitHub
전체 코드는 필자의 GitHub 저장소에서 확인할 수 있다.
주석
[1] 토비의 스프링 3.1 - 3.5 템플릿과 콜백
템플릿
템플릿(template)은 어떤 목적을 위해 미리 만들어둔 모양이 있는 틀을 가르킨다. 학생들이 도형을 그릴 때 사용하는 도형자 또는 모양자가 바로 템플릿이다. 프로그래밍에서는 고정된 틀 안에 바꿀 수 있는 부분을 넣어서 사용하는 경우 템플릿이라고 부른다.
- 이하 중략
콜백
콜백(callback)은 실행되는 것을 목적으로 다른 오브젝트의 메소드의 전달되는 오브젝트를 말한다. 파라미터로 전달되지만 값을 참조하기 위한 것이 아니라 특정 로직을 담은 메소드를 실행 시키기 위해 사용한다.- 이하 중략
[2] OrderService는 스프링 부트를 사용하고 있다. 스프링 부트 부모 POM에서 이미 스프링 Retry 버전을 관리하고 있기 때문에 별도로 버전을 기술할 필요는 없다.
참고 자료
- https://www.baeldung.com/spring-retry
- 클라우드 네이티브 자바 - Isolating Failures and Graceful Degradation