Study/Mastering-Spring-5-Study

ch12. 마이크로서비스 구축하기(MSA)

hongeeii 2023. 1. 16.
728x90
반응형

마이크로서비스 예제를 먼저 작성해 본다.

  1. random한 5개의 number목록을 만들어주는 service
spring.application.name=microservice-a

@Slf4j
@RestController
public class RandomNumberController {
	@RequestMapping("/random")
	public List<Integer> random() {
		List<Integer> numbers = new ArrayList<>();
		IntStream.rangeClosed(1, 5).forEach(num -> {
			numbers.add(generateRandomNumber());
		});

		log.info("Returning :::" + numbers);
		return numbers;

	}

	private int generateRandomNumber() {
		return (int) (Math.random() * 1000);
	}
}
  1. microservice-a에서 랜덤 목록 서비스를 소비하는 서비스
server.port=8100
spring.application.name=service-consumer
number.service.url=http://localhost:8080/random

@Slf4j
@RestController
public class NumberAdderController {

	@Value("${number.service.url}")
	private String numberServiceUrl;

	@RequestMapping("/add")
	public Long add() {
		long sum = 0;

		ResponseEntity<Integer[]> responseEntity = new RestTemplate().getForEntity(numberServiceUrl, Integer[].class);

		Integer[] numbers = responseEntity.getBody();

		for (int number : numbers) {
			sum += number;
		}

		log.warn("Returning :::" + sum);
		return sum;
	}
}

http://localhost:8100/add url을 호출하면 microservice-a에서 random 목록이 리턴되고, 서비스 소비자의 add 서비스를 추가해 리턴받는다.

마이크로서비스에 클라우드 네이티브 기능 추가

6개의 서로다른 마이크로서비스 애플리케이션과 구성요소를 만들기 위해 특정 애플리케이션에 특정 포트를 사용.

중앙 집중식 마이크로서비스 구성

다른 마이크로서비스에 관한 구성을 별도로 유지하면 운영이 어려워질 수 있다.
솔루션은 중앙 집중식 컨피그 서버를 만드는 것이다.


중앙 집중식 컨피그 서버는 서로 다른 모든 마이크로서비스에 속한 모든 구성을 보유한다.
Spring Initializer에서 제공되는 옵션


주키퍼와 콘솔이 좋은 대안이지만, 가장 인기있는 옵션은 Spring-cloud-config다. (Config Client)

스프링 클라우드 컨피그

스프링 클라우드 컨피그는 중앙 집중식 마이크로서비스 구성을 지원한다.
두 가지 중요한 구성요소의 조합이다.

  • 스프링 클라우드 컨피그 서버
    • 버전 관리 레파지토리(GIT 또는 하위 버전)에 의해 백업 된 중앙 집중식 구성 노출을 지원
  • 스프링 클라우드 컨피그 클라이언트
    • 애플리케이션이 스프링 클라우드 컨피그 서버에 연결하도록 지원

여러 마이크로서비스 구성은 단일 깃 레파지토리에 저장.

스프링 클라우드 컨피그 서버 구현

스프링 클라우드 컨피그를 구현하려면 다음 단계가 필요하다.

  1. 클라우드 컨피그 서버 설정
  2. 마이크로서비스-a에 서비스를 새로 만들어 애플리케이션 구서으이 일부 정보 리턴, 서비스로 스프링 클라우드 컨피그 서버에서 구성을 선택할 수 있는지 테스트
  3. 로컬 깃 레파지토리를 설정하고 스프링 클라우드 컨피그 서버에 연결
  4. 스프링 클라우드 컨피그 클라이언트를 사용해 클라우드 컨피그 서버의 구성을 사용하도록 마이크로서비스-a를 업데이트

1. 스프링 클라우드 컨피그 서버 설정

컨피그 서버 dependency 추가, EnableConfigServer 어노테이션 추가

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-config-server</artifactId>
</dependency>


@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(ConfigServerApplication.class, args);
	}

}

2. 애플리케이션 구성에서 메시지를 반환하기 위해 마이크로서비스-a에 서비스 생성

application.properties에서 간단한 메시지를 반환하는 서비스 만든다.

application.message=Default Message

@Configuration
@Setter
@Getter
@ConfigurationProperties("application")
public class ApplicationConfiguration {
	private String message;
}

@RestController
public class MessageController {

	@Autowired
	ApplicationConfiguration configuration;

	@RequestMapping("/message")
	public Map<String, String> welcome() {
		Map<String, String> map = new HashMap<>();
		map.put("message", configuration.getMessage());
		return map;
	}
}

