Feign 이란,
Interface 선언을 통해 자동으로 Http Client 를 생성할 수 있다.
RestTemplate 는 concreate 클래스라 테스트 하기 어렵다.
관심사의 분리
- 서비스의 관심 - 다른 리소스, 외부 서비스 호출과 리턴값
- 관심 X - 어떤 URL 인지, 어떻게 파싱할 것인지 (jackson을 쓸 것인지....)
Spring Cloud 에서 Open-Feign 기반으로 Wrapping 한 것이 Spring Cloud Feign 이다.
Feign을 이용하면, 인터페이스 선언 만으로 Http Client 구현물을 만들어 준다.
build.gradle
compile('org.springframework.cloud:spring-cloud-starter-openfeign') // To use Feign
DisplayApplication.java
@EnableFeignClients 추가.
@SpringBootApplication
@EnableCircuitBreaker
@EnableEurekaClient
@EnableFeignClients
public class DisplayApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(DisplayApplication.class);
}
}
FeignProductRemoteService.java
@FeignClient(name = "product", url = "http://localhost:8082/")
public interface FeignProductRemoteService {
@RequestMapping(path = "/products/{productId}")
String getProductInfo(@PathVariable("productId") String productId);
}
@FeignClient 를 인터페이스에 명시하고, 각 API 에는 Spring MVC 에서 제공하는 Annotation 을 지원해주기 때문에 이를 이용해서 정의한다.
@FeignClient의 name : service id
url: 정의된 url만 사용하는 순수 feign client 로서만 동작
DisplayController.java
API를 호출하는 쪽에서는 ProductRemoteService 가 아닌 FeignProductRemoteService 를 사용하도록
@RestController
@RequestMapping(path = "/displays")
public class DisplayController {
// private final ProductRemoteService productRemoteService;
private final FeignProductRemoteService feignProductRemoteService;
public DisplayController(final FeignProductRemoteService feignProductRemoteService) {
this.feignProductRemoteService = feignProductRemoteService;
}
@GetMapping(path = "/{displayId}")
public String getDisplayDetail(@PathVariable String displayId) {
String productInfo = getProductInfo();
return String.format("[display id = %s at %s %s ]", displayId, System.currentTimeMillis(), productInfo);
}
private String getProductInfo() {
return feignProductRemoteService.getProductInfo("12345");
}
}
url 만 제거하면 ribbon과 eureka와 hystrix 를 모두 이용하도록 된다.
즉, eureka에서 product 서버 목록을 조회해서 ribbon 을 통해 라운드 로빈 방식의 load-balancing 하며 Http 호출을 수행
FeignProductRemoteService.java
@FeignClient(name = "product")
public interface FeignProductRemoteService {
@RequestMapping(path = "/products/{productId}")
String getProductInfo(@PathVariable("productId") String productId);
}
application.yml
feign을 통해 호출되는 모든 메소드 하나하나가 hystrix command 로서 호출되도록 설정하기
feign:
hystrix:
enabled: true
FeignProductRemoteServiceFallbackImpl.java
hystrix fallback을 이용하기 위해서는 feign으로 정의한 인터페이스를 직접 구현하고 ( implements FeignProductRemoteService ) Spring Bean ( @Component ) 으로 선언한다.
@Component
public class FeignProductRemoteServiceFallbackImpl implements FeignProductRemoteService {
@Override
public String getProductInfo(String productId) {
return "[ this product is sold out ]";
}
}
FeignProductRemoteService.java
그리고, Feign 인터페이스에 Fallback 클래스를 @FeignClient 선언시 명시 ( fallback = FeignProductRemoteServiceFallbackImpl.class ) 한다.
@FeignClient(name = "product", fallback = FeignProductRemoteServiceFallbackImpl.class)
public interface FeignProductRemoteService {
@RequestMapping(path = "/products/{productId}")
String getProductInfo(@PathVariable("productId") String productId);
}
하지만, 이는 어떤 exception이 발생했는 지 확인이 불가능하다는 단점이 있다.
-> 이는 FallbackFactory 를 만들어 해결할 수 있다.
FeignProductRemoteServiceFallbackFactory.java
FallbackFactory 인터페이스의 구현체를 정의하고, 빈으로 등록한다.
@Component
public class FeignProductRemoteServiceFallbackFactory implements FallbackFactory {
@Override
public FeignProductRemoteService create(Throwable cause) {
System.out.println("t = " + cause);
return productId -> "[ this product is sold out ]";
}
}
FeignProductRemoteService.java
그리고, Feign 인터페이스에 Fallback 클래스를 @FeignClient 선언시 FallbackFactory를 명시한다.
@FeignClient(name = "product", fallbackFactory = FeignProductRemoteServiceFallbackFactory.class)
public interface FeignProductRemoteService {
@RequestMapping(path = "/products/{productId}")
String getProductInfo(@PathVariable("productId") String productId);
}
Feign 은 인터페이스 선언으로 configuration 파일을 통해 다양한 커스터마이징이 가능하다
- Http Client (default apache)
- Eureka 를 통한 타깃 서버 주소 획득
- Ribbon을 이용한 Client-Side Load Balancing
- Hystrix 를 통한 메소드 별 Circuit Breaker
등 모두 가능!!
발생 가능한 장애 유형에 대한 Feign 사용 시 동작 로직
예> 특정 API 서버의 인스턴스가 한개 다운된 경우
EUREKA - HeartBeat 송신이 중단 됨으로 일정 시간 후, 유레카 서버 리스트에서 삭제
RIBBON - IOException 이 발생한 경우 다른 인스턴스로 Retry 시켜 줌 ( 단, 30초 간격으로 검사하기 때문에 30 초 동안은 지연이 있음 )
HISTRIX - Circuit 은 오픈되지 않음 ( retry 를 통해 통신이 성공했기 때문 ), 그러나, Fallback, Timeout 은 동작
예> 특정 서버 한 대의 API만 비정상 동작하는 경우 ( 지연, 에러, .. )
HISTRIX - ( Retry 하지 않고 ) 해당 API 를 호출하는 Circuit Breaker 오픈, Fallback, Timeout 은 동작
'Spring > Spring Cloud' 카테고리의 다른 글
Zuul - API Gateway (0) | 2021.01.17 |
---|---|
Eureka (0) | 2021.01.17 |
Hystrix & Ribbon (0) | 2021.01.17 |