Feign ์‚ฌ์šฉ (2)

· โ˜• 2 min read · ๐Ÿ‘€... views

Circuit Breaker ์ ์šฉ

API ํ†ต์‹ ์—์„œ ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๋ฅผ ์ ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ์–˜๊ธฐ๋ฅผ ๋“ค์—ˆ๊ณ , ์ด ์—ญ์‹œ ์ฒ˜์Œ ๋“ฃ๋Š” ์šฉ์–ด๋ผ ์ผ๋‹จ ์ดํ•ด๋ฅผ ์œ„ํ•ด..

Circuit Breaker ๋ž€?

  1. ํšŒ๋กœ์ฐจ๋‹จ๊ธฐ๋กœ ๋ถˆ๋ฆฌ๋ฉฐ, ์ „๊ธฐ์  ํšŒ๋กœ๋ฅผ ๊ณผ์ „์•• ๋“ฑ์œผ๋กœ๋ถ€ํ„ฐ ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•œ ์žฅ์น˜
  2. ์‹œ์Šคํ…œ์—์„œ ์žฅ์• ์˜ ํ™•์‚ฐ์„ ๋ง‰๊ธฐ์œ„ํ•ด (๊ฒฐํ•จ์„ ๊ฒฉ๋ฆฌ) ์‚ฌ์šฉ๋˜๋Š” ํŒจํ„ด์˜ ์ผ์ข…
  3. Netflix ์˜ Hystrix๊ฐ€ ์žˆ์Œ

์ฆ‰, ํ†ต์‹ ์— ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ๋•Œ, pool ์„ ๊ณ„์† ์ ์œ ํ•˜๋Š” ํ˜„์ƒ์„ ๋ง‰๊ธฐ ์œ„ํ•ด, ์ฐจ๋‹จ๊ธฐ๋ฅผ open ํ•˜์—ฌ ๊ฒฉ๋ฆฌํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋  ๊ฑฐ ๊ฐ™๋‹ค.
๋ญ”๊ฐ€ ๊ฐœ๋…์ ์œผ๋กœ๋Š” ์ดํ•ด๊ฐ€ ๋˜๋ฉด์„œ๋„ ํ—ท๊ฐˆ๋ฆฌ๋Š” ๊ฐœ๋…์ด๋ž„๊นŒ๋‚˜.. (์ดํ•ด์•ˆ๋๋‹ค๋Š” ์†Œ๋ฆฌ…?)
์—ฌํ•˜ํŠผ ๋ณธ๋ก ์œผ๋กœ ๋“ค์–ด๊ฐ€์„œ ์ด์ „์— ์ ์šฉํ–ˆ๋˜ Feign ์— ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค๋ฅผ ์ ์šฉ์‹œ์ผœ๋ณด๋„๋ก ํ•จ.

1. ์˜์กด์„ฑ ์ถ”๊ฐ€

์ด์ „๊ณผ ๊ฐ™์ด maven ์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ pom.xml ์— ์ถ”๊ฐ€ํ•ด์คŒ.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-feign</artifactId>
            <version>0.17.0</version>
        </dependency>
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-circuitbreaker</artifactId>
            <version>0.17.0</version>
        </dependency>

2. Config Decorating

  1. (1)์—์„œ ์ž‘์„ฑํ•œ AAAClientConfig ๋ฅผ ๋ณ€๊ฒฝ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    @Configuration
    public class AAAClientConfig {
    
    @Value("${url}")
    private String url;

    CircuitBreakerConfig config = CircuitBreakerConfig.custom()
            .failureRateThreshold(50) // ์‹คํŒจ ์ž„๊ณ„์น˜ 50% ๋กœ ์„ค์ •
            .ringBufferSizeInClosedState(20) // 20๋ฒˆ ์‹œ๋„ 
            .ringBufferSizeInHalfOpenState(10) // 10์ดˆ ํ›„ 10๋ฒˆ ์‹œ๋„
            .waitDurationInOpenState(Duration.ofSeconds(3L)) // Open ํ›„ 3์ดˆ ๊ธฐ๋‹ค๋ฆผ
            .build();

    CircuitBreaker circuitBreaker = CircuitBreaker.of("AAAClientCB", config);

    FeignDecorators decorators =
            FeignDecorators.builder().withCircuitBreaker(circuitBreaker).build();

    @Bean
    public AAAClient aAAClient() {
        return Resilience4jFeign.builder(decorators)
                .client(new OkHttpClient())
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .options(new Request.Options(3000, 5000))
                .retryer(Retryer.NEVER_RETRY)
                .logger(new Slf4jLogger(AAAClient.class))
                .logLevel(Logger.Level.FULL)
                .target(AAAClient.class, url);
    }
}

3. Test ์ฝ”๋“œ ์ž‘์„ฑ

  1. ์ •์ƒ์ ์ธ ๊ฒฝ์šฐ
  2. ์ผ๋ถ€๋Ÿฌ ์‹คํŒจ๋ฅผ ํ–ˆ์„ ๋•Œ์˜ ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค open close ํ™•์ธ
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
public class CBTest {
    @Rule
    public WireMockRule wireMockRule = new WireMockRule();
    
