[ Spring ] MSA

한대희·2024년 1월 3일

Spring

목록 보기
3/4

✅ Microservice와 Spring Cloud

소프트웨어 아키텍처 변화 과정

  • 초기 it 시스템은 하드웨어 중심이었다. MainFrame의 시대라고도 부른다.
  • 소프트웨어 보다는 하드웨어 사양, 성격에 맞춰서 서비스를 구축하였었다.
  • 따라서 서비스의 기능을 수정하거나 변경할려면 고가의 하드웨어르 바꾸어야 했기 때문에 서비스 변경과 수정이 어려웠다.
  • 이때는 깨지기 쉬운 시스템이라는 뜻에서 Fragile 이라고 부른다.
  • 그 다음으로는 시스템이 분산화 되기 시작하였다.
  • Distributed(분산), Robust( 안정화 ) 라는 키워드를 사용할 수 있고, 분산화된 시스템 덕분에 서비스에 불확실성의 변화가 발생한다 하더라도 안정성 있고, 성능이 높은 서비스를 구축할 수 있었다.
  • 마지막으로 최신에는 Anti-Fragile과 Resilient를 키워드로 들 수 있는데 이것은 Cloud Native 환경을 의미한다.
  • 시스템은 local에서 클라우드로 이전되었고, 확장성과 안정성이 더욱 강화 되었으며, 지속적인 개선 및 변경사항이 생기더라도 시스템은 탄력적으로 운영되도록 시스템이 구축 되었다.

Antifragile의 4가지 특징

Auto Scaling

  • 자동 확장성을 갖는 것을 의미한다.
  • 시스템을 구성하는 여러가지 인스턴스 들이 하나의 오토 스케일링 그룹으로 묶인다.
  • 해당 그룹에는 최소로 유지되어야 하는 인스턴스 갯수를 지정할 수 있고, 사용량의 증가에 따라 인스턴스 갯수가 증가할 수 있는 환경을 의미한다.
  • 쇼핑몰을 예로 들면 특수한 이벤트가 있는 달에는 서버의 운영 개수를 늘리고, 비수기에는 서버의 운영 개수를 다시 줄이는 것을 들 수 있다.
  • 이러한 작업은 관리자나 운영자가 수작업으로 처리하는 것이 아니라 CPU나 메모리, 네트워크, 데이터베이스 사용량 등에 따라 자동으로 처리 되는 개념이 바로 Auto Scaling이다.

MicroServices

  • 마이크로서비스란 각각의 기능이 개별적인 모듈로 독립적으로 개발되고, 배포 되어 하나의 서비스가 구성되는 것을 말한다.

Chaos Engineering

  • 카오스 엔지니어링이란 시스템이 급격하고 예측하지 못하는 상황이라도 견딜수 있도록 시스템에 실험하는 방법과 규칙을 의미한다.

Continuous Deployments

  • CI / CD 와 같은 배포 파이프라인을 의미한다.
  • CI / CD는 지속적인 통합, 지속적인 배포를 의미한다.
  • 클라우드 네이티브 어플리케이션은 수십, 수백개의 마이크로 서비스로 도메인이 분리되어 개발되기 때문에 이것들을 하나하나 빌드하고 테스트 할 순 없다.
  • 따라서 하나의 작업에서 다른 작업으로 연계되는 과정을 파이프라인으로 연결 시켜 빌드, 배포 과정을 자동화 시키는 시스템을 구축한 것.

✅ Service Discovery

  • Discovery는 말 그대로 외부에서 마이크로 서비스들을 검색 하기 위해서 사용되는 개념이다.
  • 전화번호 부 처럼 어떤 msa가 어디에 위치하고 있는지 등록된 것이라고 생각하면 된다.
  • 따라서 각각의 마이크로 서비스 전부 자신의 위치 정보를 Discovery에 올려야 한다.

Discovery 서버 생성

  • 먼저 msa 들이 등록되는 Discovery 서버를 만들어 보자.
  • spring initializer를 통해 eureka server 디펜던시를 설치하여 프로젝트를 만든다.
  • 그 다음 application.properties파일의 이름을 application.yml로 바꾸고 아래와 같이 구성해 보자.
server:
  port: 8761

spring:
  application:
    name: discoveryService

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
  • 그 다음 메인 메서드에 EnableEurekaServer 어노테이션을 추가한다.
