본문 바로가기

Spring/Spring Cloud

Feign

반응형

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 Cloud 기반 MSA로의 전환

 

 

 

반응형

'Spring > Spring Cloud' 카테고리의 다른 글

Zuul - API Gateway  (0) 2021.01.17
Eureka  (0) 2021.01.17
Hystrix & Ribbon  (0) 2021.01.17