3 분 소요

이전 글에서는, 마이크로 서비스 간 통신에서 문제가 발생했을 때, circuit breaker를 사용해 연쇄적으로 문제가 발생하는 것을 방지하는 방법을 알아봤다.

이번에는, 하나의 어플리케이션이 기동될 때, 여러 마이크로 서비스를 사용할텐데,
이 때 해당하는 요청 정보가 어떻게 실행되고, 어느 단계를 거쳐 어떤 마이크로 서비스로 이동하는지 추적하는 방법에 대해 살펴보자.
이런 내용을 마이크로 서비스의 “분산 추적” 이라고 한다.

개요

Zipkin

스크린샷 2022-10-13 오전 10 54 33

  • https://zipkin.io/
  • Twitter에서 사용하는 분산 환경의 Timing 데이터 수집, 추적 시스템 (오픈소스)
  • Google Drapper에서 발전하였으며, 분산 환경에서의 시스템 병목 현상 파악
  • Collector, Query Service, Database, WebUI로 구성
  • Span
    - 하나의 요청에 사용되는 작업의 단위
    - 64 bit unique ID
  • Trace
    - 트리 구조로 이뤄진 Span 셋
    - 하나의 요청에 대한 같은 Trace ID 발급

Zipkin 설치 및 실행

# 설치
$ curl -sSL https://zipkin.io/quickstart.sh | bash -s

# 실행
$ java -jar zipkin.jar

설치
스크린샷 2022-10-13 오전 11 06 29

실행
스크린샷 2022-10-13 오전 11 07 30
zipkin의 default 포트번호는 9411번이다.

http://127.0.0.1:9411에 접속해보자.
스크린샷 2022-10-13 오전 11 09 24

Spring Cloud Sleuth

스크린샷 2022-10-13 오전 11 00 45

  • 스프링 부트 애플리케이션을 Zipkin과 연동 -> 가지고 있는 로그 파일 데이터 혹은 스트리밍 데이터 값을 Zipkin에 전달하는 역할
  • 요청 값에 따른 Trace ID, Span ID 부여
  • Trace와 Span Ids를 로그에 추가 기능
    - servlet filter
    - rest template
    - scheduled actions
    - message channels
    - feign client

Spring Cloud Sleuth + Zipkin

user-service

pom.xml

<!-- Zipkin -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
    <version>2.2.3.RELEASE</version>
</dependency>

application.yml

spring:
    zipkin:
        base-url: http://127.0.0.1:9411
        enabled: true
    sleuth:
        sampler:
            probability: 1.0

UserServiceImpl.java

@Service
@Slf4j
public class UserServiceImpl implements UserService {
    ...
    @Override
    public UserDto getUserByUserId(String userId) {

        UserEntity userEntity = userRepository.findByUserId(userId);
        UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);

        log.info("Before call orders microservice"); // 🌟 로그 생성
        CircuitBreaker circuitbreaker = circuitBreakerFactory.create("circuitbreaker");
        List<ResponseOrder> orderList = circuitbreaker.run(() -> orderServiceClient.getOrders(userId),
            throwable -> new ArrayList<>());
        log.info("After called orders microservice"); // 🌟 로그 생성

        userDto.setOrders(orderList);

        return userDto;
    }
}

order-service

pom.xml

이전에 user-service와 마찬가지로 두 가지 dependency를 추가한다.

application.yml

이전에 user-service와 마찬가지로 zipkin과 sleuth 설정 정보를 추가한다.

OrderController.java

@RestController
@RequestMapping("/order-service")
@Slf4j
public class OrderController {

    @PostMapping("/{userId}/orders")
    public ResponseEntity<ResponseOrder> createOrder(@PathVariable("userId") String userId, @RequestBody RequestOrder requestOrder) {
        log.info("Before add orders data"); // 🌟 로그 생성
        ...
        log.info("After added orders data"); // 🌟 로그 생성
        return ResponseEntity.status(HttpStatus.CREATED).body(responseOrder);
    }

