내용 다 날라감. 티스토리 일 안해?
1. SAGA Pattern
분산 트랜잭션의 주요 개념으로 트랜잭션을 여러 단계로 분할하여 각 단계를 독립적으로 실행하고,
실패 시 보상 트랜잭션을 수행하여 롤백함.
✉️ 메세지의 흐름
- 주문이 정상 작동한다면 : Order -ProductQueue-> Product -PaymentQueue-> Payment
- 주문에 에러가 난다면 : Payment -ProductErrQueue-> Product -OrderErrQueue-> Order
1) Order Application
- OrderApplicationQueueConfig
package com.market.order;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OrderApplicationQueueConfig {
@Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
@Value("${message.exchange}")
private String exchange;
@Value("${message.queue.product}")
private String queueProduct;
@Value("${message.queue.payment}")
private String queuePayment;
@Value("${message.err.exchange}")
private String exchangeErr;
@Value("${message.queue.err.order}")
private String queueErrOrder;
@Value("${message.queue.err.product}")
private String queueErrProduct;
// 정상
@Bean public TopicExchange exchange() { return new TopicExchange(exchange); }
@Bean public Queue queueProduct() { return new Queue(queueProduct); }
@Bean public Queue queuePayment() { return new Queue(queuePayment); }
@Bean public Binding bindingProduct() { return BindingBuilder.bind(queueProduct()).to(exchange()).with(queueProduct); }
@Bean public Binding bindingPayment() { return BindingBuilder.bind(queuePayment()).to(exchange()).with(queuePayment); }
// 에러
@Bean public TopicExchange exchangeErr() { return new TopicExchange(exchangeErr); }
@Bean public Queue queueErrOrder() { return new Queue(queueErrOrder); }
@Bean public Queue queueErrProduct() { return new Queue(queueErrProduct); }
@Bean public Binding bindingErrOrder() { return BindingBuilder.bind(queueErrOrder()).to(exchangeErr()).with(queueErrOrder); }
@Bean public Binding bindingErrProduct() {return BindingBuilder.bind(queueErrProduct()).to(exchangeErr()).with(queueErrProduct); }
}
- OrderService
package com.market.order;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
@Value("${message.queue.product}")
private String productQueue;
private final RabbitTemplate rabbitTemplate;
private Map<UUID, Order> orderStore = new ConcurrentHashMap<>();
public Order createOrder(OrderEndpoint.OrderRequestDto orderRequestDto) {
Order order = orderRequestDto.toOrder();
orderStore.put(order.getOrderId(), order);
DeliveryMessage deliveryMessage = orderRequestDto.toDeliveryMessage(order.getOrderId());
rabbitTemplate.convertAndSend(productQueue, deliveryMessage);
return order;
}
public Order getOrder(UUID orderId) {
return orderStore.get(orderId);
}
public void rollbackOrder(DeliveryMessage deliveryMessage) {
Order order = orderStore.get(deliveryMessage.getOrderId());
order.cancelOrder(deliveryMessage.getErrorType());
}
}
- 결과
ProductQueue에 들어온 메세지인 Order 객체 정보를 모두 확인할 수 있다.
- 에러처리
2) Product Application
- ProductApplicationQueueConfig
package com.market.product;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ProductApplicationQueueConfig {
@Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
- ProductService
package com.market.product;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@Slf4j
@Service
@RequiredArgsConstructor
public class ProductService {
private final RabbitTemplate rabbitTemplate;
@Value("${message.queue.payment}")
private String paymentQueue;
@Value("${message.queue.err.order}")
private String orderErrorQueue;
public void reduceProductAmount(DeliveryMessage deliveryMessage) {
Integer productId = deliveryMessage.getProductId();
Integer productQuantity = deliveryMessage.getProductQuantity();
if (productId != 1 || productQuantity > 1) {
this.rollbackProduct(deliveryMessage);
return;
}
rabbitTemplate.convertAndSend(paymentQueue,deliveryMessage);
}
public void rollbackProduct(DeliveryMessage deliveryMessage) {
log.info("PRODUCT ROLLBACK");
// Product에서 에러가 발생한 경우 굳이 Payment까지 동작하게 하지 않고 에러 처리
if (!StringUtils.hasText(deliveryMessage.getErrorType())) {
deliveryMessage.setErrorType("PRODUCT ERROR");
}
rabbitTemplate.convertAndSend(orderErrorQueue,deliveryMessage);
}
}
- 결과
- 에러처리
Order -> Product일 때, Product 에러가 난다면 굳이 Payment까지 안 가도 Product에서 에러처리 하면 된다.
3) Payment Application
- PaymentApplicationQueueConfig
Product랑 이름만 다르고 똑같음.
- PaymentService
package com.market.payment;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class PaymentService {
@Value("${message.queue.err.product}")
private String productErrorQueue;
private final RabbitTemplate rabbitTemplate;
public void createPayment(DeliveryMessage deliveryMessage) {
Payment payment = Payment.builder()
.paymentId(UUID.randomUUID())
.userId(deliveryMessage.getUserId())
.payStatus("SUCCESS")
.build();
Integer payAmount = deliveryMessage.getPayAmount();
if (payAmount >= 10000) {
log.error("Payment amount exceeds limit: {}", payAmount);
deliveryMessage.setErrorType("PAYMENT_LIMIT_EXCEEDED");
this.rollbackPayment(deliveryMessage);
}
}
public void rollbackPayment(DeliveryMessage deliveryMessage) {
log.info("PAYMENT ROLLBACK");
rabbitTemplate.convertAndSend(productErrorQueue, deliveryMessage);
}
}
- 결과
- 에러처리
2. JMeter
Apache JMeter - Download Apache JMeter
Download Apache JMeter We recommend you use a mirror to download our release builds, but you must verify the integrity of the downloaded files using signatures downloaded from our main distribution directories. Recent releases (48 hours) may not yet be ava
jmeter.apache.org
# 실리콘 맥
# Binaries의 압축파일을 다운로드
# 해당 폴더 안에 bin 폴더에서 jmeter.sh 실행
cd apache-jmeter-5.6.3
./bin/jmeter.sh
1) 쓰레드 그룹 추가
테스트 계획 > 추가 > 쓰레드들 > 쓰레드 그룹 추가
- 쓰레드 속성 입력
- 100명의 사용자가 각 10번씩 요청하여 1000번의 요청이 이루어지는 테스트 시나리오
2) HTTP 요청
쓰레드 그룹 > 추가 > 표본추출기 > HTTP 요청
- HTTP 요청 항목 입력
- createOrder
3) HTTP 헤더 관리자 추가
HTTP 요청 > 추가 > 설정 엘리먼트 > HTTP 헤더 관리자 추가
- 헤더관리자 속성 추가
4) 결과 트리 보기 및 요약 보고서
쓰레드 그룹 > 추가 > 리스너 > 결과들의 트리 보기 및 요약 보고서 추가
5) 실행
- 요약보고서
'TIL' 카테고리의 다른 글
TIL24. 업체 만들기 (0) | 2025.03.17 |
---|---|
TIL23. 공통 모듈 만들기 - Exception, BaseEntity (0) | 2025.03.14 |
TIL21. 물류 관리 및 배송 시스템 프로젝트 시작!! (0) | 2025.03.12 |
TIL20. Kafka (0) | 2025.03.11 |
TIL19. RabbitMQ (0) | 2025.03.10 |