๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • ์žฅ์›์ต ๊ธฐ์ˆ ๋ธ”๋กœ๊ทธ
๐Ÿ’Š Java & Kotlin & Spring/- spring framework +

[Distributed Tracing] HTTP ํ™˜๊ฒฝ์—์„œ์˜ ๋ถ„์‚ฐ ์ถ”์  ์‹ค์Šตํ•˜๊ธฐ

by Wonit 2022. 5. 1.

๋ณธ ๊ธ€์€ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์˜ ๋ถ„์‚ฐ ์ถ”์  ์‹œ๋ฆฌ์ฆˆ๋กœ ์ด๋ก ๊ณผ ์‹ค์Šต์ด ํ•จ๊ป˜ ํฌํ•จ๋œ ์‹œ๋ฆฌ์ฆˆ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜ ๋ชฉ์ฐจ์— ํ‘œ์‹œ๋œ ๊ธ€์„ ๋ชจ๋‘ ์ฐธ๊ณ ํ•˜๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค.

 

๋ชฉ์ฐจ

 

 
์‹ค์Šต์— ๋Œ€ํ•œ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜์‹œ๊ณ  ์‹ถ๋‹ค๋ฉด ์‹ค์Šต github์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 


Spring Cloud Sleuth ๋ฅผ ์ด์šฉํ•œ HTTP ํ™˜๊ฒฝ์˜ ๋ถ„์‚ฐ ์ถ”์ 

 

์ด๋ฒˆ์€ ์ง€๋‚œ ์‹œ๊ฐ„์— ์•Œ์•„๋ณด์•˜๋˜ ๋ถ„์‚ฐ ์ถ”์ ์— ๋Œ€ํ•œ ์‹ค์Šต์„ ์ง„ํ–‰ํ•ด๋ณด๋ ค ํ•œ๋‹ค.

 

์šฐ์„  ๊ฐœ๋žต์ ์ธ ์•„ํ‚คํ…์ฒ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค

 

 

์œ„ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ตฌ๋ถ„ํ•˜์ž๋ฉด 2๊ฐœ์˜ ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋‹ค.

 

  1. HTTP API
  2. Messaging

 

HTTP ํ™˜๊ฒฝ์—์„œ๋Š” Polygrat ํ•œ ํ˜„์‹ค ์„ธ๊ณ„์˜ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋ฐ˜์˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑ์„ ํ•˜์˜€๋‹ค.

 

  • user-service : express, node.js
  • order-service : spring boot, java
  • delivery-service : spring boot, java & aws sqs
  • notification-service : spring boot, java & aws sqs

 

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํ˜ธ์ถœ Flow

 

์‹ค์Šต์— ๋Œ€ํ•œ ํ˜ธ์ถœ์˜ flow ๋ฅผ ๋น„์ฆˆ๋‹ˆ์Šค์ ์ธ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ํ‘œํ˜„ํ•˜์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค

 

์‚ฌ์šฉ์ž (User) ๊ฐ€ ์ฃผ๋ฌธ (Order) ์„ ํ•œ๋‹ค๋ฉด ์‚ฌ์šฉ์ž์˜ ์ •๋ณด์™€ ์ฃผ๋ฌธ ์ƒํ’ˆ์˜ ์ •๋ณด๋ฅผ ํ† ๋Œ€๋กœ ๋ฐฐ์†ก (Delivery) ํ•˜๊ณ , ๋ฐฐ์†ก์ด ์‹œ์ž‘๋˜์—ˆ๋‹ค๋Š” ์•Œ๋ฆผ (Notification) ์„ ์ „์†กํ•œ๋‹ค.

 

์œ„ ๊ณผ์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜ธ์ถœ์ด ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค

 

  • user-service ๋Š” order-service ๋กœ HTTP GET ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค
  • order-service ๋Š” delivery-service ๋กœ HTTP POST ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค
  • ๋‹ค์Œ Messaging ๋ฐฉ์‹์—์„œ ๊ณ„์†..

 

User-Service ๊ฐœ๋ฐœํ•˜๊ธฐ

 

์šฐ์„  node ํ™˜๊ฒฝ์˜ express ์—์„œ zipkin ์— report ํ•˜๊ธฐ ์œ„ํ•œ ๋ชจ๋“ˆ์„ ์†์‰ฝ๊ฒŒ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์•„๋ž˜์—์„œ ๋‚˜์˜ค๊ฒ ์ง€๋งŒ Spring Cloud Sleuth ์—์„œ๋Š” ์ด ๊ณผ์ • ์กฐ์ฐจ๋„ auto-configuration ์ด ์ ์šฉ๋˜์–ด ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

 

express ์„œ๋ฒ„ ๋ผˆ๋Œ€ ๊ตฌ์„ฑํ•˜๊ธฐ

 