    private AAAClient client;
    
    @Before
    public void setUp() {
        
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
            .failureRateThreshold(50) // ์‹คํŒจ ์ž„๊ณ„์น˜ 50% ๋กœ ์„ค์ •
            .ringBufferSizeInClosedState(20) // 20๋ฒˆ ์‹œ๋„ 
            .ringBufferSizeInHalfOpenState(10) // 3์ดˆ ํ›„ 10๋ฒˆ ์‹œ๋„
            .waitDurationInOpenState(Duration.ofSeconds(3L)) // Open ํ›„ 3์ดˆ ๊ธฐ๋‹ค๋ฆผ
            .build();

        CircuitBreaker circuitBreaker = CircuitBreaker.of("AAAClientCB", config);

        FeignDecorators decorators =
              FeignDecorators.builder().withCircuitBreaker(circuitBreaker).build();
    
        aAAClient = Resilience4jFeign.builder(decorators)
                .client(new OkHttpClient())
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .options(new Request.Options(3000, 5000))
                .retryer(Retryer.NEVER_RETRY)
                .logger(new Slf4jLogger(AAAClient.class))
                .logLevel(Logger.Level.FULL)
                .target(AAAClient.class, "http://localhost:8080");
    }

    @Test
    public void ์ •์ƒ์ธ๊ฒฝ์šฐ_Test() {

        stubFor(
                get(urlPathMatching("/1"))
                        .willReturn(
                                aResponse()
                                        .withStatus(200)
                                        .withHeader("content-type", "application/json")
                                        .withBody("{}")
                        )
        );

        boolean allSuccess = IntStream.range(0, 40)
                .mapToObj(i -> Try.of(() -> client.getDetail(1)))
                .allMatch(t -> t.isSuccess());

        assertThat(allSuccess).isTrue();
    }
    
    @Test
    public void ์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค_Open_Close_Test() {

        stubFor(
                get(urlPathMatching("/71"))
                        .willReturn(
                                aResponse()
                                        .withStatus(200)
                                        .withHeader("content-type", "application/json")
                                        .withBody("{}")
                        )
        );

        stubFor(
                get(urlPathMatching("/72"))
                        .willReturn(
                                aResponse()
                                        .withStatus(500)
                                        .withHeader("content-type", "application/json")
                                        .withBody("{}")
                        )
        );

        // ์—๋Ÿฌ ๋น„์œจ 50% ์ด์ƒ
        IntStream.range(0, 10).forEach(i -> Try.of(() -> client.getDetail(72)));

        // 3์ดˆ๊ฐ„ ์˜คํ”ˆ ์ƒํƒœ์—์„œ ๋ฐ”๋กœ ์—๋Ÿฌ
        sleep(1000);
        Try<ApiResponse> failFast1 = Try.of(() -> client.getDetail(71));
        sleep(1500);
        Try<ApiResponse> failFast2 = Try.of(() -> client.getDetail(71));

        // 3์ดˆ ํ›„ ๋ฐ˜ ์˜คํ”ˆ
        sleep(1000);
        Try.of(() -> client.getDetail(71));
        Try<ApiResponse> realEx = Try.of(() -> client.getDetail(72));// 1๊ฐœ ์‹คํŒจ๋กœ๋Š” 50% ๋น„์œจ ์•ˆ ๋จ

        // ๋ฐ˜ ์˜คํ”ˆ ํ›„ ๋‹ค์‹œ ์—ด๋ฆฌ์ง€ ์•Š์œผ๋ฉด ๋ชจ๋‘ ์„ฑ๊ณต
        boolean allSuccess = IntStream.range(0, 40)
                .mapToObj(i -> Try.of(() -> client.getDetail(71)))
                .allMatch(t -> t.isSuccess());

        SoftAssertions.assertSoftly(softly -> {
            softly.assertThat(failFast1.getCause()).isInstanceOf(CallNotPermittedException.class);
            softly.assertThat(failFast2.getCause()).isInstanceOf(CallNotPermittedException.class);
            softly.assertThat(realEx.getCause()).isNotInstanceOf(CallNotPermittedException.class);
            softly.assertThat(allSuccess).isTrue();
        });
    }

    private void sleep(int sleepTime) {
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
        }
    }
}
  • SoftAssertions์€ ๊ฐ assertThat ์ด ์‹คํŒจํ•ด๋„ ๊ฒ€์ฆ์„ ๊ณ„์† ์‹คํ–‰ํ•˜๊ณ  ๋งˆ์ง€๋ง‰์— ๊ฒฐ๊ณผ๋ฅผ ํ•œ๊บผ๋ฒˆ์— ๋ณด์—ฌ์คŒ
    • (๋งˆ์ง€๋ง‰์— softly.assertAll() ์ถ”๊ฐ€)
  • softly.assertAll() ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ์— ๋Œ€ํ•ด์„  AssertJ ์˜ Collect all errors with soft assertions ๋ถ€๋ถ„ ์ฐธ์กฐ
Share on

snack
WRITTEN BY
snack
Web Programmer


What's on this Page