๐ก Zipkin
ํํ์ด์ง ๋งํฌ
๋ถ์ฐ ์ถ์ ์ ํ๊ธฐ ์ํด์ ์ถ์ ์ ๋ณด ์ฆ ํธ๋ ์ด์ฑ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ธฐ ์ํ ์๋น์ค๊ฐ Zipkin์ด๋ค.
- Twitter์์ ์ฌ์ฉํ๋ ๋ถ์ฐ ํ๊ฒฝ์ Timing ๋ฐ์ดํฐ ์์ง, ์ถ์ ์์คํ (์คํ์์ค)
- Google Drapper์์ ๋ฐ์ ํ์์ผ๋ฉฐ, ๋ถ์ฐ ํ๊ฒฝ์์์ ์์คํ ๋ณ๋ชฉ ํ์ ํ์
- collerctor, Query Service, databaem WebUI๋ก ๊ตฌ์ฑ
Span
- ํ๋์ ์์ฒญ์ ์ฌ์ฉ๋๋ ์์ ๋จ์
- 64 bit unique ID
Trace
- ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ์ด๋ฃจ์ด์ง Span set
- ํ๋์ ์์ฒญ์ ๋ํ ๊ฐ์ Trace ID ๋ฐ๊ธ
๐ก Spring Cloud Sleuth
ํํ์ด์ง ๋งํฌ
๋ง์ดํฌ๋ก ์๋น์ค๊ฐ ์ฌ๋ ๋์ด ์๋ ์ํ ๊ฐ์ ์ถ์ ํด์ ๋๊ฐ ๋๊ตฌ๋ฅผ ํธ์ถํ๊ณ ์๊ฐ์ด ์ผ๋ง๋ ๊ฑธ๋ ธ์ผ๋ฉด ์ ์ ์ํ์ธ์ง ๋น์ ์ ์ํ์ธ์ง๋ฅผ ์๋ ค์ฃผ๊ณ ์๊ฐํ ์์ผ์ฃผ๋ ๋๊ตฌ
- ์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์ ์ Zipkin๊ณผ ์ฐ๋
- ์์ฒญ ๊ฐ์ ๋ฐ๋ฅธ Trace ID, Span ID ๋ถ์ฌ
- Trace์ Span IDs๋ฅผ ๋ก๊ทธ์ ์ถ๊ฐ ๊ฐ๋ฅ
- servlet filter
- rest template
- scheduled actions
- message channels
- feign client
Zipkin์ ๋ง์ดํฌ๋ก ์๋น์ค ๊ฐ์ ์์ฐจ์ ์ธ ๋ฐ์ดํฐ ํธ์ถ๊ด๊ณ์ ๋ํด ๋ฐ์ดํฐ ์ด๋ ฅ์ ๋ณด๊ด(ํธ๋ ์ด์ฑ)ํ๊ณ
Sluth๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์คํ๋ง ๋ถํธ์์ ๋ฐ์ํ๋ ๋ก๊ทธ ๋ฐ์ดํฐ ๊ฐ์ ์งํ ์๋ฒ๋ก ์ ๋ฌ์์ผ์ฃผ๋ ์ญํ
Zipkin ์ค์น
- Zipkin ์ค์น
curl -sSL https://zipkin.io/quickstart.sh | bash -s
zipkin.jar ํ์ผ์ด ์ค์น๋์๋ค.
- ํ์ผ ์คํ
java -jar zipkin.jar
- ํฌํธ๋ฒํธ 9411๋ก ๋ค์ด๊ฐ ๋ณด์.
Spring Cloud Sleuth + Zipkin์ ์ด์ฉํ Microservice์ ๋ถ์ฐ ์ถ์
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:
application:
name: user-service
zipkin:
base-url: http://localhost:9411
enabled: true #zipkin ์๋ฒ ์๋ O
sleuth:
sampler:
probability: 1.0 # ๋ฐ์ํ ๋ก๊ทธ๋ฅผ 100%๋ณด์ฌ์ฃผ๊ฒ ๋ค.
- UserServiceImpl
circuitBreaker ์์ฑํ ๊ณณ์ log ์ถ๊ฐ(@Slf4j ์ฌ์ฉ)
@Override
public UserDto getUserByUserId(String userId) {
UserEntity userEntity = userRepository.findAllByUserId(userId);
if (userEntity == null)
throw new UsernameNotFoundException("User not found");
UserDto userDto = new ModelMapper().map(userEntity, UserDto.class); // (๋ฐ๊พธ๊ณ ์ถ์ ๋ณ์, ๋ฐ๊พธ๊ณ ์ถ์ "ํด๋์ค")
// ...์๋ต...
/* ErrorDecoder */
log.info("Before call orders microservice"); ๐ ์ฌ๊ธฐ! log ์ถ๊ฐ!!
CircuitBreaker circuitbreaker = circuitBreakerFactory.create("circuitbreaker"); // circuitbreaker ์์ฑ
List<ResponseOrder> orderList = circuitbreaker.run(() -> orderServiceClient.getOrder(userId), throwable -> new ArrayList<>()); // circuitbreaker ์คํ
// run(() -> ์ ์์๋ํ ๊ฒฝ์ฐ ๋ฐํ๊ฐ, ๋ฌธ์ ์๊ฒผ์ ๊ฒฝ์ฐ ๋ฐํ๊ฐ)
log.info("After call orders microservice"); ๐ ์ฌ๊ธฐ! log ์ถ๊ฐ!!
userDto.setOrders(orderList);
return userDto;
}
Order Service
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:
application:
name: user-service
zipkin:
base-url: http://localhost:9411
enabled: true #zipkin ์๋ฒ ์๋ O
sleuth:
sampler:
probability: 1.0 # ๋ฐ์ํ ๋ก๊ทธ๋ฅผ 100%๋ณด์ฌ์ฃผ๊ฒ ๋ค.
- OrderServiceImpl
log ์ถ๊ฐ(@Slf4j ์ฌ์ฉ)
@PostMapping("/{userId}/orders")
public ResponseEntity<ResponseOrder> createOrder(@PathVariable("userId") String userId, @RequestBody RequestOrder orderDetails) {
log.info("Before add orders data"); ๐ ์ฌ๊ธฐ! log ์ถ๊ฐ!!
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
OrderDto orderDto = mapper.map(orderDetails, OrderDto.class);
orderDto.setUserId(userId);
/* jpa */
OrderDto createdOrder = orderService.createOrder(orderDto);
ResponseOrder responseOrder = mapper.map(createdOrder, ResponseOrder.class);
log.info("After add orders data"); ๐ ์ฌ๊ธฐ! log ์ถ๊ฐ!!
return ResponseEntity.status(HttpStatus.CREATED).body(responseOrder);
}
Test 1
PostMan์์ ํ์๊ฐ์ ๊ณผ ๋ก๊ทธ์ธ ํ ํ์์ ๋ณด ์กฐํ ์คํ
ํ์์ ๋ณด๋ฅผ ์กฐํํ๋ ํ๋์ ์์ฒญ์ด๋ฏ๋ก ํ๋์ trace ID์ด๋ค.
๋์ผํ trace ID์ ๋ค๋ฅธ span ID์ธ ๊ฒ์ ํ์ธํ ์ ์๋ค.
Zipkin server์์ ํ์ธํด๋ณด์(port : 9411)
๊ฒ์๋ฐฉ๋ฒ โ
trace ID๋ฅผ ๊ฒ์ํด์ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์๋ค.
Zipkin์ ๋ง์ดํฌ๋ก ์๋น์ค ๊ฐ์ ์์ฐจ์ ์ธ ๋ฐ์ดํฐ ํธ์ถ๊ด๊ณ์ ๋ํด ๋ฐ์ดํฐ ์ด๋ ฅ์ ๋ณด๊ด(ํธ๋ ์ด์ฑ)ํ๊ณ
Sluth๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์คํ๋ง ๋ถํธ์์ ๋ฐ์ํ๋ ๋ก๊ทธ ๋ฐ์ดํฐ ๊ฐ์ ์งํ ์๋ฒ๋ก ์ ๋ฌ์์ผ์ฃผ๋ ์ญํ
๊ฒ์๋ฐฉ๋ฒ โก
ServiceName์ผ๋ก๋ ๊ฒ์ํ ์ ์๋ค.
๊ฒ์๋ฐฉ๋ฒ โข
Dependencies๋ฅผ ๋ณผ ์ ์๋ค. ๋ ์ง๋ณ๋ก ํธ์ถ์ ๋ณด๋ฅผ ์๊ฐํํด์ ๋ณด์ฌ์ค๋ค.
์๋ฌ์ฌ๋ถ๊ฐ ์์๋์ง๋ ํ์ธ ๊ฐ๋ฅํ ๋์ฌ๋ณด๋์ด๋ค.
Test 2 ์ค๋ฅ ๊ฐ์
๊ฐ์ ๋ก ์ค๋ฅ ๋ฐ์ํ์ ๋๋ฅผ ๊ฐ์ ํด๋ณด์!
Order Serivce
- OrderController
@SneakyThrows
@GetMapping("/{userId}/orders")
public ResponseEntity<List<ResponseOrder>> getOrder(@PathVariable("userId") String userId) {
log.info("Before retrieve orders data");
Iterable<OrderEntity> orderList = orderService.getOrdersByUserId(userId);
List<ResponseOrder> result = new ArrayList<>();
orderList.forEach(v -> result.add(new ModelMapper().map(v, ResponseOrder.class)));
try { // ๐ ์ฌ๊ธฐ! ์ค๋ฅ ์ถ๊ฐ
Thread.sleep(1000);
throw new Exception("์ฅ์ ๋ฐ์");
} catch(InterruptedException ex) {
log.warn(ex.getMessage());
}
log.info("After retrieve orders data");
return ResponseEntity.status(HttpStatus.OK).body(result);
}
Zipkin ์๋ฒ์์ ํ์ธํด๋ณด์
์ ์ฉํ๋ ์ค๋ฅ๋ฅผ ํ์ธ ํ ์ ์๋ค.
Dependencies ํ์ธ
๋ง๋ฌด๋ฆฌ
zipkin์ ํ์ฉํ๋ฉด ์ด๋ ๊ธฐ๋ฅ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋์ง ์ฝ์์ ํ๋ํ๋ ์ฐ์ด ํ์ธํ์ง ์์๋, ์ฝ๊ฒ ์ถ์ ํ ์ ์์ด์ ์ข๋ค.
1. ์๋ฌ๊ฐ ์ด๋์ ๋ฐ์ํ๋์ง ์ ์ ์์ด์๋ ์ข์ง๋ง(๋ถ์ฐ์ถ์ )
2. ํธ์ถ ๋ด์์ ์ข ์์ฑ์ ํ์ธ ํ ์ ์์ด์๋ ์ข๋ค! ํ๊ฐ์ ํธ์ถ์ ํด๋นํ๋ ๊ด๊ณ๋ฅผ trace ID๋ก ์ ์ ์๋ค.(์๋น์ค ๋งคํ)
3. ์ด๋ฅผ ์๊ฐํํด์ ๋ณผ ์ ์๋ค๋ ์ ์์ ์ดํดํ๊ธฐ ๋์ฑ ์ข๋ค!(์๊ฐํ)
์ด ์ธ์ ์ฅ์ ๋ ์กด์ฌํ๋ค.
ํ ๋ฐ ๋ ๋์๊ฐ ๋ง์ดํฌ๋ก ์๋น์ค๊ฐ ํ์ฌ ๊ฐ์ง๊ณ ์๋ ๋ฉ๋ชจ๋ฆฌ ์ํ๋ ํธ์ถ๋๋ ์ ํํ ํ์๋ ์ถ๊ฐ์ ์ผ๋ก ๋ชจ๋ํฐ๋ง์ด ๊ธฐ๋ฅ์ ๋ฃ์ด์ผ ํ๋ค! ๋ค์ ๊ฒ์๋ฌผ ์ฐธ๊ณ !
๐ฅ Spring Boot 3.2 + Spring Cloud 2023.0.0 ๋ฒ์ ์ด์
๋ฒ์ ์ ๋์์ง ์ํ์์๋ Dendency ๋ฑ ๋ง์๊ฒ ๋ฐ๋ ๋ฏ ํ๋ค.
์์ ์ค์ ์ ์์ ๋ฒ์ ์ผ๋ก ์ต์ ๋ฒ์ ์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์๋ ๋งํฌ๋ฅผ ํ์ธํด์ ๋ค์ ์งํํด์ผํ๋ค.