package.json ์˜์กด์„ฑ์—๋Š” ๊ธฐ๋ณธ์ ์ธ express server ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ ํ•„์š”ํ•œ ๊ฒƒ๋“ค์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค

 

$ npm install express
$ npm install axios

 

๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด index.js ๋ฅผ ๊ตฌ์„ฑํ•ด๋ณด์ž

 

index.js

const express = require("express");
const axios = require("axios");

const app = express();

app.get("/", (req, res) => {
  res.send("hello world");
});

 

๊ทธ๋Ÿฐ ๋‹ค์Œ Order-Service ๋กœ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด์„œ axios ๋ฅผ ์ด์šฉํ•˜์—ฌ Server Request ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

const express = require("express");
const axios = require("axios");

const ORDER_SERVICE_ENDPOINT = "http://localhost:8080/";

const app = express();

app.get("/", async (req, res) => {
  const result = axios.get(`${ORDER_SERVICE_ENDPOINT}`);
  res.send("[user-service] :: " + result.data);
});

 

order-service ์˜ endpoint ๋Š” 8080 ๋ฒˆ์˜ ํฌํŠธ๋กœ ์—ด๋ ค์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ฒ ๋‹ค.

 

๋งŒ์•ฝ ํ†ต์‹ ์— ์„ฑ๊ณตํ•˜๋ฉด String ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์–ป์„ ์ˆ˜ ์žˆ๋”ฐ.

 

์ด์ œ zipkin ์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž

 

$ npm install zipkin
$ npm install zipkin-instrumentation-axios
$ npm install zipkin-transport-http
$ npm install zipkin-instrumentation-express

 

์œ„์˜ ์˜์กด์„ฑ๋“ค์€ zipkin ๊ณผ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ๋Š” Reporter ๋ฅผ ๋งŒ๋“ค ๋•Œ ํ•„์š”ํ•œ ์˜์กด์„ฑ๋“ค์ด๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  index.js ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ์„ฑํ•ด๋ณด์ž.

 

index.js

const express = require("express");

const axios = require("axios");
const zipkinInstrumentationAxios = require("zipkin-instrumentation-axios");

const {
  Tracer,
  ExplicitContext,
  BatchRecorder,
  jsonEncoder,
} = require("zipkin");
const { HttpLogger } = require("zipkin-transport-http");
const zipkinMiddleware =
  require("zipkin-instrumentation-express").expressMiddleware;

const ZIPKIN_ENDPOINT = "http://localhost:9411";
const ORDER_SERVICE_ENDPOINT = "http://localhost:8080/";

const tracer = new Tracer({
  ctxImpl: new ExplicitContext(),
  recorder: new BatchRecorder({
    logger: new HttpLogger({
      endpoint: `${ZIPKIN_ENDPOINT}/api/v2/spans`,
      jsonEncoder: jsonEncoder.JSON_V2,
    }),
  }),
  localServiceName: "user-service",
});

const app = express();
app.use(zipkinMiddleware({ tracer }));

const zipkinAxios = zipkinInstrumentationAxios(axios, {
  tracer,
  serviceName: "user-service",
});

app.get("/", async (req, res) => {
  const result = await zipkinAxios.get(`${ORDER_SERVICE_ENDPOINT}`);
  res.send("[user-service] :: " + result.data);
});

const server = app.listen(8000, () => {
  console.log("[User-Service] server started");
});

 

์œ„์˜ axios ๋ฅผ ๋ณด๋‹ค์‹ถ์ด ์•Œ ์ˆ˜ ์žˆ์ง€๋งŒ, axios ๋ฅผ Zipkin Instrumentation ์œผ๋กœ ํ•œ๋ฒˆ Wrapping ํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

 

๊ทธ๋Ÿผ ๋‚ด๋ถ€์ ์œผ๋กœ Tracing ์— ๋Œ€ํ•œ ์ •๋ณด๋“ค (Span, tId) ์™€ ๊ฐ™์€ ๊ฒƒ๋“ค์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

๋‚˜๋Š” node, express ์— ๋Œ€ํ•ด์„œ ์ž˜ ์•Œ์ง€ ๋ชปํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ์ž์„ธํ•œ ์‚ฌํ•ญ์€ Tracing Express services with zipkin-js ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Order-Service ๊ฐœ๋ฐœํ•˜๊ธฐ

 

์ด์ œ Spring Cloud Sleuth ๊ฐ€ ํฌํ•จ๋œ Order-Service ์— ๋Œ€ํ•ด์„œ ๊ฐœ๋ฐœ์„ ํ•ด๋ณด์ž

 