// EnableEurekaServer 어토테이션을 추가하면 해당 서버가 Discovery서버가 된다.

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication {

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

}

Discovery 서비스에 MSA ( 디스커버리 서버의 클라이언트 ) 추가

  • 이제 마이크로 서비스를 하나 만들어서 discovery에 추가해 보자.

  • discovery는 수많은 마이크로 서비스가 등록되는 서버고, 그에 따라 마이크로 서비스는 discovery의 클라이언트가 된다.

  • 따라서 eureka server client 디펜던시를 설치하여 msa 프로젝트를 만들어 보자.

  • 프로젝트롤 만들고 메인 메서드에 @EnableDiscoveryClient 어노테이션을 추가한다.

@SpringBootApplication
@EnableDiscoveryClient
public class Msa1Application {

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

}
  • application.yml파일에 설정 정보를 입력한다.
server:
  port: 9001


spring:
  application:
    name: msa1

eureka:
  client:
  // 유레카 서버에 등록할 것인지 여부
    register-with-eureka: true
  // 유레카 서버로 부터 인스턴스들의 정보를 주기적으로 가져올 것인지 여부
    fetch-registry: true
  // msa가 등록되는 유레카 서버의 위치 정보 지정 아래는 유레카 서버에 
    /eureka 엔드포인트를 추가하여 현재 msa의 위치를 지정 한 것.
    service-url:
     defalutZone: http://127.0.0.1:8761/eureka
  • 여기 까지 하고 실행을 해 보면 유레카 서버에 접속했을 때 msa1이 등록된 것을 확인할 수 있다.

  • msa를 하나더 등록해 보자.

  • 인텔리제이의 edit configuration을 활용하면 같은 프로젝이트 이지만 설정 정보를 다르게 하여 다른 프로젝트 처럼 실행 시킬 수 있다.

  • 아래 창을 열고, modify option에서 옵션 추가 해서 -Dserver.port=9002를 추가하여 MSA2는 9002번 포트에서 열리게 해보자.

  • 유레카 서버에 접속하여 확인하면 아래 처럼 2개의 MSA가 생긴것을 확인할 수 있다.

Load Balancer

  • 위의 방식으로 각각의 msa 마다 포트를 다르게 지정하여 여러개의 msa를 Discovery 서버에 등록할 수 있지만 이건 매우 번거로운 작업이다.
  • 따라서 스프링에서는 랜덤 포트 방식을 지원한다.
  • application.yml의 port를 0번으로 지정하면 된다.
  • 이렇게 하면 위에서 했던 것 처럼 msa 마다 포트를 다르게 지정할 필요가 없다.
  • 하지만 실행해 보면 discovery에 하나의 MSA만 등록되어 있는 것을 확인할 수 있는데 그 이유는 Discovery에서 msa를 등록할 때 호스트IP+msa이름+포트로 msa를 구분하는데 각각의 msa가 다른 포트로 열렸더라도 설정은 0번으로 되어 있기 때문에 하나만 등록이 되는 것이다.
  • 따라서 application.yml에 인스턴스 마다 다른 아이디가 생성 되도록 추가적인 정보를 입력해 주자.
eureka:
  // 아래 부분을 추가하였다.
  instance:
    instance_id: ${spring.cloud.client.hostname}:${spring.application.instance_id}:${random.value}}
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
  • 이제 실행해 보면 2개의 msa가 등록된 것을 확인할 수 있다.

  • 이처럼 새로운 msa가 추가 될 때마다 Discovery 서비스에 등록이 되게 하여 로드 밸런싱 하는 작업을 구현할 수 있다.

Api Gateway Service

  • 위의 과정에서 msa들이 Discovery에 등록이 되었다.
  • 그러면 이제 클라이언트들이 msa를 요청하게 될텐데 이때 사용되는 것이 GateWay다.
  • 클라이언트가 Gateway 서버에 특정 msa를 요청하면 Gateway는 Discovery에 msa가 어디에 있는지 물어보고 위치를 반환 받아 클라이언트에게 msa를 전달하는 것이다.
  • Gateway를 사용하는 이유는 만약 클라이언트가 각각의 msa의 엔드 포인트로 직접적인 요청을 하게 된다면 msa에 수정사항이 생겼을 때 msa는 개별적인 수정 배포를 원래 해서 문제가 없지만, 만약 엔드 포인트 변경과 같은 걸로 클라이언트 코드도 수정사항이 생긴다면 클라이언트도 수정 배포를 거쳐야 하기 때문에 이것을 막고자 단일 진입점을 개발한 것이다.

