๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Spring

Feign ํด๋ผ์ด์–ธํŠธ ๊ตฌํ˜„๊ณผ Eureka, Hystrix ์ ์šฉ

by Leica 2021. 8. 18.
๋ฐ˜์‘ํ˜•

 

1. Dependency

ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ์— spring-cloud-starter-openfeign๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

 

2. Feign client ํ™œ์„ฑํ™”

@EnableFeignClients ์• ๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ feign client๋ฅผ ํ™œ์„ฑํ™”ํ•œ๋‹ค.

@SpringBootApplication ๋˜๋Š” @Configuration ํด๋ž˜์Šค์— ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.

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

 

3. Feign ์ธํ„ฐํŽ˜์ด์Šค ๊ฐœ๋ฐœ

Feign ํด๋ผ์ด์–ธํŠธ๋Š” ์ธํ„ฐํŽ˜์ด์Šค์— @FeignClient๋ฅผ ๋ถ™์ด๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

๋‹ค์Œ์€ feign ํด๋ผ์ด์–ธํŠธ์˜ ์˜ˆ์ด๋‹ค.

 

@FeignClient(name = "coupon", url = "${api-url}")
public interface CouponClient {

    @GetMapping("/coupon/v1/coupons/infos")
    List<Coupon> infos(@RequestParam("couponNos") List<Long> couponNos);

    @GetMapping("/coupon/v1/issue/status/{memberNo}")
    CouponIssueStatusInfo issueStatusInfo(@PathVariable(value = "memberNo") String memberNo,
                                          @RequestParam(value = "couponNo") Long couponNo);

    @GetMapping("/coupon/v1/issued/status/{memberNo}")
    List<CouponIssueInfo> issueInfos(@PathVariable("memberNo") String memberNo,
                                     @RequestParam("couponNos") List<Long> couponNos);
}

Spring mvc์˜ handler mapping ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด์„œ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ •์˜ํ•œ๋‹ค.

 

์‚ฌ์šฉ๋œ @FeignClient ์†์„ฑ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž.

 

name

name์€ ํ•„์ˆ˜ ์†์„ฑ์œผ๋กœ ์„œ๋น„์Šค ID ๋˜๋Š” ์„œ๋น„์Šค์˜ ๋…ผ๋ฆฌ์ ์ธ ์ด๋ฆ„์ด๋‹ค. ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ์—์„œ ์‚ฌ์šฉ๋œ๋‹ค.

 

url

url์€ feign client๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœํ•  url์ด๋‹ค. eureka, ribbon, hystrix๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๋•Œ ์„ค์ •ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด http://api.atoz-develop.com:8765 ์™€ ๊ฐ™์ด ์„ค์ •ํ•œ๋‹ค. profile ๋ณ„๋กœ ๋‹ค๋ฅธ url์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์ด ์„ค์ •์œผ๋กœ ๋นผ๋ฉด ๋œ๋‹ค.

 

4. Feign Client ์‚ฌ์šฉ

์ž‘์„ฑํ•œ feign client๋Š” ๋นˆ์„ ์ฃผ์ž…๋ฐ›์•„์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@Slf4j
@RequiredArgsConstructor
@Service
public class DefaultInquireCouponService implements InquireCouponService {

    private final CouponClient couponClient;

    @Override
    public List<Coupon> getCouponInfos(String couponNumbers) {

        try {
            List<Coupon> couponInfoList = couponClient.infos(couponNos);
            ...
        } catch (Exception ex) {
            log.error("##getCouponInfos ERR:", ex);
        }

        return couponInfos;
    }
}

 

5. Eureka(์œ ๋ ˆ์นด) ์ ์šฉ

์œ„์—์„œ๋Š” @FeignClient์˜ url ์†์„ฑ์— ํ˜ธ์ถœํ•  ๋Œ€์ƒ์„ ์ง์ ‘ ์ง€์ •ํ–ˆ๋‹ค.

์ด๋ฒˆ์—๋Š” eureka๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ๋ฅผ ์ ์šฉํ•ด๋ณด์ž.

์œ ๋ ˆ์นด ์„œ๋ฒ„๋Š” ์ค€๋น„๋œ ๊ฒƒ์œผ๋กœ ๊ฐ€์ •ํ•œ๋‹ค.

 

dependency

eureka client ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'

 

Eureka Client ํ™œ์„ฑํ™”

@EnableEurekaClient๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ eureka client๋ฅผ ํ™œ์„ฑํ™”ํ•œ๋‹ค.

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

 

์„ค์ •

๋””์Šค์ปค๋ฒ„๋ฆฌ ์„ค์ •์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

eureka.client.serviceUrl.defaultZone์— ์œ ๋ ˆ์นด ์„œ๋ฒ„ url์„ ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค.

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

 

์ถ”๊ฐ€๋กœ ์œ ๋ ˆ์นด ์„œ๋ฒ„์— ํ˜„์žฌ ํด๋ผ์ด์–ธํŠธ ์„œ๋น„์Šค๋ฅผ ๋“ฑ๋กํ•˜์ง€ ์•Š์œผ๋ ค๋ฉด register-with-eureka๋ฅผ false๋กœ ์„ค์ •ํ•œ๋‹ค.

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://localhost:8761/eureka/

 