http://localhost:8080/message
{
  "message": "Default Message"
}

3. 스프링 클라우드 컨피그 서버를 로컬 깃 레파지토리에 연결

킁라우드 컨피그 서버가 깃 레파지토리에서 구성을 선택하기를 원하므로 시작하기 위해선 깃 레파지토리를 설정해야한다.

management.security.enabled=false
application.message=Message From Default Local Git Repository

위의 내용으로 깃 레파지토리에 마이크로서비스-a의 구성을 추가, add, commit 

config-server에서 application.properties 구성

spring.application.name=config-server

server.port=8888

# 로컬 깃 레파지토리의 uri구성
# 원격 깃 레파지토리에 연결하려면 여기에서 깃 레파지토리의 uri를 구성해야함
spring.cloud.config.server.git.uri=file://C:/gb_0900_msh/tobySpring/resource/sts-4.13.1.RELEASE/workspace/git-localconfig-repo

서버를 시작하고 http://localhost:8888/microservice-a/default 요청을 보내면 아래와 같은 응답이 표시된다.

URI 포멧은 /{application-name}/{profile}[/{label}]이다.
기본 프로파일을 사용하고 있으므로 서비스는 microservice-a.properties에서 구성을 반환한다.
propertySource>name 필드에서 확인할수 있다.
source : 응답내용은 속성 파일의 내용.

git-localconfig-repo에서 이름이 microservice-a-dev.properties인 새파일 생성

 git add, git commit
http://localhost:8888/microservice-a/dev 요청


microservice-a-dev.properties에 구성된 속성은 microservice-a.properties에 구성된 기본값보다 우선순위가 높다.

dev와 마찬가지로 마이크로서비스A에 대한 별도의 구성을 다양한 환경에 맞게 만들수 있다.
클라우드 컨피그 서버의 마이크로서비스A의 애플리케이션 구성이 준비되었다.
마이크로서비스A를 클라우드 컨피그 서버에 연결해보자.

마이크로서비스A를 스프링 클라우드 컨피그 클라이언트로 만들기

마이크로서비스a는 애플리케이션 구성을 검색하기 위해 스프링 클라우드 컨피그 서버와 통신해야 한다.

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-config</artifactId>
	<version>3.1.0</version>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-bootstrap</artifactId>
	<version>3.1.0</version>
</dependency>

application.properties이름을 bootstrp.properties로 변경

spring:
  application:
    name: microservice-a
  profiles:
    active: default   
  cloud:
    config:
       import: "optional:configserver:http://localhost:8888"

마이크로서비스a가 컨피그서버와 연결되기를 원하므로 spring.cloud-config.uri를 사용하여 컨피그 서버의 uri제공.
클라우드 컨피그 서버는 마이크로서비스a의 구성을 검색하는데 사용.

스프링 클라우드 컨텍스트  

부트 스트랩 애플리케이션 컨텍스트는 마이크로서비스 애플리케이션의 상위 컨텍스트.  
외부 구성(스프링 클라우드 컨피그 서버 등)을 로드하고 구성파일(외부 및 로컬)의 암호를 해독.  
부트 스트랩 컨텍스트는 bootstrap.yml이나 bootstrap.properties를 사용해 구성.  
마이크로서비스a가 부트 스트랩에 컨피그 서버를 사용하기 원했기 때문에 마이크로서비스a에서 application.properties라는 이름을 bootstrap.properties로 변경해야함.

==> 먼저 2.4 이전에는 client 에 bootstrap.yml(또는 properties) 파일이 필요했다. 그래서 서버 정보를 입력해두면 라이프사이클 상 application.yml 보다 bootstrap.yml 을 먼저 읽어 config server 에 정의돼있는 프로퍼티들을 읽어보게끔 했었다. 2.4 에선 더 이상 bootstrap.yml 이 필요하지 않다.

spring: config: import: "optional:configserver:http://localhost:8888"

메시지는 localconfig-repo/microservice-a.properties 파일에서 선택됨.
dev구성을 선택하기 위해 활성 프로파일을 dev로 설정가능.

spring.profiles.active=dev

 

이벤트 중짐 접근법

비동기 통신에는 두 가지 접근 방식이 있다.

  • JMS API를 사용하는 스프링 JMS
  • AMQP(고급 메시지 큐 프로토콜) 

JMS API를 이용한 스프링 JMS

