MSA란?
MSA (MicroService Architecture) 마이크로서비스 아키텍처의 약자로 아키텍처는 애플리케이션을 작고 독립적인 서비스들로 나누어 각 서비스를 독립적으로 개발, 배포, 확장할 수 있는 아키텍처이다.
각각의 서비스에 최적의 아키텍처를 설계 가능하며, 하나의 서비스의 장애가 전체 서비스에 영향을 주지 않는다.
특징은 아래와 같다.
- 독립적인 서비스
- 각 서비스는 독립적이며, 특정 비즈니스 기능을 담당
- 분산 시스템
- 각 서비스가 독립적으로 동작하고, 독립적인 데이터베이스를 가질 수 있음
- 배포 단위
- 서비스별로 독립적으로 배포가 가능하고, 각 서비스는 별도의 환경에서 운영될 수 있음
- 도커 베이스 컨테이너 기반
- 기술 선택 자유
- 각 서비스는 필요한 기술 스택을 선택하여 개발할 수 있음
- 예를 들어, 하나의 서비스는 Java로 개발하고 다른 서비스는 Python으로 개발할 수 있음
- 인증 : 스프링부트
- AI 활용 : 파이썬
- 상품검색 : 스프링부트 + 엘라스틱서치
- 관리자(CMS) : nodejs
- 판매자 : react + nextjs
- 개별 서비스는 restapi로 통신
Eureka란?
Eureka는 클라우드 환경의 다수의 서비스(예: API 서버)들의 로드 밸런싱 및 장애 조치 목적을 가진 미들웨어서버이다.
총 4가지를 만들어 실습할 것이며 생성은 아래와 같다.
`eureka-server` : 유레카 서버이며 하위 서비스들을 등록 관리한다.
`API Gateway` : 고객이 요청하는 대상이며 관문 역할을 한다.
`server-1` : 1번째 서비스이며 주문, 상품검색 기능을 구현한다.
`server-2` : 2번째 서비스이며 1과 동일하게 구성하지만, WebClient.Builder 없이 구현한다.
이번 글에서 제가 구현하고자 하는 것은 Spring Cloud Netfilx Eureka이며 Eureka가 하는 일이 Service Discovery이다.
여기서 Discovery란 다른 서비스의 정보를 찾는것을 의미한다.
또한 Registry라는 용어가 나오는데, 서비스 정보를 등록하는 것이며,
따라서 Registry를 통해 등록 된 서비스들의 정보를 찾는 것이 Discovery이다.
생성
eureka-server
추가적으로 종속성에 Eureka를 추가해 준다.
build.gradle로 가서 수정하거나 처음 종속성에 추가했으면 아래 라인이 들어있을 것이다.\
의존성은 다음과 같다.
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
`EurekaServerApplication.java` 에 들어가 서버 어노테이션을 추가한다.
package com.example.eurekasever;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer // 추가
public class EurekaSeverApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaSeverApplication.class, args);
}
}
`application.yml` 으로 리펙토링 후 아래와 같이 입력한다.
spring:
application:
name: eureka-sever
# 유레카 서버 구동시 사용할 포트 변경 (8080 -> 8761)
server:
port: 8761
# 유레카 서버에 클라이언트로 등록 되지 않게 설정
eureka:
client:
register-with-eureka: false
fetch-registry: false
그 후 `http://localhost:8761/` 로 접속하게 되면 Eureka에서 기본 제공하는 화면으로 만날 수 있다.
프로젝트는 실행을 유지하면서,
이제 다음으로 `api-gateway` 를 생성하자.
api-gateway
이름은 아래와 같이 생성하고 종속성에 다음 5개를 추가하면 된다.
새창으로 열어준 뒤 `build.gradle` 에 들어가면 추가된 종속성을 확인할 수 있다.
이제 `ApiGatewayApplication.java` 에 유레카 클라이언트 어노테이션을 추가한다.
이는 디스커버리 서비스가 찾아서 등록하는 대상 서비스로 게이트웨이를 담당한다.
(다른 서비스들의 라우팅 처리를 수행한다.)
package com.example.apigateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient // 추가
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
그다음 이 프로젝트의 `application.yml` 으로 들어가자.
아직 미구현 상태로 `404 error` 가 뜰 테지만 일단은 등록해 놓자.
spring:
application:
name: api-gateway
# 미구현
cloud:
gateway:
routes:
#- id: 서비스이름
server:
port: 8080
# 유레카 서버 등록 (미구현)
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
그 후 실행하면
`http://localhost:8761/` 에서 아래와 같이 인스턴스가 생성된 걸 확인할 수 있다.
server-1
프로젝트를 생성하고 아래와 같이 종속성을 추가해 본다.
당연히 Eureka Client와 비동기식 처리를 위해 Reactive Web을 추가하며,
다음 단계들을 추가해 준다.
자 `build.gradle` 을 참고하여 잘 적용되었는지 확인한다.
(상위 4가지를 보면 잘 추가된 걸 알 수 있다.)
이 프로젝트에는 2가지 파일이 더 필요하다.
`Service1 Configuration.java` 와 ` Server1 Controller.java` 을 생성한다.
위 파일들을 만드는 이유는 Bean을 구성하여 싱글톤으로 사용하기 위함이다.
클라이언트를 생성하는 방법은 크게 2가지가 있는데,
`WebClient` 와 `WebClient.Builder`이다.
둘의 차이점은 `WebClient` 은 매번 생성되며 `WebClient.Builder` 는 1회 생성 후 재사용을 한다.
매번 만드는 건 자원낭비가 있기에 여기선 `WebClient.Builder` 을 사용하겠다.
//Service1Configuration.java
package com.example.server1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class Service1Configuration {
// 빈 구성 -> DI 사용 처리
@Bean
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
}
이제 Configuration에서 생성한 빈을 DI로 가져오자.
여기선 server2를 통해 요청을 받아와서 넘기는 과정이다.(즉 2가 없으면 사용이 안된다.)
package com.example.server1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@RestController
public class Server1Controller {
@Autowired
private WebClient.Builder webClientBuilder;
// 서비스 1의 고유 요청 처리
@GetMapping("/service1")
public String service1() {
return "서비스 1에서 완성되는 고유한 작업 요청 응답";
}
// 서비스 2의 요청 처리 후 응답
@GetMapping("/service1/byService2")
public Mono<String> byService2() {
//요청 1개에 대해 결과가 0 or 1 만 나오게 구성
return webClientBuilder.baseUrl("http://localhost:8082") // 서비스 2에 대한 특정 url 요청
.build()
.get()
.uri("/service2")
.retrieve()
.bodyToMono(String.class)
// lambda 식으로 처리
.map(res -> "서비스 1의 요청에 대한 서비스 2의 응답: " + res);
}
}
여기선 서로의 통신방식을 이해하기 위해 간단히 구성하였다.
추후 사용에는 Json을 통해 응답처리를 할 수 있다.
이렇게 구성하면 격리되고 독립적으로(오류를 최소하하여) 요청 및 응답을 처리할 수 있다.
이제 마지막 `appication.yml` 으로 이동하자.
포트번호는 중복이 안되도록 8081로 설정하였다.
spring:
application:
name: server-1
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 8081
이제 gateway에서 서비스 1을 물고 와야 하므로 다시 gateway의 application을 수정하자
cloud:
gateway:
routes:
- id: service1-route
uri: lb://SERVER-1
predicates:
- Path=/service1/**
아까 주석처리한 routes에서 수정해 준다.
여기서 `lb:` 는 로드벨런싱을 뜻한다.
이제 확인을 위해 server1을 실행하고 gateway를 이어서 실행하면
2개의 애플리케이션이 등록된 걸 볼 수 있다.
server-2
server-1과 같이 만들면 되기에 빠르게 진행하겠다.
종속성은 같이 설치하며, `application.yml` 은 아래와 같이 진행한다.
spring:
application:
name: server-2
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 8082
바뀐 부분은 `port` 와 이름 빼고 없다.
다음은 컨트롤러를 복사해 활용하자.
//Server2Controller.java
package com.example.server2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Server2Controller {
// 서비스 1의 고유 요청 처리
@GetMapping("/service2")
public String service2() {
return "주문 관리";
}
}
해당 내용도 필요 없는 부분은 지우고 기본값만 바꿔주었다.
이제 다시 api-gateway에 가서
#id: service1-route 아래에 기입
- id: service2-route
uri: lb://SERVER-2
# URL 기술, 다양한 방식 등록, 메소드,.....
predicates:
- Path=/service2/**
이렇게 추가해 준다.
결과
이제 `http://localhost:8081/service1` , `http://localhost:8082/service2` 뿐 아니라
`http://localhost:8080/service1` 에서도 아래와 같이 나오며
`http://localhost:8080/service1/biz` 에서는
//Service1Controller.java 중..
.map(res->"서비스2로부터 응답결과:" + res)
이렇게 service2에서 결괏값을 받아와 service1으로, 그리고 8080 포트로 넘어오는 걸 확인할 수 있다.
'SK 루키즈 > Cloud' 카테고리의 다른 글
[Rookies 개발 2기] Docker를 통해 Kafka 연결하기 (2) | 2025.02.06 |
---|---|
[Rookies 개발 2기] AWS 서버리스 개념과 ECS와 Lambda (0) | 2025.01.24 |
[Rookies 개발 2기] AWS S3 에 Spring 업로드 처리 (0) | 2025.01.23 |
[Rookies 개발 2기] S3 AWS KEY 발급 (0) | 2025.01.23 |
[Rookies 개발 2기] React + SpringBoot + MySql CI/CD (2) (0) | 2025.01.20 |