관리 메뉴

IT.FARMER

spring open feign retry (circuit breaker) 본문

Spring/Spring Cloud

spring open feign retry (circuit breaker)

아이티.파머 2022. 1. 14. 11:41
반응형

2022.01.14 - [Spring/Spring Cloud] - Spring Open Feign

2022.01.14 - [Spring/Spring Cloud] - open feign log 설정

2022.01.14 - [Spring/Spring Cloud] - spring open feign 예제

2022.01.14 - [Spring/Spring Cloud] - open feign error decoder custom

2022.01.14 - [Spring/Spring Cloud] - spring open feign retry (circuit breaker)

 

Open Feign circuit breaker 테스트 시나리오는 다음과 같다. (fallback & retry) 기능 수행

 

 

전제 조건

  • service client api 와 product-service-api 는 eureka server에 등록되어 있다. → discovery pattern을 사용하지 않고 @FeginClient(url=”{host}”)를 사용하는경우엔 eureka server가 빠져도 된다.
  1. client는 service-client-api 로 요청을 한다.
  2. service-client-api 는 product-service-api 에게 상품정보 목록을 요청한다.
  3. 어떤이유로 인해 product-service-api는 타임아웃 혹은 서버오류로 인해 HTTP Status 코드가 정상(200~300)이 아닌 다른 코드를 반환한다.
  4. service-client-api 는 circuit breaker 정책에 의해 최대 5번의 재호출후(재호출 모두실패) client 에게 fallback 메세지를 전달한다.

build.gradle

