openfeign 소개와 운영 서버 적용기
Monolithic Architecture에서 MSA로 전환 시 다수의 WAS 간 통신이 복잡해지면 코드 가독성이 저하됩니다. 이를 해결하기 위해 feign을 사용하면 선언적 웹 서비스 클라이언트로 코드 가독성을 개선할 수 있습니다. Feign 설정은 Configuration 클래스나 yml을 통해 가능하며, Client-side Loadbalancing, Retry, Timeout, Request Header 설정을 손쉽게 관리할 수 있습니다. eureka와 resilience4j를 연동해 서비스 등록과 서킷 브레이커 기능을 구현할 수 있습니다. 이 글에서는 feign의 기본 사용법, 구성, 주의사항, 연동 방법 등을 다룹니다.
소개
많은 서비스들이 Monolithic Architecture에서 MSA로 전환함에 따라, 다수의 WAS 간 통신이 빈번해졌습니다. 이러한 통신의 증가로 인해 기존의 RestTemplate과 같은 방법을 사용하여 다양한 요청에 대한 설정을 관리하는 것이 점점 더 복잡해졌고 이로 인해 코드의 가독성도 현저히 저하되곤 합니다.
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", "application/json");
HttpEntity<String> entity = new HttpEntity<>(headers);
try {
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
return response.getBody();
} catch (HttpClientErrorException e) {
// Client-side error (4xx)
System.out.println("Client error: " + e.getStatusCode());
return null;
} catch (HttpServerErrorException e) {
// Server-side error (5xx)
System.out.println("Server error: " + e.getStatusCode());
return null;
} catch (Exception e) {
// General error
System.out.println("Error: " + e.getMessage());
return null;
}
이러한 코드가 서로 다른 Request 마다 작성해야한다면..?
이러한 문제점을 해결하기 위해 feign이 등장합니다. feign의 특징은 아래와 같습니다.
feign은 선언적 웹 서비스 클라이언트
인터페이스 생성하고 어노테이션달고 메서드와 파라미터 지정하는 것으로 사용 가능
선언만 하면 되어서 러닝 커브가 낮음
Spring Cloud Loadbalancer
기반으로Client-side Loadbalancing
가능
의존성
dependencies {
...
implementation(platform('org.springframework.cloud:spring-cloud-dependencies:2022.0.4'))
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
...
}
기본 사용
간단한 예제
@SpringBootApplication
@EnableFeignClients
public class MyTestApiApplication {
public static void main(String[] args) {
SpringApplication.run(SsgSearchRecommendApiApplication.class, args);
}
}
- SpringBootApplication에
@EnableFeignClients
등록
@FeignClient(value = "test", url = "ssg.com")
public interface ForTechPediaApiClient {
@GetMapping(value = "/")
String call();
@GetMapping(value = "/aggregate/inventory/front/user/items/{itemIds}")
@CollectionFormat(feign.CollectionFormat.CSV)
List<InventoryResult> inventoryItems(@PathVariable List<String> itemIds, @SpringQueryMap UserParameter userParameter);
}
@GetMapping
,@PostMapping
,@PutMapping
,@DeleteMapping
등 spring MVC annotation 사용 가능@PathVariable
을 이용해 path에 값을 넣거나@SpringQueryMap
을 이용해 query string을 넣을 수 있음
@RestController
@RequiredArgsConstructor
public class ForTechPediaApiTestController {
private final ForTechPediaApiClient client;
@PostMapping(value = "/test/techpedia/")
public String test() {
return client.call();
}
@PostMapping(value = "/test/techpedia2/")
public List<InventoryResult> test2(@RequestBody BaseRequest baseRequest) {
return client.inventoryItems(List.of("1000547588070", "1000554084667", "1000555215927"), baseRequest.getUserInfo());
}
}
- 사용하는 쪽에서는 해당 Client 주입받고 그냥 메서드 호출만 하면 끝!
Configuration
구성속성 yml에서 설정하는 방법, Configuration 클래스 생성하는 방법 있음
환경별로 설정이 다르게 하진 않을 것 같아서 보다 명시적인 클래스 생성 방식 택함
각 FeignClient 별로 Configuration을 지정해줘서 상이하게 설정 가능
public class DefaultFeignConfiguration {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
requestTemplate.header("Content-Type", "application/json");
requestTemplate.header("Connection", "Keep-alive");
};
}
@Bean
public Retryer retryer() {
// 1s 대기, 0ms 반복 3번
return new Retryer.Default(0, SECONDS.toMillis(1), 3);
}
@Bean
public Request.Options options() {
return new Request.Options(Duration.ofMillis(350), Duration.ofMillis(550), true);
}
@Bean
public Logger.Level logger() {
return Logger.Level.BASIC;
}
}
@FeignClient(url = "ssg.com", configuration = DefaultFeignConfiguration.class)
public interface ForTechPediaApiClient {...}
아래서 설명하겠지만
@Configuration
이 붙으면 default로 설정
Request Header 설정
Configuration
public class DefaultFeignConfiguration { @Bean public RequestInterceptor requestInterceptor() { return requestTemplate -> { requestTemplate.header("Content-Type", "application/json"); requestTemplate.header("Connection", "Keep-alive"); }; } ... }
Annotation
@FeignClient(url = "ssg.com") public interface ForTechPediaApiClient { @GetMapping(value = "/", headers = "Content-Type=application/json") String call(); }
timeout 설정
public class DefaultFeignConfiguration {
@Bean
public Request.Options options() {
return new Request.Options(Duration.ofMillis(350), Duration.ofMillis(550), true);
}
...
}
첫번째 파라미터: connection timeout
두번째 파라미터: read timeout (socket timeout)
세번째 파라미터: status code 3xx 일때 redirect 여부 (default: true)
retry 설정
public class DefaultFeignConfiguration {
@Bean
public Retryer retryer() {
return new Retryer.Default(0, SECONDS.toMillis(1), 3);
}
...
}
최대 3번 retry
retry interval 0ms 씩 증가
retry interval 최대 1000ms까지 증가
로그 설정
public class DefaultFeignConfiguration {
@Bean
public Logger.Level logger() {
return Logger.Level.BASIC;
}
...
}
NONE
: 로깅 X (DEFAULT)BASIC
: Request Method, URL, HTTP Status code, 실행 시간HEADERS
:BASIC
+ Response HeaderFULL
:BASIC
+HEADERS
+ Body + 메타데이터
주의사항
로그 설정 시 yml 설정
logging.level.<packageName>.<className> = DEBUG
@Configuration
붙이는 경우 전역 설정
기타
Decoder
Bean을 등록해서HttpMessageConverter
에 다른 ObjectMapper를 사용할 수 있음 링크BasicAuthRequestInterceptor
Bean을 등록해 auth 설정 가능@Cacheable
를 feign client 인터페이스의 메서드에 붙여 연동 가능하다고함공식적인 reactive client는 제공하지 않음
refresh 사용으로 되어있으면, connectionTimeout, readTimeout을
POST /actuator/refresh
로 변경 가능
운영 환경에서 활용
eureka discovery 연동
eureka와 연동 시,
@FeignClient
에서 url을 별도로 입력하지 않아도 된다@Feignclient(value = "TEST-API", configuration = QueryApiFeignConfiguration.cLass) public interface TestApiclient { @CircuitBreaker(name ="default", fallbackMethod ="falLback") @PostMapping(value ="/ys/search/target/reconmend_advert_item") SearchResponse searchResultForAdvert(SearchRequest searchRequest); }
사용 예시 | 설명 | |
1 | @FeignClient(name="testClient", url=" http://localhost:8081") or @FeignClient(name="testClient", url="${external-url.testClient}") external-url.testClient=http://localhost:8081 | - 지정한 url로 request |
- 로드밸런싱 X | ||
2 | @FeignClient(name="testClient") spring.cloud .openfeign.client.config.testClient.url= http://localhost:8081 | - 구성속성에 지정된 url로 request |
- 로드밸런싱 X | ||
- refresh-enabled인 경우, POST /actuator/refresh 로 실행 중 변경 가능 | ||
3 | @FeignClient(name="testClient") | - name에 지정된 서비스명을 eureka로 부터 받아와 request |
- client-side 로드밸런싱 O |
주의사항
eureka:
client:
enabled: true
service-url:
defaultZone: <http://11.111.11.111:8761/eureka/>
register-with-eureka: false # false로 하여 로컬이 유레카에 등록되지 않도록 함
healthcheck:
enabled: true
instance:
instanceId: ${spring.application.name}-qa
preferIpAddress: true
eureka enable 켜져있어야하는지 확인 필요
euraka를 통하기 때문에 service-url에서 eureka 값이 지정되어있어야함
로컬 IDE에서 실행할 때,
eureka.client.register-with-eureka: false
resilience4j 연동
fallback
feign의 fallback
resilience4j의 fallback
CircuitBreaker, TimeLimiter 설정은 기본 resilience4j와 동일하게 yml에서 구성설정
메서드 별로 CircuitBreaker, TimeLimiter을 지정하려면
<feignClientClassName>#<calledMethod>(<parameterTypes>)
에 맞춰 구성설정 instance에 지정
circuitbreaker
spring:
cloud:
openfeign:
circuitbreaker:
enabled: true
alphanumeric-ids:
enabled: false
httpclient:
max-connections: 200
max-connections-per-route: 60
time-to-live: 2000
resilience4j:
circuitbreaker:
configs:
default:
slidingWindowSize: 10
permittedNumberOfCallsInHalfOpenState: 1
minimumNumberOfCalls: 5
waitDurationInOpenState: 10s
failureRateThreshold: 50
eventConsumerBufferSize: 10
registerHealthIndicator: true
instances:
PersonalizationApiClient#personalizationItems(String,List):
slidingWindowSize: 50
permittedNumberOfCallsInHalfOpenState: 10
minimumNumberOfCalls: 50
waitDurationInOpenState: 30s
failureRateThreshold: 30
eventConsumerBufferSize: 10
registerHealthIndicator: true
timelimiter:
configs:
default:
timeoutDuration: 5000ms
참고
https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html
https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/
resilience4j
https://mangkyu.tistory.com/289
https://arnoldgalovics.com/spring-cloud-feign-resilience4j-timelimiter/
Subscribe to my newsletter
Read articles from sangminLee directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by