Spring Cloud(2) - MSA

오민석·2021년 7월 20일
0

Spring Cloud Config

  • 분산시스템에서 설정 정보(application.yml)을 외부 시스템에서 관리
  • 하나의 중앙화 된 저장소에서 구성요소 관리
  • 각 서비스 빌드하지 않고, 바로 적용가능
  • 어플리케이션 배포 파이프라인 통해 dev-prod 환경 맞는 구성정보 사용

application.yml application-name.yml application-name-.yml

구성정보 변경 방법

서버 재가동

Actuator Refresh

Application 상태 및 모니터링 End Point 통해서
ex. beans, health, env, metrics

remote git에 있는 설정정보를 commit 할 때마다 해당 endpoint를 통해서 모든 서버를 재가동하는것은 힘들다
http://localhost:8001/actuator/refresh

Spring cloud bus(미들웨어)

분산시스템의 노드(MicroService)를 메세지 브로커(RabittMQ)와 연결
설정 변경 사항을 Broadcast하게 AMQP 프로토콜을 통해 연결된 노드들에게 알려줌
Spring Cloud Bus 는 SpringBoot Application 에 부착되어 설정 정보를 지속적으로 반영할 수 있게 한다.

Cofig-Service

  server:
  port: 8888

spring:
  application:
    name: config-service
  #모든 serivce에 rabbitmq 등록
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
  cloud:
    config:
      server:
        git:
          uri: https://github.com/minsuk1/spring-cloud-config

# actuator에 busrefresh 등록 
management:
  endpoints:
    web:
      exposure:
        include: health, busrefresh

순서

  • 개발자는 configuration file 을 remote repository 에 push 한다.

  • Spring Cloud Bus 가 Message Broker 로 변경된 설정 정보에 대한 Message 를 발행한다.

  • Message Broker는 설정 정보를 저장하고 있는다.

  • 개발자가 설정 정보가 변경되었음을 Config Server 에게 알려준다.(localhost:8888/actuator/busrefresh)

  • Message Broker 가 해당 메시지를 Subscribing 하고 있는 Application 들 에게 Broadcasting 한다.

  • 각각의 Application 은 Spring Cloud Bus 가 받은 설정 정보를 반영한다.

    AMQP: 메세지 기반 미들웨어위한 프로토콜

    	- 메시지 지향, 큐잉, 라우팅(P2P, Pub-Sub), 신뢰성, 보안
    	- Erlang, RabbitMQ에서 사용

    Kafka

    	- 분산형 스트리밍 플랫폼
    	- 대용량 데이터 처리 가능한 메시징 시스템

    RabbitMQ vs Kafka

    	- RabbitMQ: 적은 데이터 안정적, 시스템 간 메시지 전달, 소비자 중심 
    	- Kafka: 대용량 적합, Pub/Sub, Topic에 메시지 전달, 생산자 중심

서비스간 통신 방법

https://wonit.tistory.com/507

동기: RestTemplate

RestTemplate Bean으로 등록하고 Service Layer에서 주입시켜줌

UserServiceImpl

      @Override
    public UserDto getUserByUserId(String userId) {
        UserEntity userEntity = userRepository.findByUserId(userId);

        if (userEntity == null)
            throw new UsernameNotFoundException("User not found");

        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);

          String tmp = "http://127.0.0.1:8000/order-service/%s/orders";
          String orderUrl = String.format(tmp, userId);
          ResponseEntity<List<ResponseOrder>> orderListRespone =
                  restTemplate.exchange(orderUrl, HttpMethod.GET, null,
                          new ParameterizedTypeReference<List<ResponseOrder>>() {
          });
          List<ResponseOrder> orderList = orderListRespone.getBody();

        userDto.setOrders(orderList);
        return userDto;
    }

Error 처리는 try catch문으로 해야 한다

특징

  • Restful 원칙을 지킨다
  • url, HTTP Method, Request Body, Response Data Type Reference 를 알아야한다.
  • 하드 코딩이다

동기: Feign

OrderServiceClient

  @FeignClient(name="order-service")
public interface OrderServiceClient {

    @GetMapping("/order-service/{userId}/orders")
    List<ResponseOrder> getOrders(@PathVariable String userId);
}
  

특징

  • Client에 있는 Interface작성하려면 다른 서비스 내용을 잘 알아야 한다

  • FeignClient 라는 어노테이션을 이용하면 직접 해당 URL을 명시하지 않더라도 Eureka 에 register한 Instance 이름을 찾아서 URL을 매핑해준다. 이를 호출할 인터페이스를 생성해야한다

  • 객체의 책임에 있어서 orderService 호출 부분이 바뀌면 client쪽이 바뀌면 된다. 해당 Service의 역할이 아니다

    UserServiceImpl

      @Override
    public UserDto getUserByUserId(String userId) {
        UserEntity userEntity = userRepository.findByUserId(userId);

        if (userEntity == null)
            throw new UsernameNotFoundException("User not found");

        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);

        List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);
        userDto.setOrders(orderList);
        return userDto;
    }

Error 처리: 예외 처리를 핸들링하는 방법을 ErrorDecoder로 제공

  
  @Component
public class FeignErrorDecoder implements ErrorDecoder {
    Environment env;

    @Autowired
    public FeignErrorDecoder(Environment env) {
        this.env = env;
    }

    @Override
    public Exception decode(String methodKey, Response response) {
        switch(response.status()) {
            case 400:
                break;
            case 404:
                if (methodKey.contains("getOrders")) {
                    return new ResponseStatusException(HttpStatus.valueOf(response.status()),
                            env.getProperty("order_service.exception.orders_is_empty"));
                }
                break;
            default:
                return new Exception(response.reason());
        }

        return null;
    }
}
  

Client에 있는 Interface작성하려면 다른 서비스 내용을 잘 알아야 한다
FeignClient 라는 어노테이션을 이용하면 직접 해당 URL을 명시하지 않더라도 Eureka 에 register한 Instance 이름을 찾아서 URL을 매핑해준다.

분산추적: Zipkin

분산환경에서 시스템 병목 현상 파악

Span

  • 하나의 요청에 사용되는 작업 단위

    Trace

  • 트리 구조로 이루어진 span 셋

  • 하나의 요청에 대한 같은 Trace ID 발급

    Spring Coud Sleuth: Log file을 Zipkin에 전달. 요청 값에 따른 Trace ID, Span ID 부여

    OpenTracing은 애플리케이션 간 분산 추적을 위한 표준. 이 표준의 대표적인 구현체로 Jaeger와 Zipkin이 있지만 Zipkin이 구현하기 더 쉬움

    장점

  • 시스템이 분산되있다보니 log 정보로 에러 원인을 찾기가 힘들다. Zipkin 사용하면 traceID과 Zipkin UI 통해 전체 흐름 찾을 수 있다. 웹 UI에서 ID를 넣고 검색하면 서비스 A와 B, C의 호출 순서를 타임라인과 함께 볼 수 있으며 에러가 발생한 구간도 명확하게 표시되는 것을 확인 가능하다

    https://engineering.linecorp.com/ko/blog/line-ads-msa-opentracing-zipkin/

데이터 동기화 문제

하나의 DB 사용


트랜잭션 관리, 동시성

DB 간 동기화 - Kafka

변경된 데이터가 있으면 MQ통해 주고받는다

Reference
https://heodolf.tistory.com/49
https://wonit.tistory.com/512

0개의 댓글