자바에서 비동기 통신에 가장 많이 사용되는 API중 하나는 JMS(java Messaging Service)이다.
JMS API는 메시지 브로커를 통해 메시지를 송수신함으로써 자바 애플리케이션이 느슨하게 결합된 비동기 방식으로 통신하도록 API를 정의한다.
ActiveMQ는 가장 널리 사용되는 JMS 호환 메시지 브로커중 하나다.
스프링 JMS는 스프링 애플리케이션에서 JMS통신을 단순화 한다.
Artemis는 ActiveMQ 를 새롭게 다시 구현한 차세대 브로커다.

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-artemis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-artemis</artifactId>
    <version>2.6.4</version>
    <scope>test</scope>
</dependency>

@Autowired
	private JmsTemplate jmsTemplate;

	private Queue queue = new ActiveMQQueue("Yoon-ju-young-queue");

	public void simpleSend() {
		this.jmsTemplate.send(queue, new MessageCreator() {
			@Override
			public Message createMessage(Session session) throws JMSException {
				return session.createTextMessage("send Message");
			}
		});
	}

JmsTemplate은 메시지 브로커를 통해 메시지를 주고받을 수 있는 메소드를 제공하는 헬퍼 클래스다.
Queue는 메시지 브로커를 나타낸다.

AMQP

AMQP(Advanced Message Queuing Protocal)는 비동기 통신에 가장 많이 사용되는 프로토콜 중 하나다.
RabbitMQ, OpenAMQ과 StormQM는 널리 사용되는 구현 중 일부다.
AMQP는 신뢰할 수 있고, 상호 운용 가능하다.

스프링 클라우드 버스

스프링 클라우드 버스는 마이크로서비스를 카프카, RabbitMQ와 같은 가벼운 메시지 브로커에 원활하게 연결한다.

----넘어가겠습니다.........---------------------------------------------------------------------------

선언적 REST 클라이언트 - 페인

페인(Feign)은 최소한의 구성 및 코드로 RESTful 서비스를 위한 REST클라이언트를 생성할 수 있도록 도와준다.
간단한 인터페이스를 정의해야하는데, 적절한 어노테이션을 사용한다.
RestTemplate은 일반적으로 REST 서비스 호출을 수행하는데 사용된다.
페인은 restTemplate과 주변의 로직 없이도 REST클라이언트를 작성할 수 있도록 도와준다.

페인은 립본(로드밸런싱)과 유레카(네임 서버)와 잘 통합된다.

Feign 설정

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
	<version>3.1.1</version>
</dependency>

@EnableFeignClients("com.example.demo")
@SpringBootApplication

Feign 사용

@FeignClient(name = "microservice-a", url = "localhost:8080")
public interface RandomServiceProxy {

	@GetMapping(value = "/random")
	List<Integer> getNumbers();
}

@FeignClient(name = "microservice-a", url = "localhost:8080") : FeignClient어노테이션은 주어진 인터페이스를 가진 REST클라이언트를 생성해야한다고 선언하는 데 사용.
여기서는 현재 마이크로서비스A의 URL을 하드 코딩하고있다.
네임 서버에 연결해 하드 코딩이 필요 없는 방법은 나중에 설명.

@RequestMapping():특정 메소드(get...) 서비스 메소드는 URI, 즉 /random에 노출
public List getRandomNumbers() : 서비스 메소드의 인터페이스를 정의.

서비스를 호출하기 위해 RandomServiceProxy를 사용하도록 Controller 업데이트.

@RestController
public class NumberAdderController {

	@Autowired
	private RandomServiceProxy randomServiceProxy;

	@RequestMapping("/add")
	public Long add() {
		long sum = 0;

		List<Integer> numbers = randomServiceProxy.getNumbers();

		for (int number : numbers) {
			sum += number;
		}
		return sum;
	}
}	

로드밸런싱

마이크로서비스 인스턴스는 특정 마이크로서비스의 로드에 따라 확장 및 축소된다. 다른 마이크로서비스 인스턴스 간에 로드가 균등하게 분배되도록 하려면 로드밸런싱을 활용하면 된다.
로드 밸런싱은 로드가 서로 다른 마이크로서비스 인스턴스 간에 균등하게 분배되도록 한다.

넷플릭스 립본(Ribbon)

립본은 마이크로서비스의 여러 인스턴스 간에 라운드-로빈실행을 사용해 로드밸런싱을 제공.
라운드로빈이란 그룹 내에 있는 모든 요소들을 합리적인 순서에 입각하여 뽑는 방법을 말한다.

서비스 소비자 마이크로서비스에 립본을 추가한다.
ribbon 설정

microservice-a.ribbon.listOfServers=http://localhost:8080,http://localhost:8081
	
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-ribbon -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
	<version>2.2.2.RELEASE</version>