Spring Cloud Sleuth ๋ฅผ ์ด์šฉํ•ด์„œ Zipkin ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ 2๊ฐœ์˜ ์˜์กด์„ฑ์ด ํ•„์š”ํ•˜๋‹ค

 

build.gradle

 

implementation 'org.springframework.cloud:spring-cloud-starter-sleuth:3.1.1'
implementation 'org.springframework.cloud:spring-cloud-sleuth-zipkin:3.1.1'

 

๋งŒ์•ฝ ์—ฌ๊ธฐ์„œ sleuth ๋งŒ ์ถ”๊ฐ€ํ•  ๊ฒฝ์šฐ zipkin ๊ณผ ์—ฐ๋™ํ•  ์ˆ˜ ์—†๋‹ค.

 

zipkin ๊ณผ ์—ฐ๋™ํ•˜๊ธฐ ์œ„ํ•ด์„œ sleuth-zipkin ์„ ์ถ”๊ฐ€ํ•ด์ฃผ๋„๋ก ํ•˜์ž.

 

๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ์˜ ์„ค์ • ํŒŒ์ผ์ธ application.yml ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋งŒ๋“ค์ž

 

application.yml

spring:
  application:
    name: order-service
  zipkin:
    base-url: http://localhost:9411

 

๊ทธ๋ฆฌ๊ณ  User-Service ์—์„œ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ๋ฐ›์•„์ค„ ์ˆ˜ ์žˆ๋Š” Controller ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์ž.

 

์ด์ œ ์†๋„๋ฅผ ์ข€ ๋†’์—ฌ์„œ ํ•œ ์Šคํ…์„ ๋” ๋‚˜์•„๊ฐ€ ๋ณด๊ฒ ๋‹ค.

 

์šฐ๋ฆฌ์˜ ์‹ค์Šต ํ”Œ๋กœ์šฐ๋Š”

 

์‚ฌ์šฉ์ž (User) ๊ฐ€ ์ฃผ๋ฌธ (Order) ์„ ํ•œ๋‹ค๋ฉด ์‚ฌ์šฉ์ž์˜ ์ •๋ณด์™€ ์ฃผ๋ฌธ ์ƒํ’ˆ์˜ ์ •๋ณด๋ฅผ ํ† ๋Œ€๋กœ ๋ฐฐ์†ก (Delivery) ํ•˜๊ณ , ๋ฐฐ์†ก์ด ์‹œ์ž‘๋˜์—ˆ๋‹ค๋Š” ์•Œ๋ฆผ (Notification) ์„ ์ „์†กํ•œ๋‹ค.

 

์ด๊ธฐ ๋•Œ๋ฌธ์— Order ์—์„œ ๋ฐ”๋กœ ๋‹ค์Œ ์„œ๋น„์Šค์ธ Delivery๋กœ ์š”์ฒญ์„ Relay ํ•ด์ค„ ๊ฒƒ์ด๋‹ค.

 

์ด๋ฒˆ์€ Post ๋กœ ์š”์ฒญ์„ ํ•ด๋ณด๋„๋ก ํ•˜์ž

 

OrderController.java

@RestController
@RequiredArgsConstructor
public class OrderController {

    private final RestTemplate rest;
    private final URI uri = URI.create("http://localhost:8081/delivery");

