프로젝트 의존성에 spring-cloud-starter-openfeign를 추가한다.
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
@EnableFeignClients 애노테이션을 추가하여 feign client를 활성화한다.
@SpringBootApplication 또는 @Configuration 클래스에 추가하면 된다.
@EnableFeignClients
@SpringBootApplication
publicclassApplication {
publicstaticvoidmain(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Feign 클라이언트는 인터페이스에 @FeignClient를 붙이는 것만으로 만들 수 있다.
다음은 feign 클라이언트의 예이다.
@FeignClient(name = "coupon", url = "${api-url}")
publicinterfaceCouponClient {
@GetMapping("/coupon/v1/coupons/infos")
List<Coupon>infos(@RequestParam("couponNos") List<Long> couponNos);
@GetMapping("/coupon/v1/issue/status/{memberNo}")
CouponIssueStatusInfoissueStatusInfo(@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은 필수 속성으로 서비스 ID 또는 서비스의 논리적인 이름이다. 서비스 디스커버리에서 사용된다.
url은 feign client를 통해 호출할 url이다. eureka, ribbon, hystrix를 사용하지 않을 때 설정한다. 예를 들어 http://api.atoz-develop.com:8765 와 같이 설정한다. profile 별로 다른 url을 사용하려면 위 코드와 같이 설정으로 빼면 된다.
작성한 feign client는 빈을 주입받아서 사용할 수 있다.
@Slf4j
@RequiredArgsConstructor
@Service
publicclassDefaultInquireCouponServiceimplementsInquireCouponService {
privatefinal 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;
}
}
위에서는 @FeignClient의 url 속성에 호출할 대상을 직접 지정했다.
이번에는 eureka를 사용해서 서비스 디스커버리를 적용해보자.
유레카 서버는 준비된 것으로 가정한다.
eureka client 의존성을 추가한다.
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
@EnableEurekaClient를 추가하여 eureka client를 활성화한다.
@EnableFeignClients
@EnableEurekaClient
@SpringBootApplication
publicclassApplication {
publicstaticvoidmain(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에서 @FeignClient의 url 속성을 제거한다.
@FeignClient(name = "coupon")
publicinterfaceCouponClient {
...
}
서비스 기동 후 응답이 정상적으로 오면 서비스 디스커버리가 잘 적용된 것이다.
추가로 feign client는 다음과 같이 timeout 및 로그 레벨을 설정할 수 있다.
feign:
client:
config:
default:
loggerLevel: BASIC
connectTimeout: 20000
readTimeout: 20000
connectTimeout은 서비스 접속 타임아웃, readTimeout은 HTTP 연결 읽기 타임아웃 설정이다.
default 대신 특정 서비스에 대해 설정할 수도 있다.
최신 버전의 스프링 클라우드에서는 feign의 hystrix 활성화가 disable 되어있기 때문에 활성화하기 위해 다음 설정을 추가해야 한다.
feign:
hystrix:
enabled: true
feign client에서 대상 서비스로의 요청 실패가 임계치를 넘으면 대신 응답할 FallBackFactory 클래스를 설정할 수 있다. 참고로 이러한 매커니즘을 Circuit breaker pattern이라고 하며 서킷이 open이면 오류 상태, close이면 정상 상태를 나타낸다.
아래와 같이 FallbackFactory를 implements하여 구현한다.
@Slf4j
@Component
publicclassCouponClientFallbackFactoryimplementsFallbackFactory<CouponClient> {
@Override
public CouponClientcreate(Throwable cause) {
returnnewCouponClient() {
@Override
public List<Coupon>infos(List<Long> couponNos) {
log.error("couponNos: {}", couponNos, cause);
return Collections.emptyList();
}
@Override
public CouponIssueStatusInfoissueStatusInfo(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)
publicinterfaceCouponClient {
...
}
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