</dependency>  

ribbon 사용

@RibbonClient(name = "microservice-a")
@FeignClient(name = "microservice-a")
public interface RandomServiceProxy {

	@GetMapping(value = "/random")
	List<Integer> getNumbers();
}
	

마이크로서비스 URL 하드 코딩의 한계

application.properties에 다음 구성을 추가했다.
microservice-a.ribbon.listOfServers=http://localhost:8080,http://localhost:8081
마이크로서비스A가 다른 서버로 이동했거나 기존 인스턴스는 더 이상 사용될수 없는 경우, (cloud 환경에선 url이 자주 바뀔수 있다.)

네임 서버

네임 서버와 상호 작용하는 데 중요한 두 단계

  1. 등록 : 모든 마이크로서비스는 각 마이크로서비스가 시작될 때 네임 서버에 자신을 등록
  2. 다른 마이크로서비스의 위치 찾기 : 서비스 소비자가 특정 마이크로서비스의 위치를 얻으려고 할 때 네임 서버를 요청.
    서비스 소비자는 마이크로서비스 id로 네임 서버를 찾을 때마다 해당 마이크로서비스의 인스턴스 리스트를 가져옴.

네임 서버 - 유레카

유라카 구현 과정

  1. 유레카 서버 설정
  2. 유레카 서버에 등록할 마이크로서비스A 인스턴스 업데이트
  3. 유레카 서버에 등록된 마인크로서비스A 인스턴스를 사용하도록 서비스 소비자 마이크로서비스 업데이트
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

	
<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>
	
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}

}
	
server.port=8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

유레카 대시보드는 다음과 같다.


현재 유레카에 등록된 애플리케이션은 없다.
마이크로서비스A와 기타 서비스를 유레카에 등록해본다.

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
	
@EnableDiscoveryClient
@SpringBootApplication
public class MicroserviceAApplication {

	public static void main(String[] args) {
		SpringApplication.run(MicroserviceAApplication.class, args);
	}

}

spring.application.name: microservice-a

eureka.client.service-url.defaultZone=http://localhost:8761/eureka

마이크로서비스A가 다시 시작되면 유레카 서버 로그에 다음 메시지가 표시된다.


유레카 대시보드는 다음과 같다.

마이크로서비스A의 인스턴스가 유레카 서버에 등록됬다.
서비스 소비자 마이크로서비스를 연결해 유레카 서버에서 마이크로서비스A 인스턴스의 URL을 가져온다.

서비스 소비자 마이크로서비스와 유레카 연결

service-consumer에 설정 추가.

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>	
	
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
	
@EnableDiscoveryClient
@EnableFeignClients("com.example.demo")
@SpringBootApplication
public class ServiceConsumerApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServiceConsumerApplication.class, args);
	}

}

유라카 서비스에서 url을 가져오면 립본에서 선택한 서비스 인스턴스를 호출한다.

다음과 관련된 단계들을 살펴보자.

  1. 마이크로서비스A의 각 인스턴스가 시작되면 유레카 네임 서버에 등록
  2. 서비스 소비자 마이크로서비스는 마이크로서비스A 인스턴스에 대해 유레카 네임 서버를 요청
  3. 서비스 소비자 마이크로서비스는 립본 로드밸런서를 사용해 호출할 마이크로서비스A의 특정 인스턴스를 결정
  4. 서비스 소비자 마이크로서비스는 마이크로서비스A의 특정 인스턴스를 호출

유레카서비스의 가장 큰 장점은 서비스 소비자 마이크로서비스가 마이크로서비스A와 분리된다는 것.
서비스 소비자 마이크로서비스는 마이크로서비스A의 새로운 인스턴스가 등장하거나 기존 인스턴스가 다운될 때마다 재구성할 필요가 없다.

API 게이트 웨이

마이크로서비스 아키텍처에는 많은 마이크로서비스가 구축된다.
각 마이크로서비스는 구현해야 하는 공통 기능이 있다.
공통 기능을 크로스 컷팅 문제라고 한다.

  • 인증, 권한 부여, 시큐리티
  • 동적 라우팅
  • 내결함성

마이크로서비스가 서로 대화할때는 개별 마이크로서비스를 통해 문제를 해결해야한다.
아키텍처는 각 마이크로서비스가 이런 문제를 다르게 처리할 수 있기 때문에 유지관리가 어려울수 있다.
API 게이트웨이를 사용해 마이크로서비스를 오가는 모든 서비스 호출은 api 게이트웨이를 거쳐야한다.

728x90
반응형

추천 글