Spring/Spring Cloud

Spring Cloud 마이크로서비스들은 어떻게 서로 통신 할 수 있을까? - Discovery Client

묠니르묘묘 2023. 10. 9. 16:24

어떻게 마이크로서비스(MS)들은 서로 위치를 알고 호출하는 걸까?

Eureka(유레카)를 이용하여 서로 물리적 위치를 검색하여 찾기 때문이다.

서비스 디스커버리를 위해 MS들이 Spring Cloud Load Balancer(로드밸런서)와 상호 작용할 수 있는 3가지 방법을 찾아보자.

  1. 스프링 Discovery Client
  2. REST 템플릿을 사용한 스프링 Discovery Client
  3. 넷플릭스 Feign Client

 

🚀 스프링 Discovery Client

  • Discovery Client와 표준 스프링 RestTemplate 클래스를 사용하여 호출
  • 로드 밸런서와 그 안에 등록된 서비스에 대해 가장 낮은 수준 접근 가능
  • 즉, Discovery Client를 사용하면 로드밸런서에 등록된 모든 서비스와 URL을 쿼리할 수 있음
@SpringBootApplication
@RefreshScope
@EnableDiscoveryClient // Eureka Discovery Client를 활성화
public class MicroServiceApplication {
	public static void main(String[] args) {
    	SpringApplication.run(MicroServiceApplication.class, args);
    }
}

`@EnableDiscoveryClient`로 스프링 클라우드에서 애플리케이션이 Discovery Client와 스프링 클라우드 로드 밸런서 라이브러리를 사용할 수 있게 됨.

@Component
@RequiredArgsConstructor
public class UserDiscoveryClient {

	// Discovery Client를 클래스에 주입
	private final DiscoveryClient discoveryClient;
    
	public User getUser(String userId) {
		RestTemplate restTemplate = new RestTemplate();
		// 유저 서비스의 모든 인스턴스 리스트를 얻음
		List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
		if (instances.isEmpty()) return null;
        
		// 서비스 엔드포인트를 검색해서 가져옴
		String serviceUri = String.format("%s/v1/user/%s", instances.get(0).getUri().toString(), userId);
		// 서비스 호출을 위해 표준 스프링 RestTemplate 클래스 사용
		ResponseEntity<User> restExchange = restTemplate.exchange(serviceUri, HttpMethod.GET, null, User.class, userId);
		return restExchange.getBody();
	}
}

 

😈 단점

  • Discovery Client를 직접 호출해서 모든 서비스 리스트를 얻고, 그 중 호출할 서비스 인스턴스를 개발자가 책임지고 선정
  • 개발자가 서비스 호출하는 데 사용될 URL 생성
  • RestTemplate을 직접 생성하여 사용
    • 자동 구성이 필요하지 않고 직접적인 제어가 필요할 때 직접 생성함
    • 로드밸런서 인터셉터의 영향을 피하고 특정한 설정 적용가능

 

🚀 로드 밸런서를 지원하는 스프링 REST 템플릿

  • 향상된 스프링 RestTemplate으로 로드밸런서를 사용하는 서비스 호출
  • 스프링을 통해 로드 밸런서와 상호 작용할 수 있는 일반적인 방법
@SpringBootApplication
@RefreshScope
public class MicroServiceApplication {
	public static void main(String[] args) {
		SpringApplication.run(MicroServiceApplication.class, args);
	}

	// RestTemplate 또는 WebClient가 스프링 클라우드 서비스 디스커버리(유레카)와 통합
	// 서비스 이름을 기반으로 특정 서비스에 대한 인스턴스를 선택하고 요청 가능
	@LoadBalanced
	@Bean
	public RestTemplate getRestTemplate() {
		return new RestTempalte();
	}
}
  • 서비스의 물리적 위치 대신 호출하려는 서비스의 유레카 서비스 ID를 사용하여 대상 URL 생성 가능
@Component
@RequiredArgsConstructor
public class UserRestTemplateClient {

	private final RestTemplate restTemplate;
    
	public User getUser(String userId) {
		ResponseEntity<User> restExchange = 
			restTemplate.exchange("http://user-service/v1/user/{userId}", HttpMethod.GET, null, User.class, userId);
        
		return restExchange.getBody();
	}
}
  • 첫 번째 방법과 다르게 Discovery Client가 없어짐
  • URL에서 서버 이름은 유레카에 등록한 서비스 키의 애플리케이션 ID를 사용
  • 즉, 로드 밸런서를 지원하는 RestTemplate 클래스는 전달된 URL을 파싱하고 서버 이름으로 전달된 것을 키로 사용하여 서비스의 인스턴스를 로드 밸런서에 쿼리함
  • 실제 서비스 위치와 포트는 개발자에게 완전히 추상화
  • RestTemplate 클래스를 사용하면 스프링 클라우드 로드 밸런서는 서비스 인스턴스에 대한 모든 요청을 라운드 로빈 방식으로 부여

 

🚀 Feign Client

  • 넷플릭스 Feign Client 라이브러리를 사용하여 로드 밸런서를 경유하여 서비스 호출
  • 자바 인터페이스를 정의하고, 스프링 클라우드 애노테이션을 추가하여 쉽게 사용
@SpringBootApplication
@RefreshScope
@EnableFeignClients // Feign Client 사용하도록 설정
public class MicroServiceApplication {
	public static void main(String[] args) {
		SpringApplication.run(MicroServiceApplication.class, args);
	}
}

유저 서비스의 엔드포인트를 호출할 Feign Client의 Interface 정의를 해보자.

@FeignClient("user-service") // Feign에 서비스를 알려줌
public interface UserFeignClient {
	
    @GetMapping("/v1/user/{userId}") // 엔드포인트 경로와 액션(verb) 정의
    User getUser(@PathVariable String userId); // 엔드포인트에 전달되는 매개변수 정의
}

 

표준 스프링 RestTemplate은 서비스 호출에 대한 HTTP 상태 코드(status code)는 ResponseEntity의 getStatus()에 반환된다. 하지만 Feign Client는 호출된 서비스에서 반환한 모든 HTTP 4xx ~ 5xx 상태코드가 FeignException에 매핑된다. 이 예외에 특정 에러 메시지에 대해 파싱할 수 있는 JSON 내용이 포함되어 있다.

Feign은 사용자가 정의한 Exception 클래스에 에러를 재매핑하는 에러 디코더(ErrorDecoder) 클래스를 작성할 수 있는 기능을 제공한다.

 

🚀 결론

이 중 역시 Feign Client가 가장 쉽게 작성가능하며, 추상화 되어 있고, Load Balanced 지원하기에 이것을 사용하는것이 가장 좋은 방법으로 보인다. 하지만 예외처리는 평범한 RestTemplate과는 다르니 FeignException 또는 FeignErrorDecoder, CircuitBreaker (Resilience4j) 등 클라이언트 측 회복성을 위한 다양한 방법을 숙지해야 한다.

 


스프링 마이크로서비스 코딩공작소 개정2판