plugins {
    id 'org.springframework.boot' version '2.6.2'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

ext {
    set('springCloudVersion', "2021.0.0")
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

test {
    useJUnitPlatform()
}

service-client-api module

spring boot application

@SpringBootApplication
@EnableFeignClients
public class ClientApplication {
    public static void main(String[] args) throws Exception{
        SpringApplication.run(ClientApplication.class, args);
    }
}
  • @EnableFeignClients 를 선언하여, 페인 클라이언트를 활성화 시킨다.

Custom FeignConfig

@Configuration
public class GlobalCustomFeignConfig {    
    @Bean
    public Retryer retryer(){
        return new Retryer.Default();
    }
}
  • Retry.Default() : this(100, *SECONDS*.toMillis(1), 5); 기본 밀리세컨드 단위로 되어 있다.
    • period, ducation, max attempts 로 되어 있으며, 기본 5회 호출 대기시간 100 * 1 mil sec 로 구성되어 있다.

ClientController.

@Slf4j
@Controller
@AllArgsConstructor
public class ClientController {

    private final FallbackClient fallbackClient;

    @RequestMapping(method = RequestMethod.GET, value = "/client/feign/fallback/lazy/getAll")
    public @ResponseBody
    String fallbackClientGetAll() {
        String responseStr = fallbackClient.productLazyGetAll();
        log.debug("responseStr = {}", responseStr);
        return responseStr;
    }

    @RequestMapping(method = RequestMethod.GET, value = "/client/feign/test/eureka/findone")
    public @ResponseBody
    String fallbackClientNone() {
        String responseStr = fallbackClient.product();
        log.debug("responseStr = {}", responseStr);
        return responseStr;
    }

}

Feign FallbackClient

@FeignClient(
        name="MOS-SERVICE-PRODUCT"
        ,fallback =FallbackClient.Fallback.class
        ,decode404 = true
)
public interface FallbackClient {

    @RequestMapping("/product/lazy/get_all")
    String productLazyGetAll();

    @RequestMapping("/external/product")
    String product();

    @Component
    public class Fallback implements FallbackClient{

        @Override
        public String productLazyGetAll() {
            return "fixed response getall";

        }

        @Override
        public String none() {
            return "fixed response none";
        }
    }
}

@FeignClient를 선언하고, 필요한 속성 값들을 입력한다.

  • 여기서 fallback 값은 FallBackClient를 구현한한다. @Compoment로 Bean 등록을 꼭 해준다.
  • name 은 필수 값으로써 페인 클라이언트의 이름 혹은 eureka 를 사용 하고 있다면 eureka 에서 discovery된 이름을 적어준다.
  • url 예제에서는 없지만 eureka를 사용하지 않고 직접 url을 입력해서사용할때 디폴트 기본 도메인이 된다.

application.yml

server:
  port: ${PORT:80}
spring:
  application:
    name: mos-server-client
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka

feign:
  circuitbreaker:
    enabled: true
  autoconfiguration:
    jackson:
      enabled: true
  client:
    config:
      default:
        errorDecoder: com.client.config.GlobalCustomErrorDecoder
        connectionTimeout: 3000
        readTimeout: 3000
    default-to-properties: false
  • feign.circuitbreaker.enable:true 기본 false 임으로 true 로 변경해 주어야 한다.
  • connectionTimeout : feigin client 가 맺는 타임아웃 (milisecond)
  • readTimeout : feigin client 읽는 시간에 대한 타임 아웃 (milisecond)
  •  

service-product-api module

  • 상품 서비스 모듈
@Slf4j
@Component
public class ProductRouters {

    @Bean
    public RouterFunction<ServerResponse> routerFunction(ProductHandler productHandler) {
        return
                RouterFunctionsroute(GET("/product/lazy/get_all")
                                .and(RequestPredicates.accept(MediaType.ALL)), request -> {

                            try {
                                // 테스트를 위한 지연시간 4 초 , 게이트웨이 설정 시간 3초
                                TimeUnit.SECONDS.sleep(4);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }

                            return ServerResponse.ok().body(Mono.just("hello user get all"), String.class);
                        }).and(
                                .route(GET("/external/product"), request -> {
                                    try {
                                        String productId = request.queryParam("productId").orElseThrow(IllegalArgumentException::new);
                                        APIResponse apiResponse = new APIResponse("SUCCESS", "data", "message");
                                        return ServerResponse.ok()
                                                .contentType(MediaType.APPLICATION_JSON)
                                                .body(
                                                Mono.just(apiResponse), APIResponse.class
                                        );
                                    } catch (Exception e) {
                                        log.error("product find error",e);
                                        return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
                                                Mono.just(new APIResponse("FAIL", HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e.getMessage())), APIResponse.class
                                        );
                                    }
                                }))

                ;
    }

}
  • /product/lazy/get_all : readTimeout
    • 응답 시간이 3초 임으로 , 응답 지연을 4초로 셋팅하여 fallback 이 동작되는것을 확인
  • /external/product : Exception
    • productId 값이 파리미터 값으로 없으면 ServerResponse status 값을 INTERAL_SERVER_ERROR 로 설정 한다.

Logs

open feign client 에서 ‘/external/product’ 를 호출 하였을때 productId 값을 보내지 않으면 server error 가 발생하고 retry 정책에 의해 5번을 호출하고 fallback 이 실행된 것을 확인 할 수 있다.

2022-01-10 17:44:56.555 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignDefaultClient       : [ProductApiFeignDefaultClient#findOne] ---> GET http://localhost:8083/external/product HTTP/1.1
2022-01-10 17:44:56.747 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignDefaultClient       : [ProductApiFeignDefaultClient#findOne] <--- HTTP/1.1 500 Internal Server Error (191ms)
2022-01-10 17:44:56.747  WARN 80320 --- [pool-1-thread-2] c.c.config.GlobalCustomErrorDecoder      : global error code 500
2022-01-10 17:45:11.939 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignEurekaClient        : [ProductApiFeignEurekaClient#findOne] ---> GET http://MOS-SERVICE-PRODUCT/external/product HTTP/1.1
2022-01-10 17:45:11.955 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignEurekaClient        : [ProductApiFeignEurekaClient#findOne] <--- HTTP/1.1 500 Internal Server Error (16ms)
2022-01-10 17:45:11.955  WARN 80320 --- [pool-1-thread-2] com.client.config.ProductErrorDecoder    : product error code 500
2022-01-10 17:45:12.112 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignEurekaClient        : [ProductApiFeignEurekaClient#findOne] ---> RETRYING
2022-01-10 17:45:12.112 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignEurekaClient        : [ProductApiFeignEurekaClient#findOne] ---> GET http://MOS-SERVICE-PRODUCT/external/product HTTP/1.1
2022-01-10 17:45:12.118 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignEurekaClient        : [ProductApiFeignEurekaClient#findOne] <--- HTTP/1.1 500 Internal Server Error (5ms)
2022-01-10 17:45:12.118  WARN 80320 --- [pool-1-thread-2] com.client.config.ProductErrorDecoder    : product error code 500
2022-01-10 17:45:12.345 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignEurekaClient        : [ProductApiFeignEurekaClient#findOne] ---> RETRYING
2022-01-10 17:45:12.346 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignEurekaClient        : [ProductApiFeignEurekaClient#findOne] ---> GET http://MOS-SERVICE-PRODUCT/external/product HTTP/1.1
2022-01-10 17:45:12.352 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignEurekaClient        : [ProductApiFeignEurekaClient#findOne] <--- HTTP/1.1 500 Internal Server Error (6ms)
2022-01-10 17:45:12.352  WARN 80320 --- [pool-1-thread-2] com.client.config.ProductErrorDecoder    : product error code 500
2022-01-10 17:45:12.691 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignEurekaClient        : [ProductApiFeignEurekaClient#findOne] ---> RETRYING
2022-01-10 17:45:12.691 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignEurekaClient        : [ProductApiFeignEurekaClient#findOne] ---> GET http://MOS-SERVICE-PRODUCT/external/product HTTP/1.1
2022-01-10 17:45:12.695 DEBUG 80320 --- [pool-1-thread-2] c.c.e.ProductApiFeignEurekaClient        : [ProductApiFeignEurekaClient#findOne] <--- HTTP/1.1 500 Internal Server Error (4ms)
2022-01-10 17:45:12.696  WARN 80320 --- [pool-1-thread-2] com.client.config.ProductErrorDecoder    : product error code 500

 

반응형

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

Feign Client with Spring Boot: RequestParam.value() was empty on parameter 0  (0) 2022.11.02
open feign error decoder custom  (0) 2022.01.14
spring open feign 예제  (0) 2022.01.14
open feign log 설정  (0) 2022.01.14
Spring Open Feign  (0) 2022.01.14