๋ณธ ๊ธ์ ๋ง์ดํฌ๋ก์๋น์ค์ ๋ถ์ฐ ์ถ์ ์๋ฆฌ์ฆ๋ก ์ด๋ก ๊ณผ ์ค์ต์ด ํจ๊ป ํฌํจ๋ ์๋ฆฌ์ฆ์ ๋๋ค. ์๋ ๋ชฉ์ฐจ์ ํ์๋ ๊ธ์ ๋ชจ๋ ์ฐธ๊ณ ํ๋ฉด ์ข์ต๋๋ค.
๋ชฉ์ฐจ
- Distributed Tracing, ๋ถ์ฐ ์ถ์ ์ด๋?
- Spring Cloud Sleuth ์ Zipkin
- Sleuth ๋ก Http ํ๊ฒฝ์ ๋ถ์ฐ ์ถ์ ์ค์ต
- Sleuth ๋ก Messaging ํ๊ฒฝ์ ๋ถ์ฐ ์ถ์ ์ค์ต
์ค์ต์ ๋ํ ์์ค์ฝ๋๋ฅผ ํ์ธํ์๊ณ ์ถ๋ค๋ฉด ์ค์ต github์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
Spring Cloud Sleuth ๋ฅผ ์ด์ฉํ HTTP ํ๊ฒฝ์ ๋ถ์ฐ ์ถ์
์ด๋ฒ์ ์ง๋ ์๊ฐ์ ์์๋ณด์๋ ๋ถ์ฐ ์ถ์ ์ ๋ํ ์ค์ต์ ์งํํด๋ณด๋ ค ํ๋ค.
์ฐ์ ๊ฐ๋ต์ ์ธ ์ํคํ ์ฒ๋ ๋ค์๊ณผ ๊ฐ๋ค
์ ์ํคํ ์ฒ๋ฅผ ๊ตฌ๋ถํ์๋ฉด 2๊ฐ์ ๋ถ๋ถ์ผ๋ก ๋๋ ์ ์๋ค.
- HTTP API
- 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 ํ๋ ์์คํ ์ ๋ง๋ค๊ณ , ์ด๋ค์ ๋ถ์ฐ ์ถ์ ํด๋ณด๋ ค ํ๋ค.
๋๊ธ