Feign Client ์ˆ˜์ •

์ด์ œ ์ž‘์„ฑํ•œ feign client์—์„œ @FeignClient์˜ url ์†์„ฑ์„ ์ œ๊ฑฐํ•œ๋‹ค.

@FeignClient(name = "coupon")
public interface CouponClient {

    ...
}

 

์„œ๋น„์Šค ๊ธฐ๋™ ํ›„ ์‘๋‹ต์ด ์ •์ƒ์ ์œผ๋กœ ์˜ค๋ฉด ์„œ๋น„์Šค ๋””์Šค์ปค๋ฒ„๋ฆฌ๊ฐ€ ์ž˜ ์ ์šฉ๋œ ๊ฒƒ์ด๋‹ค.

 

์ถ”๊ฐ€๋กœ feign client๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด timeout ๋ฐ ๋กœ๊ทธ ๋ ˆ๋ฒจ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

feign:
  client:
    config:
      default:
        loggerLevel: BASIC
        connectTimeout: 20000
        readTimeout: 20000

connectTimeout์€ ์„œ๋น„์Šค ์ ‘์† ํƒ€์ž„์•„์›ƒ, readTimeout์€ HTTP ์—ฐ๊ฒฐ ์ฝ๊ธฐ ํƒ€์ž„์•„์›ƒ ์„ค์ •์ด๋‹ค.

default ๋Œ€์‹  ํŠน์ • ์„œ๋น„์Šค์— ๋Œ€ํ•ด ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

6. hystrix ์ ์šฉ

์ตœ์‹  ๋ฒ„์ „์˜ ์Šคํ”„๋ง ํด๋ผ์šฐ๋“œ์—์„œ๋Š” feign์˜ hystrix ํ™œ์„ฑํ™”๊ฐ€ disable ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ™œ์„ฑํ™”ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

feign:
  hystrix:
    enabled: true

 

feign client์—์„œ ๋Œ€์ƒ ์„œ๋น„์Šค๋กœ์˜ ์š”์ฒญ ์‹คํŒจ๊ฐ€ ์ž„๊ณ„์น˜๋ฅผ ๋„˜์œผ๋ฉด ๋Œ€์‹  ์‘๋‹ตํ•  FallBackFactory ํด๋ž˜์Šค๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฐธ๊ณ ๋กœ ์ด๋Ÿฌํ•œ ๋งค์ปค๋‹ˆ์ฆ˜์„ Circuit breaker pattern์ด๋ผ๊ณ  ํ•˜๋ฉฐ ์„œํ‚ท์ด open์ด๋ฉด ์˜ค๋ฅ˜ ์ƒํƒœ, close์ด๋ฉด ์ •์ƒ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด FallbackFactory๋ฅผ implementsํ•˜์—ฌ ๊ตฌํ˜„ํ•œ๋‹ค.

@Slf4j
@Component
public class CouponClientFallbackFactory implements FallbackFactory<CouponClient> {

    @Override
    public CouponClient create(Throwable cause) {

        return new CouponClient() {

            @Override
            public List<Coupon> infos(List<Long> couponNos) {
                log.error("couponNos: {}", couponNos, cause);
                return Collections.emptyList();
            }

            @Override
            public CouponIssueStatusInfo issueStatusInfo(String memberNo, Long couponNo) {
                log.error("memberNo: {}, couponNo: {}", memberNo, couponNo, cause);
                return null;
            }

            @Override
            public List<CouponIssueInfo> issueInfos(String memberNo, List<Long> couponNos) {
                log.error("memberNo: {}, couponNos: {}", memberNo, couponNos, cause);
                return Collections.emptyList();
            }
        };
    }
}

 

๋‹ค์Œ๊ณผ ๊ฐ™์ด @FeignClient์— fallbackFactory ์†์„ฑ๊ฐ’์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

@FeignClient(name = "coupon", fallbackFactory = CouponClientFallbackFactory.class)
public interface CouponClient {

    ...
}

 

6-1. Hystrix ์„ค์ •

Hystrix๋ฅผ java bean์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฐธ๊ณ ๋กœ spring-cloud-netflix-hystrix 2.2.6.RELEASE ๊ธฐ์ค€์ด๋‹ค.

@Bean
public Customizer<HystrixCircuitBreakerFactory> defaultConfig() {
    return factory -> factory.configureDefault(id -> HystrixCommand.Setter
            .withGroupKey(HystrixCommandGroupKey.Factory.asKey(id))
            .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                    .withExecutionTimeoutInMilliseconds(20000)
                    .withCircuitBreakerRequestVolumeThreshold(20)
                    .withCircuitBreakerErrorThresholdPercentage(50)
                    .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
            .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
                    .withCoreSize(50)
                    .withMaximumSize(300)
                    .withAllowMaximumSizeToDivergeFromCoreSize(true))
    );
}

๊ณต์‹ reference: https://docs.spring.io/spring-cloud-netflix/docs/2.2.6.RELEASE/reference/html/#configuring-hystrix-circuit-breakers

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€