๐ Spring @Retryable๋ก ์ค๋ฅ ๋ณต๊ตฌ ๋ง์คํฐํ๊ธฐ! ๐ ๏ธ

์๋ ํ์ธ์, ๊ฐ๋ฐ์ ์ฌ๋ฌ๋ถ! ์ค๋์ ์ ๋ง ๊ฟ์ผ ์ฃผ์ ๋ก ์ฐพ์์์ด์. ๋ฐ๋ก Spring์ @Retryable์ ์ด์ฉํ ์ค๋ฅ ๋ณต๊ตฌ ๋ฉ์ปค๋์ฆ ๊ตฌํ์ ๋ํด ์์๋ณผ ๊ฑฐ์์. ์ด๊ฑฐ ์ง์ง ๊ฐ๋ฐ์๋ค ์ฌ์ด์์ ํซํ ์ฃผ์ ๋ผ๊ณ ์! ๐ฅ ์ฌ๋ฅ๋ท์์๋ ์ด๋ฐ ์คํฌ ๊ฐ์ง ๊ฐ๋ฐ์๋ค ๋ชจ์๋ฌ ๋๋ฆฌ ๋ฌ๋ค๋๋ฐ? ใ ใ ใ
์ ๊น! ํน์ ์์ง Spring์ด ๋ญ์ง ๋ชจ๋ฅด๋ ๋ถ๋ค๋ ๊ณ์ค ํ ๋ฐ, ๊ฑฑ์ ๋ง์ธ์. ์ฐ๋ฆฌ ํจ๊ป ์ฐจ๊ทผ์ฐจ๊ทผ ์์๊ฐ ๋ด์. Spring์ ์๋ฐ ๊ฐ๋ฐ์ ์ํ ์คํ์์ค ํ๋ ์์ํฌ์์. ์ฝ๊ฒ ๋งํด์, ์๋ฐ๋ก ์ ํ๋ฆฌ์ผ์ด์ ๋ง๋ค ๋ ์์ฒญ ํธํ๊ฒ ํด์ฃผ๋ ๋๊ตฌ๋ผ๊ณ ์๊ฐํ์๋ฉด ๋ผ์!
๐ค @Retryable์ด ๋ญ๊ธธ๋ ์ด๋ ๊ฒ ๋๋ฆฌ์ผ?
์, ์ฌ๋ฌ๋ถ. ๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ์ง์ง ์ง์ฆ๋๋ ๊ฒ ๋ญ๊ฒ์? ๋ฐ๋ก ์๋ฌ์ฃ ! ํนํ ๋คํธ์ํฌ ๋ฌธ์ ๋ ์ผ์์ ์ธ ์๋ฒ ๋ค์ด ๊ฐ์ ๊ฑฐ ๋๋ฌธ์ ์๊ธฐ๋ ์๋ฌ๋ ์ ๋ง... ๐คฆโโ๏ธ ๊ทผ๋ฐ @Retryable์ ์ฐ๋ฉด ์ด๋ฐ ๋ฌธ์ ๋ฅผ ์์ฒญ ์ฝ๊ฒ ํด๊ฒฐํ ์ ์์ด์!
@Retryable์ Spring์ ์ด๋ ธํ ์ด์ ์ค ํ๋์์. ์ด๊ฑธ ๋ฉ์๋ ์์ ๋ถ์ด๋ฉด, ๊ทธ ๋ฉ์๋๊ฐ ์คํจํ์ ๋ ์๋์ผ๋ก ์ฌ์๋๋ฅผ ํด์ค์. ์์ ๊ฐ๊ฟ ๊ธฐ๋ฅ์ด์ฃ ? ใ ใ ใ
์ ๊ทธ๋ฆผ์ ๋ณด์ธ์. ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด @Retryable์ด "์ ๊น๋ง, ๋ด๊ฐ ๋ค์ ํ ๋ฒ ํด๋ณผ๊ฒ!"๋ผ๊ณ ํ๋ฉด์ ์ฌ์๋๋ฅผ ํ๋ ๊ฑฐ์์. ์์ ๋ ๋ ํ ์น๊ตฌ ๊ฐ์ฃ ? ๐
๐ ๏ธ @Retryable ์ฌ์ฉ๋ฒ, ์ด๋ ต์ง ์์์!
์, ์ด์ ์ค์ ๋ก ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ์์๋ณผ๊น์? ๋จผ์ , Spring Boot ํ๋ก์ ํธ์ spring-retry ์์กด์ฑ์ ์ถ๊ฐํด์ผ ํด์.
implementation 'org.springframework.retry:spring-retry'
implementation 'org.springframework.boot:spring-boot-starter-aop'
์ด๋ ๊ฒ ์์กด์ฑ์ ์ถ๊ฐํ๊ณ ๋๋ฉด, ์ด์ @Retryable์ ์ฌ์ฉํ ์ค๋น๊ฐ ๋ ๊ฑฐ์์! ๐
๊ทธ ๋ค์, ์ฌ์๋ํ๊ณ ์ถ์ ๋ฉ์๋ ์์ @Retryable ์ด๋ ธํ ์ด์ ์ ๋ถ์ฌ์ฃผ๋ฉด ๋ผ์. ์๋ฅผ ๋ค์ด๋ณผ๊น์?
import org.springframework.retry.annotation.Retryable;
@Service
public class MyService {
@Retryable(value = RuntimeException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public void doSomethingRisky() {
// ์ํํ ์์
์ํ
}
}
์ฐ์, ์ด๊ฒ ๋ญ๊ฐ ์ถ์ฃ ? ํ๋์ฉ ์ค๋ช ํด ๋๋ฆด๊ฒ์!
- value = RuntimeException.class: ์ด๊ฑด ์ด๋ค ์์ธ๊ฐ ๋ฐ์ํ์ ๋ ์ฌ์๋ํ ๊ฑด์ง ์ง์ ํ๋ ๊ฑฐ์์. ์ฌ๊ธฐ์๋ RuntimeException์ด ๋ฐ์ํ๋ฉด ์ฌ์๋ํ๋ค๋ ๋ป์ด์์.
- maxAttempts = 3: ์ต๋ ๋ช ๋ฒ๊น์ง ์ฌ์๋ํ ๊ฑด์ง ์ ํ๋ ๊ฑฐ์์. ์ฌ๊ธฐ์๋ 3๋ฒ์ด๋ค์.
- backoff = @Backoff(delay = 1000): ์ฌ์๋ ์ฌ์ด์ ์ผ๋ง๋ ๊ธฐ๋ค๋ฆด์ง ์ ํ๋ ๊ฑฐ์์. 1000์ 1์ด๋ฅผ ์๋ฏธํด์.
์ด๋ ๊ฒ ์ค์ ํ๋ฉด, doSomethingRisky() ๋ฉ์๋๊ฐ RuntimeException์ ๋์ก์ ๋, Spring์ ์ต๋ 3๋ฒ๊น์ง 1์ด ๊ฐ๊ฒฉ์ผ๋ก ์ฌ์๋๋ฅผ ํ ๊ฑฐ์์. ์์ ๋๋ํ์ฃ ? ๐จโ๐ฌ
์ฃผ์! @Retryable์ ๋ง๋ฅ์ด ์๋์์. ์ผ์์ ์ธ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐ๋ ์ข์ง๋ง, ์๊ตฌ์ ์ธ ์ค๋ฅ(์: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ๋ฌธ์ )์๋ ํจ๊ณผ๊ฐ ์์ ์ ์์ด์. ๊ทธ๋์ ์ ์ ํ ์ํฉ์ ์ฌ์ฉํ๋ ๊ฒ ์ค์ํด์!
๐ญ @Recover: @Retryable์ ๋ ๋ ํ ๋ฐฑ์
@Retryable๋ง์ผ๋ก ๋ถ์กฑํ๋ค๊ณ ์? ๊ฑฑ์ ๋ง์ธ์! Spring์ @Recover๋ผ๋ ๋ ๋ค๋ฅธ ๊ฟ ๊ธฐ๋ฅ์ ์ ๊ณตํด์. ์ด๊ฑด ๋ญ๋๊ณ ์? @Retryable๋ก ์ง์ ํ ์ต๋ ์ฌ์๋ ํ์๋ฅผ ๋์ด์๋ ์คํจํ๋ฉด ์คํ๋๋ ๋ฉ์๋๋ฅผ ์ง์ ํ๋ ๊ฑฐ์์.
@Service
public class MyService {
@Retryable(value = RuntimeException.class, maxAttempts = 3)
public void doSomethingRisky() {
// ์ํํ ์์
์ํ
}
@Recover
public void recover(RuntimeException e) {
// ๋ชจ๋ ์ฌ์๋๊ฐ ์คํจํ์ ๋ ์คํ๋ ๋ก์ง
System.out.println("๋ชจ๋ ์ฌ์๋ ์คํจ! ๋์ฒด ๋ก์ง ์คํ");
}
}
์ด๋ ๊ฒ ํ๋ฉด, doSomethingRisky() ๋ฉ์๋๊ฐ 3๋ฒ ์คํจํ๋ฉด recover() ๋ฉ์๋๊ฐ ์คํ๋ผ์. ์๋ฒฝํ ๋ฐฑ์ ํ๋์ด์ฃ ? ๐
์ด ๊ทธ๋ฆผ์ ๋ณด์ธ์. @Retryable์ด ์ธ ๋ฒ ์๋ํ๊ณ ์คํจํ๋ฉด, ๋ง์ง๋ง์ @Recover๊ฐ ๋ฑ์ฅํด์ ์ํฉ์ ์์ตํ๋ ๊ฑฐ์์. ์์ ํ์ด๋ก ๊ฐ์ง ์๋์? ๐ฆธโโ๏ธ
๐งช ์ค์ ์์ : ์ธ๋ถ API ํธ์ถ
์, ์ด์ ์ค์ ๋ก ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ์์ ๋ฅผ ํตํด ์์๋ณผ๊น์? ๊ฐ์ฅ ํํ ์ฌ์ฉ ์ฌ๋ก ์ค ํ๋๋ ์ธ๋ถ API๋ฅผ ํธ์ถํ ๋์์. ๋คํธ์ํฌ ๋ฌธ์ ๋ก API ํธ์ถ์ด ์คํจํ ์ ์์์์? ์ด๋ด ๋ @Retryable์ ์ฌ์ฉํ๋ฉด ์์ฃผ ์ข์์!
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class WeatherService {
private final RestTemplate restTemplate;
public WeatherService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Retryable(value = RuntimeException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public String getWeather(String city) {
String url = "https://api.weather.com/v1/current?city=" + city;
return restTemplate.getForObject(url, String.class);
}
@Recover
public String recoverGetWeather(RuntimeException e, String city) {
return "๋ ์จ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋๋ฐ ์คํจํ์ต๋๋ค. ๋์: " + city;
}
}
์ด ์์ ์์๋ ๋ ์จ API๋ฅผ ํธ์ถํ๋ getWeather() ๋ฉ์๋์ @Retryable์ ์ ์ฉํ์ด์. ๋ง์ฝ API ํธ์ถ์ด ์คํจํ๋ฉด, Spring์ ์ต๋ 3๋ฒ๊น์ง 1์ด ๊ฐ๊ฒฉ์ผ๋ก ์ฌ์๋๋ฅผ ํ ๊ฑฐ์์. ๊ทธ๋๋ ์คํจํ๋ฉด? ๊ทธ๋๋ recoverGetWeather() ๋ฉ์๋๊ฐ ์คํ๋์ด ๊ธฐ๋ณธ ๋ฉ์์ง๋ฅผ ๋ฐํํ๊ฒ ๋ผ์.
์ด๋ ๊ฒ ํ๋ฉด ์ผ์์ ์ธ ๋คํธ์ํฌ ๋ฌธ์ ๋๋ฌธ์ ์ฌ์ฉ์๊ฐ ์๋ฌ๋ฅผ ๋ณด๋ ์ผ์ ํฌ๊ฒ ์ค์ผ ์ ์์ด์. ์์ ๊ฐ๋ฐ์์ ํ๊ฒฉ ์๋๊ฒ ์ด์? ๐
ํ! ์ฌ๋ฅ๋ท์์ ์ด๋ฐ ์คํฌ์ ๊ฐ์ง ๊ฐ๋ฐ์๋ฅผ ์ฐพ๊ณ ๊ณ์ ๋ค๋ฉด, 'Spring ์ ๋ฌธ๊ฐ' ๋๋ '์ค๋ฅ ์ฒ๋ฆฌ ๋ฅ์์'๋ก ๊ฒ์ํด๋ณด์ธ์. ๋ถ๋ช ์ข์ ์ธ์ฌ๋ฅผ ๋ง๋์ค ์ ์์ ๊ฑฐ์์!
๐๏ธ @Retryable ๊ณ ๊ธ ์ค์
์ง๊ธ๊น์ง ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ์ ์์๋ดค๋๋ฐ์, @Retryable์ ๋ ๋ค์ํ ์ต์ ์ ์ ๊ณตํด์. ์ด๊ฑธ ์ ํ์ฉํ๋ฉด ๋์ฑ ์ฌ์ธํ ์ฌ์๋ ์ ๋ต์ ๊ตฌํํ ์ ์๋ต๋๋ค!
- include: ์ฌ์๋ํ ์์ธ ํ์ ์ ์ง์ ํด์. ์ฌ๋ฌ ๊ฐ๋ฅผ ์ง์ ํ ์๋ ์์ด์.
- exclude: ์ฌ์๋ํ์ง ์์ ์์ธ ํ์ ์ ์ง์ ํด์.
- backoff: ์ฌ์๋ ๊ฐ๊ฒฉ์ ๋ ์ธ๋ฐํ๊ฒ ์กฐ์ ํ ์ ์์ด์.
์๋ฅผ ๋ค์ด๋ณผ๊น์?
@Retryable(
include = {SocketTimeoutException.class, ResourceAccessException.class},
exclude = {NullPointerException.class},
maxAttempts = 5,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public void complexOperation() {
// ๋ณต์กํ ์์
์ํ
}
์ด ์ค์ ์ ๋ญ ์๋ฏธํ๋ ๊ฑธ๊น์?
- SocketTimeoutException์ด๋ ResourceAccessException์ด ๋ฐ์ํ๋ฉด ์ฌ์๋๋ฅผ ํด์.
- ํ์ง๋ง NullPointerException์ด ๋ฐ์ํ๋ฉด ์ฌ์๋ํ์ง ์์์.
- ์ต๋ 5๋ฒ๊น์ง ์ฌ์๋ํด์.
- ์ฒซ ๋ฒ์งธ ์ฌ์๋๋ 1์ด ํ์, ๊ทธ ๋ค์๋ถํฐ๋ ๊ฐ๊ฒฉ์ด 2๋ฐฐ์ฉ ๋์ด๋์. (1์ด, 2์ด, 4์ด, 8์ด...)
์ด๋ ๊ฒ ํ๋ฉด ๋คํธ์ํฌ ๊ด๋ จ ๋ฌธ์ ๋ ์ฌ์๋ํ์ง๋ง, ์ฝ๋์ ๋ ผ๋ฆฌ์ ์ค๋ฅ(NullPointerException ๊ฐ์)๋ ๋ฐ๋ก ์คํจ ์ฒ๋ฆฌํ ์ ์์ด์. ๋๋ํ์ฃ ? ๐ง
์ด ๊ทธ๋ฆผ์ ๋ณด์ธ์. ์ฌ์๋ ๊ฐ๊ฒฉ์ด ์ด๋ป๊ฒ ๋์ด๋๋์ง ํ๋์ ๋ณด์ด์ฃ ? ์ด๋ ๊ฒ ํ๋ฉด ์ด๊ธฐ์๋ ๋น ๋ฅด๊ฒ ์ฌ์๋ํ๋ค๊ฐ, ๋ฌธ์ ๊ฐ ์ง์๋๋ฉด ์ ์ ๋ ๊ธด ๊ฐ๊ฒฉ์ ๋๊ณ ์ฌ์๋ํ๊ฒ ๋ผ์. ์๋ฒ์ ๊ณผ๋ถํ๋ฅผ ์ฃผ์ง ์์ผ๋ฉด์๋ ํจ๊ณผ์ ์ผ๋ก ์ฌ์๋ํ ์ ์๋ ๋ฐฉ๋ฒ์ด์์! ๐
๐๏ธ @Retryable ํ์ฉ ์ํคํ ์ฒ
@Retryable์ ์ฌ์ฉํ ๋๋ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ์ํคํ ์ฒ๋ฅผ ๊ณ ๋ คํด์ผ ํด์. ์ด๋ป๊ฒ ๊ตฌ์ฑํ๋ฉด ์ข์๊น์?
- ์๋น์ค ๊ณ์ธต์ ์ ์ฉ: ๋ณดํต @Retryable์ ์๋น์ค ๊ณ์ธต์ ๋ฉ์๋์ ์ ์ฉํด์. ์ด๋ ๊ฒ ํ๋ฉด ๋น์ฆ๋์ค ๋ก์ง๊ณผ ์ฌ์๋ ๋ก์ง์ ๊น๋ํ๊ฒ ๋ถ๋ฆฌํ ์ ์์ด์.
- AOP ํ์ฉ: @Retryable์ ๋ด๋ถ์ ์ผ๋ก AOP(Aspect-Oriented Programming)๋ฅผ ์ฌ์ฉํด์. ์ด๋ฅผ ์ ํ์ฉํ๋ฉด ์ฝ๋ ์ค๋ณต์ ์ค์ด๊ณ ๊ด์ฌ์ฌ๋ฅผ ๋ถ๋ฆฌํ ์ ์์ด์.
- ๋ก๊น ์ ๋ต: ์ฌ์๋๊ฐ ๋ฐ์ํ ๋๋ง๋ค ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๋ ๊ฒ์ด ์ข์์. ์ด๋ ๊ฒ ํ๋ฉด ๋์ค์ ๋ฌธ์ ๋ฅผ ๋ถ์ํ๊ธฐ ์ฌ์์ ธ์.
- ๋ชจ๋ํฐ๋ง ์ฐ๋: ์ฌ์๋ ํ์๋ ์คํจ์จ ๋ฑ์ ๋ชจ๋ํฐ๋ง ์์คํ ๊ณผ ์ฐ๋ํ๋ฉด, ์์คํ ์ ๊ฑด๊ฐ ์ํ๋ฅผ ์ค์๊ฐ์ผ๋ก ํ์ธํ ์ ์์ด์.
์ด๋ฐ ๊ตฌ์กฐ๋ก ์ค๊ณํ๋ฉด ์ด๋ค ์ฅ์ ์ด ์์๊น์?
- ์ฝ๋์ ๊ฐ๋ ์ฑ์ด ๋์์ ธ์.
- ์ ์ง๋ณด์๊ฐ ์ฌ์์ ธ์.
- ์์คํ ์ ์์ ์ฑ์ด ํฅ์๋ผ์.
- ๋ฌธ์ ์ํฉ์ ๋น ๋ฅด๊ฒ ํ์ ํ๊ณ ๋์ํ ์ ์์ด์.
์์ ๊ฐ๋ฐ์์ ๊ฟ์ด์ฃ ? ๐
์ฐธ๊ณ ! ์ฌ๋ฅ๋ท์์๋ ์ด๋ฐ ๊ณ ๊ธ ์ํคํ ์ฒ๋ฅผ ์ค๊ณํ ์ ์๋ ์๋์ด ๊ฐ๋ฐ์๋ค์ ์์๊ฐ ๋์์. ๋ง์ฝ ์ด๋ฐ ์คํฌ์ ๊ฐ์ง๊ณ ๊ณ์๋ค๋ฉด, ์ฌ๋ฅ๋ท์์ ์ฌ๋ฌ๋ถ์ ์ฌ๋ฅ์ ๋๋ ๋ณด๋ ๊ฑด ์ด๋จ๊น์? ๐
๐งฉ @Retryable๊ณผ ๋ค๋ฅธ Spring ๊ธฐ๋ฅ๋ค์ ์กฐํฉ
@Retryable์ ํผ์์๋ ๊ฐ๋ ฅํ์ง๋ง, ๋ค๋ฅธ Spring ๊ธฐ๋ฅ๋ค๊ณผ ์กฐํฉํ๋ฉด ๋์ฑ ๋น์ ๋ฐํด์. ์ด๋ค ์กฐํฉ์ด ๊ฐ๋ฅํ ๊น์?
1. @Transactional๊ณผ์ ์กฐํฉ
@Retryable์ @Transactional๊ณผ ํจ๊ป ์ฌ์ฉํ ๋๋ ์ฃผ์๊ฐ ํ์ํด์. ์๋๊ณ ์? @Transactional์ ๋ฉ์๋ ์คํ์ด ๋๋ ๋ ํธ๋์ญ์ ์ ์ปค๋ฐํ๊ฑฐ๋ ๋กค๋ฐฑํ๋๋ฐ, @Retryable๋ก ์ธํด ๋ฉ์๋๊ฐ ์ฌ๋ฌ ๋ฒ ์คํ๋๋ฉด ์์์น ๋ชปํ ๊ฒฐ๊ณผ๊ฐ ๋ฐ์ํ ์ ์๊ฑฐ๋ ์.
@Service
public class PaymentService {
@Transactional
@Retryable(maxAttempts = 3)
public void processPayment(Payment payment) {
// ๊ฒฐ์ ์ฒ๋ฆฌ ๋ก์ง
}
}
์ด๋ฐ ๊ฒฝ์ฐ, ๊ฐ ์ฌ์๋๋ง๋ค ์๋ก์ด ํธ๋์ญ์ ์ด ์์๋ผ์. ๊ทธ๋์ ์ด์ ์๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ณ๊ฒฝ์ฌํญ์ด ๋กค๋ฐฑ๋์ง ์์ ์ ์์ด์. ์ฃผ์ํด์ ์ฌ์ฉํด์ผ ํด์!
2. @Async์์ ์กฐํฉ
@Retryable์ @Async์ ํจ๊ป ์ฌ์ฉํ๋ฉด ๋น๋๊ธฐ์ ์ผ๋ก ์ฌ์๋๋ฅผ ์ํํ ์ ์์ด์. ์ด๋ ๊ฒ ํ๋ฉด ๋ฉ์ธ ์ค๋ ๋๋ฅผ ๋ธ๋กํนํ์ง ์๊ณ ์ฌ์๋๋ฅผ ํ ์ ์์ฃ .
@Service
public class EmailService {
@Async
@Retryable(maxAttempts = 3)
public CompletableFuture<void> sendEmail(String to, String subject, String body) {
// ์ด๋ฉ์ผ ์ ์ก ๋ก์ง
return CompletableFuture.completedFuture(null);
}
}
</void>
์ด ๋ฐฉ์์ ํนํ ์ธ๋ถ ์๋น์ค ํธ์ถ ๊ฐ์ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ ์ ์ ์ฉํด์. ์ฌ์ฉ์ ๊ฒฝํ์ ํด์น์ง ์์ผ๋ฉด์๋ ์์ ์ฑ์ ๋์ผ ์ ์๊ฑฐ๋ ์!
3. Spring Cloud Circuit Breaker์์ ์กฐํฉ
@Retryable์ Spring Cloud Circuit Breaker์ ํจ๊ป ์ฌ์ฉํ๋ฉด ๋์ฑ ๊ฐ๋ ฅํ ์ค๋ฅ ์ฒ๋ฆฌ ๋ฉ์ปค๋์ฆ์ ๊ตฌ์ถํ ์ ์์ด์. Circuit Breaker๋ ์ผ์ ์์ค ์ด์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ์๋น์ค ํธ์ถ์ ์ผ์์ ์ผ๋ก ์ฐจ๋จํด์ ์์คํ ์ ๋ณดํธํ๋ ์ญํ ์ ํด์.
@Service
public class ResillientService {
@CircuitBreaker(name = "myService", fallbackMethod = "fallback")
@Retryable(maxAttempts = 3)
public String callExternalService() {
// ์ธ๋ถ ์๋น์ค ํธ์ถ ๋ก์ง
}
public String fallback(Exception e) {
return "์ธ๋ถ ์๋น์ค ํธ์ถ ์คํจ. ๊ธฐ๋ณธ๊ฐ ๋ฐํ.";
}
}
์ด๋ ๊ฒ ํ๋ฉด ์ฌ์๋ ํ์๋ ์๋น์ค๊ฐ ์ ์ํ๋์ง ์์ผ๋ฉด Circuit Breaker๊ฐ ์๋ํด์ ์์คํ ์ ๋ณดํธํด์. ์์ ์ฒ ๋ฒฝ ๋ฐฉ์ด ์๋์์? ๐ช
์ด ๊ทธ๋ฆผ์ ๋ณด์ธ์. @Retryable์ด ์ฌ๋ฌ ๋ฒ ์๋ํ๊ณ ์คํจํ๋ฉด, Circuit Breaker๊ฐ ์๋ํด์ ์์คํ ์ ๋ณดํธํ๋ ๋ชจ์ต์ด ๋ณด์ด์๋์? ์ด๋ ๊ฒ ์ฌ๋ฌ ๊ธฐ์ ์ ์กฐํฉํ๋ฉด ๋์ฑ ๊ฐ๋ ฅํ ์ค๋ฅ ์ฒ๋ฆฌ ์์คํ ์ ๋ง๋ค ์ ์์ด์!
๐ฆ @Retryable ์ฌ์ฉ ์ ์ฃผ์์ฌํญ
@Retryable์ ์ ๋ง ์ ์ฉํ ๊ธฐ๋ฅ์ด์ง๋ง, ์ฌ์ฉํ ๋ ์ฃผ์ํด์ผ ํ ์ ๋ค์ด ์์ด์. ์ด๋ค ๊ฒ๋ค์ด ์๋์ง ์์๋ณผ๊น์?
- ๋ฉฑ๋ฑ์ฑ ํ์ธ: ์ฌ์๋ํ๋ ๋ฉ์๋๊ฐ ๋ฉฑ๋ฑ์ฑ์ ๊ฐ์ง๋์ง ๊ผญ ํ์ธํด์ผ ํด์. ๋ฉฑ๋ฑ์ฑ์ด๋ ๋์ผํ ์์ฒญ์ ์ฌ๋ฌ ๋ฒ ๋ณด๋ด๋ ๊ฒฐ๊ณผ๊ฐ ๊ฐ์ ์ฑ์ง์ ๋งํด์. ์๋ฅผ ๋ค์ด, ๊ฒฐ์ ์ฒ๋ฆฌ ๊ฐ์ ๊ฒฝ์ฐ ์ฌ์๋๋ก ์ธํด ์ค๋ณต ๊ฒฐ์ ๊ฐ ๋ฐ์ํ๋ฉด ์ ๋๊ฒ ์ฃ ?
- ์ฌ์๋ ๊ฐ๊ฒฉ ์ค์ : ์ฌ์๋ ๊ฐ๊ฒฉ์ ๋๋ฌด ์งง๊ฒ ์ค์ ํ๋ฉด ์์คํ ์ ๋ถํ์ํ ๋ถํ๋ฅผ ์ค ์ ์์ด์. ๋ฐ๋๋ก ๋๋ฌด ๊ธธ๊ฒ ์ค์ ํ๋ฉด ์ฌ์ฉ์ ๊ฒฝํ์ด ๋๋น ์ง ์ ์์ฃ . ์ ์ ํ ๊ท ํ์ ์ฐพ๋ ๊ฒ ์ค์ํด์.
- ๋ฌดํ ์ฌ์๋ ๋ฐฉ์ง: maxAttempts๋ฅผ ์ค์ ํ์ง ์์ผ๋ฉด ๋ฌดํํ ์ฌ์๋ํ ์ ์์ด์. ์ด๋ ์์คํ ๋ฆฌ์์ค๋ฅผ ๊ณผ๋ํ๊ฒ ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก ์ฃผ์๊ฐ ํ์ํด์.
- ์์ธ ์ฒ๋ฆฌ ์ฃผ์: ๋ชจ๋ ์์ธ์ ๋ํด ์ฌ์๋ํ๋ ๊ฒ์ ์ข์ง ์์์. ์ผ์์ ์ธ ๋ฌธ์ ๋ก ์ธํ ์์ธ๋ง ์ฌ์๋ํ๋๋ก ์ค์ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
- ๋ก๊น ๊ณผ ๋ชจ๋ํฐ๋ง: ์ฌ์๋ ํ์์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋์ ๋ก๊น ํ๊ณ ๋ชจ๋ํฐ๋งํด์ผ ํด์. ์ด๋ฅผ ํตํด ์์คํ ์ ๋ฌธ์ ๋ฅผ ์กฐ๊ธฐ์ ๋ฐ๊ฒฌํ๊ณ ๋์ํ ์ ์์ด์.
๊ฒฝ๊ณ ! @Retryable์ ๋จ์ฉํ๋ฉด ์คํ๋ ค ๋ฌธ์ ๋ฅผ ์จ๊ธฐ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ณ์ ์ ์์ด์. ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ ํด๊ฒฐ ์์ด ์ฌ์๋๋ง ๋ฐ๋ณตํ๋ฉด ์์คํ ์ ์์ ์ฑ์ด ๋จ์ด์ง ์ ์๋ต๋๋ค. ํญ์ ์ ์คํ๊ฒ ์ฌ์ฉํด์ผ ํด์!
๐ @Retryable ์ค์ ํ์ฉ ํ
์, ์ด์ @Retryable์ ์ค์ ์์ ๋์ฑ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ ์ ์๋ ํ๋ค์ ์์๋ณผ๊น์?
1. ์ฌ์๋ ์ ์ฑ ์ปค์คํฐ๋ง์ด์ง
Spring Retry๋ ๊ธฐ๋ณธ์ ์ธ ์ฌ์๋ ์ ์ฑ ์ธ์๋ ๋ค์ํ ์ปค์คํ ์ ์ฑ ์ ๋ง๋ค ์ ์์ด์. ์๋ฅผ ๋ค์ด, ์ง์ ๋ฐฑ์คํ(Exponential Backoff) ์ ์ฑ ์ ๊ตฌํํด๋ณผ๊น์?
@Configuration
public class RetryConfig {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000);
backOffPolicy.setMultiplier(2);
backOffPolicy.setMaxInterval(30000);
retryTemplate.setBackOffPolicy(backOffPolicy);
return retryTemplate;
}
}
์ด๋ ๊ฒ ์ค์ ํ๋ฉด ์ฌ์๋ ๊ฐ๊ฒฉ์ด 1์ด, 2์ด, 4์ด, 8์ด... ์ด๋ ๊ฒ ๋์ด๋๋ค๊ฐ ์ต๋ 30์ด๊น์ง ์ฆ๊ฐํด์. ๋คํธ์ํฌ ์ง์ฐ์ด ์ฌํ ํ๊ฒฝ์์ ํนํ ์ ์ฉํ๋ต๋๋ค!
2. ์กฐ๊ฑด๋ถ ์ฌ์๋
๋๋ก๋ ํน์ ์กฐ๊ฑด์์๋ง ์ฌ์๋๋ฅผ ํ๊ณ ์ถ์ ์ ์์ด์. ์ด๋ด ๋๋ RetryTemplate๊ณผ ํจ๊ป RetryCallback์ ์ฌ์ฉํ ์ ์์ด์.
@Service
public class ConditionalRetryService {
private final RetryTemplate retryTemplate;
public ConditionalRetryService(RetryTemplate retryTemplate) {
this.retryTemplate = retryTemplate;
}
public String conditionalRetry() {
return retryTemplate.execute(context -> {
// ์ฌ๊ธฐ์ ์ฌ์๋ํ ๋ก์ง์ ์์ฑํด์
if (someCondition()) {
throw new RetryableException("์ฌ์๋ ํ์!");
}
return "์ฑ๊ณต!";
}, context -> {
// ๋ชจ๋ ์ฌ์๋๊ฐ ์คํจํ์ ๋์ ๋ก์ง
return "์ต์ข
์คํจ";
});
}
}
์ด ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด ํจ์ฌ ๋ ์ธ๋ฐํ ์ ์ด๊ฐ ๊ฐ๋ฅํด์ ธ์. ์์ ํ๋ก ๊ฐ๋ฐ์ ์คํ์ผ์ด์ฃ ? ๐
3. ์ฌ์๋ ์ํ ๊ด๋ฆฌ
์ฌ์๋ ๊ณผ์ ์์ ์ํ๋ฅผ ์ ์งํ๊ณ ์ถ์ ๋๊ฐ ์์ด์. ์ด๋ด ๋๋ RetryContext๋ฅผ ํ์ฉํ ์ ์์ด์.
- ์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์ฌ๋ฅ๋ท์ ์ต์ AI ๊ธฐ์ ๊ณผ ๋ฒ๋ฅ ์ ๊ธฐ๋ฐํ์ฌ ์์ฌ์ ์ง์ ์ฌ์ฐ๊ถ์ ์ ๊ทน์ ์ผ๋ก ๋ณดํธํ๋ฉฐ,
๋ฌด๋จ ์ฌ์ฉ ๋ฐ ์นจํด ํ์์ ๋ํด ๋ฒ์ ๋์์ ํ ๊ถ๋ฆฌ๋ฅผ ๋ณด์ ํฉ๋๋ค.
ยฉ 2025 ์ฌ๋ฅ๋ท | All rights reserved.
๋๊ธ 0๊ฐ