[MSA] 서비스 호출하기

준비물

Product Service

Display Service

Spring Boot

Spring boot에서 서비스 호출하기

클라이언트는 Api gateway를 통해 Display Service만 접근 하는 시나리오이며, Display Service는 내부적으로 Product Service를 호출한다.

1. RestTemplate

RestTemplate에 서비스의 주소를 명시하여 호출하는 방법

@Service
public class ProductService {
    private static final String url = "http://localhost:8081/products/";
    private final RestTemplate restTemplate;

    public ProductService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public String getProductInfo(String productId) {
        return restTemplate.getForObject(url + productId, String.class);
    }
}

2. Hystrix으로 CircuitBreak 적용하기

  1. Hystrix Dependency 추가
    dependencies {
      implementation 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix'
    }
    
  2. @EnableCircuitBreaker 추가
    @EnableCircuitBreaker
    public class DisplayApplication {
    
  3. Fallback method 정의
    @Service
    public class ProductService {
     private static final String url = "http://localhost:8081/products/";
     private final RestTemplate restTemplate;
    
     public ProductService(RestTemplate restTemplate) {
         this.restTemplate = restTemplate;
     }
    
     @HystrixCommand(fallbackMethod = "getProductFallback")
     public String getProductInfo(String productId) {
         return restTemplate.getForObject(url + productId, String.class);
     }
        
     public String getProductFallback(String productId, Throwable throwable) {
         return "[ sold out ]";
     }
    }
    

    FallbackMethod에 Throwable 객체를 함께 넘겨 줄 수 있다. (생략 가능)

Timeout 적용

application.yml에 timeout 적용하기

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000

@HystrixCommand에 command key를 지정해두지 않으면 default가 기본 설정이 된다.

CircuitBreaker 적용

application.yml에 Circuit breaker 를 위한 설정

hystrix:
  command:
    default:
      ...
      circuitBreaker:
        requestVolumeThreshold: 1
        errorThresholdPercentage: 50

3. Ribbon으로 LoadBalance 적용하기

  1. Ribbon Dependency 추가
    dependencies {
      implementation 'org.springframework.cloud:spring-cloud-starter-netflix-ribbon'
    }
    
  2. application.yml에 서비스명과 서버 주소를 추가
    product:
      ribbon:
     listOfServers: localhost:8081, localhost:8082
     MaxAutoRetries: 0
     MaxAutoRetriesNextServer: 1
    

    MaxAutoRetries: 첫 시도 실패시 같은 서버로 재시도 하는 수

    MaxAutoRetriesNextServer : 첫 시도 실패시 다음 서버로 재시도 하는 수

  3. Product 서비스 호출 url 변경
    @Service
    public class ProductService {
     private static final String url = "http://product/products/";
     ...
    
  4. RestTemplate Bean에 @LoadBalanced어노테이션 추가
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
     return new RestTemplate();
    }
    

4. Eureka 추가하기

4.1. Eureka 서버 추가

  1. 새 Spring Boot 앱 추가 후 Eureka Server 의존성 추가
    dependencies {
     implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
    }
    
  2. @EnableEurekaServer 어노테이션 추가
    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaServerApplication {
    ...
    
  3. application.yml에 Eureka 설정
server:
  port: 8761

spring:
  application:
    name: eureka-server

eureka:
  server:
    response-cache-update-interval-ms: 1000 # Default 30,000ms
    enableSelfPreservation: false           # Just for demo
  client:
    register-with-eureka: false             # Only for local stand-alone development
    fetch-registry: false                   # Only for local stand-alone development
    service-url:
      defaultZone: http://localhost:8761/eureka  # Default Value. Just for demo
  instance:
    prefer-ip-address: true                 # Use ip address instead of hostname from OS when reporting myself to eureka server

4.2. Product, Display서비스에 Eureka Client 추가

  1. Eureka Client 의존성 추가
    dependencies {
     implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    }
    
  2. application.yml에 Eureka 설정 추가
    eureka:
      instance:
     prefer-ip-address: true
      client:
     service-url:
       defaultZone: http://127.0.0.1:8761/eureka
    
  3. @EnableEurekaClient어노테이션 추가
@EnableEurekaClient
@SpringBootApplication
public class ProductApplication {
    ...

4.3. Display 서비스에 listOfSevers 제거

product:
  ribbon:
    MaxAutoRetries: 0
    MaxAutoRetriesNextServer: 1

5. Spring Cloud Feign 사용하기

  1. Spring Cloud Feign 의존성 추가
    dependencies {
     implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    }
    
  2. @EnableFeignClients 어노테이션 추가
    @EnableFeignClients
    public class DisplayApplication {
    
  3. FeignClient 인터페이스 생성
    @FeignClient(name = "product", url = "http://localhost:8081/")
    public interface FeignProductService {
     @RequestMapping(path = "/products/{productId}")
     String getProductInfo(@PathVariable("productId") String productId);
    }
    

    Eureka와 Ribbon을 사용 할 경우 url을 제거할 수 있다.

  4. Bean 주입 후 사용하기
    private final FeignProductService feignProductService;
    ...
    private String getProductInfo(String productId) {
         return feignProductService.getProductInfo(productId);
    }
    

5.1. Feign Client에 Hystrix 적용하기

  1. application.yml에 Feign Client에서 Hystrix설정을 켜기
    feign:
      hystrix:
     enabled: true
    
  2. FeignClient인터페이스를 구현한 클래스에 Fallback 메서드를 정의
    @Component
    public class FeignProductServiceFallbackImpl implements FeignProductService {
     @Override
     public String getProductInfo(String productId) {
         return "sold out";
     }
    }
    
  3. FeinClient인터페이스에 Fallback 메서드 설정
    @FeignClient(name = "product", fallback = FeignProductServiceFallbackImpl.class)
    public interface FeignProductService {
     @RequestMapping(path = "/products/{productId}")
     String getProductInfo(@PathVariable("productId") String productId);
    }
    
  4. application.yml에 Hystrix 값 설정하기
hystrix:
  command:
    FeignProductService#getProductInfo(String):
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000
      circuitBreaker:
        requestVolumeThreshold: 1
        errorThresholdPercentage: 50

6. Gateway 추가하기

6.1. Zuul 추가

  1. 새 Spring Boot 앱 추가 후 Eureka Client, Zuul 의존성 추가
    dependencies {
     implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
     implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    }
    
  2. @EnableZuulProxy, @EnableDiscoveryClient 어노테이션 추가
    @EnableZuulProxy
    @EnableDiscoveryClient
    @SpringBootApplication
    public class ZuulApplication {
    ...
    
  3. application.yml에 Zuul 설정
spring:
  application:
    name: zuul

server:
  port: 9000

zuul:
  routes:
    product:
      path: /products/**
      serviceId: product
      stripPrefix: false
    display:
      path: /displays/**
      serviceId: display
      stripPrefix: false

eureka:
  instance:
    non-secure-port: ${server.port}
    prefer-ip-address: true
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

6.2. Gateway에 Ribbon 로드밸런싱 추가

  1. application.yml에 ribbon 설정
product:
  ribbon:
    MaxAutoRetriesNextServer: 1
    ReadTimeout: 3000
    ConnectTimeout: 1000
    MaxTotalConnections: 300
    MaxConnectionsPerHost: 100

display:
  ribbon:
    MaxAutoRetriesNextServer: 1
    ReadTimeout: 3000
    ConnectTimeout: 1000
    MaxTotalConnections: 300
    MaxConnectionsPerHost: 100