    @GetMapping("/")
    public String getOrder() {
        DeliveryRequest req = new DeliveryRequest("1004L", "seoul");
        String response = rest.postForObject(uri, req, String.class);
        return "[order-service] :: ์˜ ์ฃผ๋ฌธ :: " + response;
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeliveryRequest {
    private String userId;
    private String address;
}

 

์—ฌ๊ธฐ์„œ ์ฃผ์˜ํ•ด์•ผํ•  ์ ์ด ์žˆ๋‹ค.

 

Axios ์—์„œ zipkinAxios ๋กœ ๊ฐ์ŒŒ๋˜ ๊ฒƒ ์ฒ˜๋Ÿผ RestTemplate ๋„ Wrapping ํ•ด์•ผ ํ•œ๋‹ค.

 

์ด๋ ‡๊ฒŒ ํ•ด์ฃผ๋Š” ์ด์œ ๋Š” ์ƒˆ๋กœ์šด Span ์„ ์ƒ์„ฑํ•˜๋Š” ์ฑ…์ž„์„ Sleuth ์˜ Auto-Configuration ์ด ๋‹ด๋‹นํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค.

 

ApplicationConfig.java

 

@Configuration
public class ApplicationConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

 

์–ด๋ ค์šด ๊ณผ์ •์€ ์—†๋‹ค.

 

๋‹จ์ง€ RestTemplate ์„ Bean ์œผ๋กœ ๋“ฑ๋กํ•ด์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

 

๊ทธ๋ ‡๋‹ค๋ฉด BeanPostProcessor ์— ์˜ํ•ด์„œ Reporting ํ•˜๊ฒŒ ๋œ๋‹ค.

 

Deliver-Service ๊ฐœ๋ฐœํ•˜๊ธฐ

 

Delivery Service ๋„ ์—ญ์‹œ ์–ด๋ ค์šด ๊ฒƒ์€ ์—†๋‹ค.

 

Order-Service ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ RestTemplate ํ˜ธ์ถœ์„ ๋ฐ›์•„๋“ค์ผ ์ˆ˜ ์žˆ๋Š” Controller ํ•˜๋‚˜๊ฐ€ ํ•„์š”ํ•˜๊ณ , ์ ๋‹นํ•œ String ๋งŒ ๋ฐ˜ํ™˜ํ•ด์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋์ด๋‹ค.

 

Controller, Dto

@RestController
@RequestMapping("/delivery")
public class DeliveryController {

    @PostMapping
    public String delivery(@RequestBody DeliveryRequest request) {

        String userId = request.getUserId();
        String address = request.getAddress();

        return "[delivery-service] userId: " + userId + " address: " + address + " ๋ฐฐ์†ก ์š”์ฒญ ์™„๋ฃŒ";
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeliveryRequest {
    private String userId;
    private String address;
}

 

์—ญ์‹œ application.yml ์ด๋‚˜ build.gradle ๋˜ํ•œ Order-Service ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๊ตฌ์„ฑํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 

ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ์ ์ด ์žˆ๋‹ค๋ฉด port ์ถฉ๋Œ์„ ๋ง‰๊ธฐ ์œ„ํ•ด์„œ server port ๋ฅผ 8081๋กœ ๋ฐ”๊ฟ”์ฃผ์ž

 

Zipkin ์—ฐ๋™ํ•˜๊ธฐ

 

์ด์ œ Zipkin ๊ณผ ์—ฐ๋™ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋์ด๋‹ค.

 

์•ž์ „ ์‹œ๊ฐ„์— ์•Œ๋ ค์ฃผ์—ˆ๋˜ docker script ๋ฅผ ์ด์šฉํ•ด์„œ ๋„์›Œ๋„ ๋ฌด๋ฐฉํ•˜์ง€๋งŒ ๋‚˜๋Š” docker-compose ๋ฅผ ์ด์šฉํ•ด์„œ ๋„์› ๋‹ค.

 

๋ณ„ ๋‹ค๋ฅธ ์ด์œ ๋Š” ์—†๊ณ  ๊ทธ๋ƒฅ docker script ๋ฅผ ์™ธ์šฐ๊ธฐ ๊ท€์ฐฎ์•„์„œ compose script ๋ฅผ ์ด์šฉํ–ˆ๋‹ค.

 

docker-compose.yml

version: "3"
services:
  zipkin:
    container_name: tracer-zipkin
    image: openzipkin/zipkin
    restart: always
    ports:
      - "9411:9411"

 

๊ทธ๋ฆฌ๊ณ  up ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด์„œ zipkin ์„œ๋ฒ„๋ฅผ ๋„์›Œ๋ณด์ž

 

$ docker-compose up

 

 

๊ทธ๋ฆฌ๊ณ  9411 ํฌํŠธ๋กœ ์ ‘๊ทผํ•˜๊ฒŒ ๋˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค

 

 

์ด์ œ node ์„œ๋ฒ„์ธ 8000 ํฌํŠธ๋กœ ์š”์ฒญ์„ ๋‚ ๋ ค๋ณด์ž

 

 

๊ทธ๋Ÿผ User-Service ์—์„œ ์‹œ์ž‘๋œ ์š”์ฒญ์ด ์•„๋ž˜์™€ ๊ฐ™์€ ํ๋ฆ„์„ ํƒ€๊ณ  ํ†ต์‹ ์— ๋Œ€ํ•œ ์„ฑ๊ณต ์‘๋‹ต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

์ด ํ๋ฆ„์— ๋Œ€ํ•œ Zipkin Tracing Span ์— ๋Œ€ํ•œ ์ •๋ณด๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค

 

 

์ด์ œ ๋‹ค์Œ ์‹œ๊ฐ„์—๋Š”

 

๋‹ค์Œ ์‹œ๊ฐ„์—๋Š” Delivery ์„œ๋น„์Šค์—์„œ Message ๋ฅผ Producing ํ•˜๊ณ  Notification ์—์„œ ํ•ด๋‹น ๋ฉ”์‹œ์ง€๋ฅผ Consume ํ•˜๋Š” ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค๊ณ , ์ด๋“ค์„ ๋ถ„์‚ฐ ์ถ”์ ํ•ด๋ณด๋ ค ํ•œ๋‹ค.

๋Œ“๊ธ€