    @GetMapping("/{userId}/orders")
    public ResponseEntity<List<ResponseOrder>> getOrder(@PathVariable("userId") String userId) {
        log.info("Before retrieve orders data"); // 🌟 로그 생성
        ...
        log.info("After retrieved orders data"); // 🌟 로그 생성
        return ResponseEntity.status(HttpStatus.OK).body(result);
    }
}

테스트

이제 아래 순서대로 서버를 기동하자.

  • eureka 서버
  • rabbitmq 서버
  • configuration 서버 (config-service)
  • gateway 서버 (gateway-service)
  • zipkin 서버
  • user-service
  • order-service

이제 상품을 두 번 주문한 뒤, order-service의 인텔리제이 콘솔을 확인하자.
스크린샷 2022-10-13 오전 11 54 54
이 때, 앞에 있는게 trace id이고 뒤에 있는게 order id이다.

trace id를 복사해 웹 브라우저(http://localhost:9411)에서 확인해보자.
스크린샷 2022-10-13 오전 11 49 37
그럼 위와 같은 정보를 얻을 수 있다.

이제 사용자 단건 조회를 해보자.
요청을 한 뒤, user-service의 인텔리제이 콘솔을 확인하자.
스크린샷 2022-10-13 오전 11 56 35
아까 order-service에서 확인한 trace iduser-service에 찍힌 trace id가 같음을 확인할 수 있다.
같은 trace id를 가진다는 것은 같은 요청이라는 의미이다.

이제 이 정보를 아까처럼 웹 브라우저에서 확인해보자.
스크린샷 2022-10-13 오후 12 02 55
user-service에서 get 요청을 한 뒤, order-service를 호출한 것을 알 수 있다. (사용자 주문 내역을 가져오기 위해)

이번에는 다른 기능을 이용해보자.
상단에 보이는 Find a trace 버튼을 클릭 -> + 버튼 클릭 -> serviceName 탭을 클릭 -> user-service 선택 -> RUN QUERY
스크린샷 2022-10-13 오후 12 06 24
이는 유저 서비스를 3번에 걸쳐 호출되었고, 오더 서비스를 1번 호출되었다는 의미이다.

이번에는 Find a trace 옆에 있는 Dependencies를 클릭해보자.
스크린샷 2022-10-13 오후 12 09 03
이는 엮여있는 서비스들을 나타내며, order-service를 클릭하게 되면, 몇 번의 호출 중 몇 번 에러가 발생했는지 나타난다.

오류 테스트

마지막으로, 오류가 발생했을 때를 가정해보자.

order-service

OrderController.java

@RestController
@RequestMapping("/order-service")
@Slf4j
public class OrderController {
    ...
    @GetMapping("/{userId}/orders")
    public ResponseEntity<List<ResponseOrder>> getOrder(@PathVariable("userId") String userId) {
        ...
        /* 오류 테스트 */
        try {
            Thread.sleep(1000);
            throw new Exception("장애 발생");
        } catch(InterruptedException ex) {
            log.warn(ex.getMessage());
        }

        log.info("After retrieved orders data"); // 🌟 로그 생성
        return ResponseEntity.status(HttpStatus.OK).body(result);
    }
}

이제 order-service 재기동 후 다시 주문을 해보자.
그리고 다시 사용자 단건 조회를 하게되면, order-service에서 예외가 발생했기 때문에 주문 목록이 비어있을 것이다.

이제 웹 브라우저를 확인해보자.
user-service의 콘솔에서 가장 최근 요청(사용자 단건조회)의 trace id를 복사한 뒤 검색해보자.
스크린샷 2022-10-13 오후 12 39 07
그럼 위 사진처럼 붉은 색으로 오류가 발생했음을 알 수 있고, ORDER-SERVICE를 클릭해보면, 아까 OrderContoller에 정의한 “장애 발생” 문구를 확인할 수 있다.

그리고 Find a trace에서 order-service를 검색해보면,
스크린샷 2022-10-13 오후 12 41 05
역시 붉은 색으로 오류가 발생했음을 알 수 있다.

이번엔 Dependencies에서 확인해보자.
스크린샷 2022-10-13 오후 12 41 58
오류가 발생한 건 수를 확인할 수 있다.



💛 개인 공부 기록용 블로그입니다. 👻

맨 위로 이동하기