ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 6. [MSA 구현 퀵스타트] HTTP 통신 Ribbon과 Feign 초간단 구현
    Spring Cloud 2021. 4. 1. 23:29

    이번장은 서비스간 HTTP 통신하는 방법에 대해 구현해 보겠습니다.

    지난 장에서 구현하였던 서비스디스커버리 정보를 이용하여 서비스1에서 서비스2의 API를 호출하여 정보를 받아오는 기능입니다.

     

    예제 소스

    이번장에 진행될 소스는 아래 Github에서 다운로드 받으시고, "2.Rest-API-Call" branch를 체크아웃해주세요.

     

    개요

    지난장에서 각 서비스들은 유레카 서버에서 주기적으로 다른 서비스들의 정보를 가져오고 이를 로컬에 캐싱한다고 하였습니다. 그리고 이 정보를 이용하여 서비스 고유명만으로 통신이 가능합니다.

     

    구현할 API는 아주 단순 합니다.

    • 서비스2 : 단순히 ID를 받으면 해당 ID의 이름을 반환합니다. (GET "/name/{id}")
    • 서비스1 : 서비스2의 "/name/{id}"를 호출하고 이름을 전달받아서 결과를 클라이언트로 전달 합니다.

     

    HTTP 통신으로 총 3가지 방식으로 통신을 해볼 것입니다.

    1. 일반 RestTemplate을 이용한 통신
    2. Ribbon을 탑재한 RestTemplate을 이용한 통신
    3. Feign을 이용한 통신
    • 최근에는 RestTemplate 대신 WebClient를 많이 쓰죠? 퀵스타트가 완료 된 이후 WebClient에 Ribbon을 탑재하여 통신하는 과정도 추후 다루어 보겠습니다.

     

    일반 RestTemplate을 이용한 통신

    Ribborn의 도움을 받지 않고 스프링의 RestTemplate을 이용한 서비스간 HTTP 통신을 먼저 구현해 보겠습니다.

     

    RestTemplate은 spring-web에 포함되어 있기에 추가 라이브러리는 필요가 없습니다.

    바로 소스 구현으로 들어가겠습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @Component
    public class RestTemplateClientCommunicator {
     
        @Autowired
        private DiscoveryClient discoveryClient;
     
        public String getName(String id) {
     
            RestTemplate restTemplate = new RestTemplate();
            List<ServiceInstance> instances = discoveryClient.getInstances("service2");
     
            if (instances.size()==0return null;
            /** 인스턴스들 중 0번째 클라이언트에 요청 */
            String serviceUri = String.format("%s/name/%s",instances.get(0).getUri().toString(), id);
        
            ResponseEntity<String> restExchange =
                    restTemplate.exchange(
                            serviceUri,
                            HttpMethod.GET,
                            nullString.class, id);
     
            return  id + " is " + restExchange.getBody();
        }
    }
    cs

    아주 단순하게 구현해보았습니다. RestTemplate 객체를 생성하여 IP와 PORT로 직접적으로 호출하는 구조입니다.

    이 예제에서는 IP와 PORT를 모른다고 가정하고, 지난장에서 다뤄본 DiscoveryClient를 이용하여, 서비스2의 IP와 Port를 얻어 온 뒤 서비스2의 "/name/name/{id}"API를 호출하고 있습니다.

     

    RestTemplate를 직접 이용할 경우 다음의 문제가 있습니다.

    • 호출하는 서비스에서  IP와 PORT를 관리해야 한다. (아니면 위 예제 처럼 DiscoveryClient를 통해 항상 정보를 가져온 뒤 호출해야 함)
    • 클라이언트 측 부하 분산을 할 수 없다. (Ribbon의 다양한 로드밸런싱 설정 사용 불가)

    Controller와 Service를 구현하여 위의 RestTemplateClientCommunicator 호출합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //  DiscoveryController.java
    @RestController
    public class DiscoveryController {
        @GetMapping(value = "/resttemplate/{id}")
        public String resttemplate(@PathVariable("id"String id) {
            return discoveryService.resttemplate(id);
        }
    }
     
    // DiscoveryController.java
    @Service
    public class DiscoveryService {
        @Autowired
        RestTemplateClientCommunicator restTemplateClientCommunicator;
     
        public String resttemplate(String id) {
            log.info("Communicating by RestTemplateClientCommunicator.");
            return restTemplateClientCommunicator.getName(id);
        }
    }
    cs

     

     

    Ribbon을 탑재한 RestTemplate을 이용한 통신

    다음으로는 RestTemplate에 Ribbon을 탑재하여 서버스2의 API를 호출해 보겠습니다.

     

    이번에도 추가 라이브러리는 필요가 없습니다.

    Ribbon은 지난장 추가했던 spring-cloud-starter-netflix-eureka-client에 포함되어 있습니다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @SpringBootApplication
    @EnableDiscoveryClient
    public class Service1Application {
     
        @LoadBalanced
        @Bean
        public RestTemplate getRestTemplate(){
            return new RestTemplate();
        }
        ... 중략 ...
    }
    cs

    우선 @EnableDiscoveryClient 어노테이션을 선언하여 DiscoveryClient를 활성하 합니다.

    이후 RestTemplate을 Bean으로 등록한 뒤 @LoadBalanced 어노테이션을 달아줍니다. 이 설정으로 RestTemplate에Ribbon의 탑재가 완료되었습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Component
    public class RibbonClientCommunicator {

        @Autowired
        RestTemplate restTemplate;
     
        /** 향상된 스프링 RestTemplate을 사용해 리본 기반의 서비스를 호출 */
        public String getName(String id){
     
            ResponseEntity<String> restExchange =
                    /** Url : http://{applicationId}/v1/ ~~ */
                    restTemplate.exchange("http://service2/name/{id}"
                               , HttpMethod.GET, nullString.class, id);
     
            return id + " is " + restExchange.getBody();
        }
    }
    cs

    @Autowired 어노테이션으로 조금전 Bean으로 등록한 RestTemplate을 주입받습니다.

    이번에는 IP:PORT 대신 서비스2의 고유명(service2)으로 호출하면 Ribbon이 내부적으로 IP와 PORT를 찾아서 처리해 줍니다.

    물론 서비스2가 여러개의 인스턴스로 이중화 구성되어 있다면 소프트웨어적 로드밸런싱 처리도 해줍니다.

    추가 설정을 하지않아 기본 설정인 Round Robbin 알고리즘으로 순차 로드밸런싱을 해줍니다.

    • Ribbon의 추가 설정은 퀵스타트 과정 이후에 자세히 다루어 보겠습니다.

    Controller와 Service는 위의 일반 RestTemplate과 동일함으로 생략하겠습니다. (필요하신분은 Git 소스코드를 참조 해주세요.)

     

    Feign을 이용한 통신

    마지막으로 Feign을 이용한 통신을 구현해 보겠습니다. Feign을 사용하면 Java코드도 최대한 줄이고 Interface에 어노테이션 형식으로 간결하고 보다 쉽게 구현할 수 있습니다. 

    1
    2
    3
    4
    dependencies {
      implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign'
      ..... 중략 .....
    }
    cs

    우선 feign을 사용하기 위해서 spring-cloud-starter-openfeign 라이브러리를 추가해줍니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @SpringBootApplication
    @EnableFeignClients
    public class Service1Application {
     
        @LoadBalanced
        @Bean
        public RestTemplate getRestTemplate(){
            return new RestTemplate();
        }
        ..... 중략 .....
    }
    cs

    Service1Application 에 @EnableFeignClients 어노테이션으로 Feign Client임을 설정합니다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    @FeignClient("service2")
    public interface FeignClientCommunicator {
        @RequestMapping(
                method= RequestMethod.GET,
                value="/name/{id}")
        String getName(@PathVariable("id"String id);
    }
    cs

    @FeignClient("service2")로 서비스2로 API 호출한다고 명시 합니다.

    @RequestMapping는 GET 요청으로 "/name/{id}" API를 호출하겠다 입니다.

     

     

    동작 확인

    Http 통신 구현이 완료되었으니 이제 테스트를 해봐야겠지요?

    Eureka 서버와 서비스1과 서비스2를 기동합니다. 그리고 Github에 포함된 Postman Collection을 import하여 실행합니다.

    이번장은 "2. Rest API Call"의 3가지 예제를 각각 실행하여 동작을 확인하실 수 있습니다.

     

    모두 기동하고 즉시 실행했을 경우 404 오류가 발생할 수 있습니다. 지난장 설명 드렸드시 유레카 서버가 준비하는 시간과 클라이언트가 가져오는 약30초의 대기가 필요 합니다.

    • 빠른 처리를 위한 설정 조정은 지난 장을 참조해주세요.

    서비스1의 "/feign/{id}" API를 호출하였습니다. 예상대로 서비스2로 부터 {name}을 받아온 뒤, {id} is {name}이라는 문장을 만들어 클라이언트로 반환 합니다.

     

     

    정리

    이번장에서는 Ribbon을 이용한 HTTP 통신을 구현해 보았습니다. 다음 장에서는 여러 서비스들의 단일 유입 지점인 API Gateway를 Zuul로 구현해 보겠습니다.

     

     

    참고문헌

    - 스프링 마이크로서비스 코딩 공작소 정성권 옮김 길벗

    - https://github.com/klimtever

    댓글

Designed by Tistory.