Spring Cloud Gateway

  • 먼저 Gateway를 만드는 새로운 프로젝트를 시작해 보자.
  • Gateway와 Eureka Discovery Client 디펜던시를 추가해서 새로운 프로젝트를 만들어 보자.
  • 그 다음 application.yml을 아래와 같이 만든다.
// gateway 서버 포트
server:
  port: 8000

// gateway를 Discovery에 등록
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      routes:
      // 클라이언트가 first-service를 요청하면 아래 uri로 보내줌
        - id: first-service
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
            
   ❗여기서 주의할 점이 있다.위의 routes를 살펴 보자.
     gateway 서버에서  /first-service로 요청이 오면 8081로, 
     /second-service 요청이 오면 8082로 이동을 한다고 되어 있는데
     그냥 이동 하는 것이 아니라 Path 경로가 이동되는 url뒤에 붙게 된다.
     
     예를 들어 localhost:8080/first-service로 요청을 하면
     localhost:8081/first-service로 요청이 가게 되는 것이다.
     
     따라서 해당 url을 컨트롤러에서 매핑을 해줘야 한다.
  • first-service 컨트롤러
@RestController
@RequestMapping("/first-service")
public class FirstServiceController {
    @GetMapping("/welcome")
    public String welcome () {
        return "Welcome to the first Service";
    }

}
  • second-service 컨트롤러
@RestController
@RequestMapping("/second-service")
public class SecondServiceController {
    @GetMapping("/welcome")
    public String welcome () {
        return "Welcome to the second Service";
    }

}
  • 이렇게 하면 게이트웨이 서버에서 url입력시 해당 서비스로 매핑되어 이동 할 수 있게 된다.

Gateway Filter 적용

  • Gateway에서 클라이언트의 요청이 처리 되는 과정을 살펴 보자.
  • 먼저 클라이언트의 요청을 받는다.
  • 그 다음 Gateway의 predicates에 요청에 대한 분기가 되어 있는데 이것과 요청을 비교한다.
  • 요청이 사전에 분기된 요청이라면 이제 사전 필터(pre-filter)와 사후 필터(post-filter)를 거쳐 최종 요청 정보를 구성하게 된다.

Gateway Load Balancer

  • 이번에는 Gateway와 Discovery 서버를 연동시켜 보자.

  • 먼저 Gateway와 firstService, secondService를 모두 Discovery에 등록해야 한다.

  • 우선 Discovery에 등록을 하려면 Eureka Client 라이브러리가 설치가 되어 있어야 한다. 각각 설치가 되었는지 확인하자.

// 라이브러리 설치 후 아래와 같이 application.yml에 작성해야 한다.
// defaultZone에 Discovery 서버의 주소를 입력한다.
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-uri:
      defaultZone: http://localhost:8761/eureka
  • 이제 Discovery 서버에 접속해 보면 Eureka 화면이 나오는데 거기에 등록된 서비스들의 목록이 나온다.
  • 여기서 등록된 서비스의 이름은 각각의 서비스의 application.yml 파일에 아래와 같이 정의된 부분이 등록된다.
spring:
  application:
    name: gateway
  • 이제 라우팅 설정을 해주자.
  // 아래 설정을 해석해 보면, gateway로 first-service 요청이 오면 
     lb://first-service로 이동한다는 의미인데
     여기서 lb는 Discovery 서버에 등록된 서비스들의 이름을 찾겠다는 의미이고
     first-service는 Discovery에 등록된 서비스의 이름이다.
  cloud:
    gateway:
      routes:
        - id: first-service
          uri: lb:// first-service
          predicates:
            - Path=/first-service/**
        - id: second-service
          uri: lb://second-service
          predicates:
            - Path=/second-service/**
  • 이렇게 하면 특정 요청을 특정 서버에 매핑하여 하나의 서버에 트래픽이 몰리지 않게 할 수 있어 로드 밸런싱을 구현할 수 있다.

✅ E-Commerce 어플리케이션 개발

profile
개발 블로그

0개의 댓글