๐ŸŒฑ Spring Retry : ์™ธ๋ถ€ API ์žฅ์• ์— ๊ฐ•ํ•œ ์„œ๋น„์Šค ๋งŒ๋“ค๊ธฐ

๋ฆผ๋ฏผ์ง€ยท2025๋…„ 5์›” 13์ผ

Today I Learn

๋ชฉ๋ก ๋ณด๊ธฐ
55/62

์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ ํ†ต์‹ ํ•  ๋•Œ, ๊ฐ„ํ—์ ์œผ๋กœ ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.

ํ•œ ๋ฒˆ ์‹คํŒจํ–ˆ๋‹ค๊ณ  ๋ฐ”๋กœ ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•ด๋ฒ„๋ฆฌ๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์€ ๋ฌผ๋ก  ์‹œ์Šคํ…œ ์‹ ๋ขฐ์„ฑ์—๋„ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋”ฐ...ใ…œใ…œ

์ด๋Ÿฐ ์ƒํ™ฉ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ์ž๋™ ์žฌ์‹œ๋„(Retry) ๊ธฐ๋Šฅ, spring-retry๋ฅผ ์•Œ์•„๋ณด์Ÿˆ!

๐Ÿ‘€ Spring Retry๋ž€

: ํŠน์ • ์ž‘์—…์ด ์‹คํŒจํ–ˆ์„ ๋•Œ ์ž๋™์œผ๋กœ ์ •ํ•ด์ง„ ํšŸ์ˆ˜๋งŒํผ ์žฌ์‹œ๋„ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” Spring ๊ธฐ๋ฐ˜์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

์–ธ์ œ ์“ฐ๋ฉด ์ข‹์„๊นŒ?

  1. ์™ธ๋ถ€ API ์ผ์‹œ์  ์˜ค๋ฅ˜๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ (์„œ๋ฒ„๊ฐ€ ์ผ์‹œ์ ์œผ๋กœ ์ฃฝ์—ˆ๊ฑฐ๋‚˜ ํƒ€์ž„์•„์›ƒ ๋ฐœ์ƒ ์‹œ)

  2. DB/Redis ์—ฐ๊ฒฐ ๋Š๊น€ (๋„คํŠธ์›Œํฌ ๋ฌธ์ œ๋กœ ์ ‘์†์ด ์•ˆ ๋˜๋Š” ๊ฒฝ์šฐ)

  3. ๊ฒฐ์ œ, ์ธ์ฆ ์„œ๋น„์Šค ํ˜ธ์ถœ (์™ธ๋ถ€ ์—ฐ๋™ ์‹คํŒจ ์‹œ ๋ณต๊ตฌ ์‹œ๋„)

์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ์ ์ด ์ƒ๊ฒผ์„ ๋•Œ, Spring Retry๋ฅผ ์“ฐ๋ฉด

  • ์ผ์‹œ์ ์ธ ์˜ค๋ฅ˜ ๋ณต๊ตฌ: ์žฌ์‹œ๋„๋กœ ํ•œ ๋ฒˆ์˜ ์‹คํŒจ๋ฅผ ๊ทน๋ณต
  • ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ํ–ฅ์ƒ: ์ฆ‰์‹œ ์‹คํŒจ ๋Œ€์‹  ์žฌ์‹œ๋„ ํ›„ ์„ฑ๊ณต ๊ฐ€๋Šฅ
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋‹จ์ˆœํ™”: ๋ณต์žกํ•œ ์˜ˆ์™ธ/์žฌ์‹œ๋„ ๋กœ์ง์„ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ฒ˜๋ฆฌ
  • Fallback ์ฒ˜๋ฆฌ ์ง€์›: ๋ชจ๋“  ์žฌ์‹œ๋„ ์‹คํŒจ ์‹œ ๋Œ€์ฒด ๋กœ์ง ์‹คํ–‰ ๊ฐ€๋Šฅ

์ด๋Ÿฌํ•œ ์žฅ์ ์ด ์ƒ๊ธด๋‹ค!

๐Ÿ”Ž ์ ์šฉํ•ด๋ณด๊ธฐ

1. gradle ์˜์กด์„ฑ ์ถ”๊ฐ€ + ๋ฉ”์ธ ์‹คํ–‰ ํด๋ž˜์Šค

implementation 'org.springframework.retry:spring-retry'
implementation 'org.springframework.boot:spring-boot-starter-aop'

spring retry๋Š” AOP๊ธฐ๋ฐ˜์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ์˜์กด์„ฑ๋„ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค.

๋˜ํ•œ ๋ฐ˜๋“œ์‹œ!! ๋ฉ”์ธ ์‹คํ–‰๋˜๋Š” ํด๋ž˜์Šค์—
@EnableRetry๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค

@SpringBootApplication
@EnableRetry
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2. @Retryable(~) + @Recover

๋„ˆ ๋ช‡ ๋ฒˆ๊นŒ์ง€ ์‹คํŒจํ•ด๋„ ๋ผ? ๋ฅผ @Retryable๋กœ ์„ค์ •ํ•˜๊ณ 
๋งŒ์•ฝ ๋‹ค ์‹คํŒจํ–ˆ์„๋•Œ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ด์ฃผ๊ณ  ์‹ถ์œผ๋ฉด ์ง๊ฟ์œผ๋กœ @Recover๋ฅผ ์‚ฌ์šฉํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ ์šฉํ•ด๋ณด์ž

@Service
public class ExternalApiService {

    @Retryable(
        value = { RuntimeException.class },//๋‹ค๋ฅธ ์—๋Ÿฌ๋กœ ์„ค์ •ํ•ด๋„ ๋จ
        maxAttempts = 3, //3๋ฒˆ๊นŒ์ง€๋Š” ์‹คํŒจํ•ด๋„ ๋ด์คŒ ใ…‹
        backoff = @Backoff(delay = 2000) //์žฌ์‹œ๋„๊นŒ์ง€ ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„(2์ดˆ)
    )
    public String callApi() {
        System.out.println("API ํ˜ธ์ถœ ์‹œ๋„...");
        throw new RuntimeException("์™ธ๋ถ€ API ์˜ค๋ฅ˜");
    }

์š”๋ ‡๊ฒŒํ•˜๋ฉด 3๋ฒˆ๊นŒ์ง€๋Š” ๋ด์ฃผ๋‹ค๊ฐ€ ๊ทธ๊ฒŒ ๋„˜์–ด๊ฐ€๋ฉด ์—๋Ÿฌ๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋œ๋‹ค.
๊ทผ๋ฐ ์ด๋ ‡๊ฒŒ ์—๋Ÿฌ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๋ณด๋‹ค ์‚ฌ์šฉ์ž์—๊ฒŒ ์–ด๋–ค ํ”ผ๋“œ๋ฐฑ์„ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ๋‹ค?

๊ทธ๋Ÿด๋–„ @Recover๋ฅผ ํ†ตํ•ด
์œ„์˜ ๋ฉ”์„œ๋“œ์—์„œ ๋ฐœ์ƒํ•œ RuntimeException์„ ๋ฐ›์•„์„œ ํ”ผ๋“œ๋ฐฑ์ด ์‹คํ–‰๋œ๋‹ค

    @Recover
    public String recover(RuntimeException e) {
        log.error("๋ชจ๋“  ์‹œ๋„๊ฐ€ ์‹คํŒจํ•จ. address: {}, error : {}", address, e.getMessage());    
    return null;    
    }
}

0๊ฐœ์˜ ๋Œ“๊ธ€