1. 문제 상황
마이크로서비스 아키텍처(MSA)에서는 각 서비스가 독립적으로 운영되기 때문에 데이터 정합성 문제가 발생할 수 있다. 기존 모놀로틱 환경에서는 하나의 트랜잭션 내에서 데이터 정합성을 보장할 수 있었지만, MSA 환경에서는 서비스 간 네트워크 통신 지연, 장애, 중복 요청, 부분 실패 등으로 인해 정합성을 유지하기 어려운 상황이 발생했다.
(1) 주요 데이터 정합성 문제
- 동기 호출 시 서비스 장애 문제:
- 한 서비스가 다른 서비스를 호출하는 도중 장애가 발생하면 데이터 불일치 가능성 증가
- 예: 주문 서비스에서 결제 요청을 보냈지만, 결제 서비스 장애로 인해 반영되지 않은 경우
- 비동기 이벤트 기반 처리 시 중복 및 유실 문제:
- 이벤트 처리 중 중복 실행이 발생하거나, 네트워크 문제로 인해 메시지가 유실될 가능성
- 예: 결제 완료 이벤트가 Kafka에서 중복 소비되는 경우
2. 해결책: Feign Client & Kafka 활용
이러한 문제를 해결하기 위해 Spring Cloud Feign Client(동기 호출)와 Kafka(비동기 메시징) 를 결합하여 데이터 정합성을 유지하는 전략을 사용했다.
3. Feign Client를 이용한 동기 호출 정합성 관리
Feign Client는 서비스 간 REST API 호출을 쉽게 할 수 있도록 지원하는 Spring Cloud의 라이브러리이다.
(1) Feign Client 적용
Spring Cloud Feign Client를 사용하여 마이크로서비스 간 통신을 간소화하고, 서킷 브레이커를 적용하여 장애 감지 및 복구를 수행했다.
Feign Client 설정
@FeignClient(name = "payment-service", fallback = PaymentFallback.class)
public interface PaymentClient {
@PostMapping("/payments")
PaymentResponse processPayment(@RequestBody PaymentRequest request);
}
서킷 브레이커 적용 (Resilience4j 사용)
@Retry(name = "paymentRetry", fallbackMethod = "fallbackPayment")
public PaymentResponse processPayment(PaymentRequest request) {
return paymentClient.processPayment(request);
}
public PaymentResponse fallbackPayment(PaymentRequest request, Throwable throwable) {
System.err.println("Payment failed Order ID: " + request.getOrderId());
System.err.println("Error: " + throwable.getMessage());
// 1. 실패한 요청을 Dead Letter Queue로 전송
sendToDLQ(request, throwable);
// 2. 관리자에게 알림 발송
notifyAdmin(request, throwable);
// 3. 장애 내역을 데이터베이스에 저장
logPaymentFailure(request, throwable);
return new PaymentResponse("FAILED", request.getOrderId());
}
(2) Feign Client를 이용한 데이터 정합성 보장 방법
- 서킷 브레이커 적용: 서비스 장애 발생 시 빠르게 감지하고, 대체 로직을 실행하여 데이터 불일치 방지
- 재시도(Retry) 메커니즘 도입: 일시적인 네트워크 장애 시 자동으로 재시도하여 요청 실패 방지
- Fallback 처리: 결제 요청 실패 시 주문 상태를 "결제 대기"로 변경하고, 사용자를 위한 별도 알림 전송
4. Kafka를 이용한 비동기 이벤트 기반 정합성 관리
Kafka는 이벤트 드리븐 방식으로 데이터 정합성을 유지하는 데 효과적이다. 서비스 간 동기 통신을 최소화하고, 장애 발생 시에도 데이터가 유실되지 않도록 이벤트 저장 및 재처리 메커니즘을 적용했다.
(1) Kafka 이벤트 발행
주문이 완료되면 Kafka를 통해 결제 이벤트를 발행한다.
@Component
public class OrderEventPublisher {
private final KafkaTemplate<String, PaymentEvent> kafkaTemplate;
public void publishPaymentEvent(PaymentEvent event) {
kafkaTemplate.send("payment-topic", event);
}
}
(2) Kafka 이벤트 소비 (결제 서비스)
@KafkaListener(topics = "payment-topic", groupId = "payment-group")
public void processPayment(PaymentEvent event, Acknowledgment acknowledgment) {
try {
paymentService.processPayment(event);
acknowledgment.acknowledge();
} catch (Exception e) {
System.err.println("Payment failed event: " + event.getOrderId() + ". Sending to DLQ.");
sendToDLQ(event);
}
}
private void sendToDLQ(PaymentEvent event) {
kafkaTemplate.send(new ProducerRecord<>("payment-dlq", event));
}
(3) Kafka를 이용한 데이터 정합성 보장 방법
- 이벤트 저장 및 재처리:
- Kafka는 메시지를 저장하므로, 서비스 장애 발생 시 이벤트를 다시 처리할 수 있음
- 예: 결제 서비스가 다운되었을 경우, 복구 후 동일한 결제 요청을 다시 처리 가능
- Idempotency(멱등성) 보장:
- 이벤트 소비 시 동일한 요청이 중복 실행되지 않도록 결제 요청 ID를 기준으로 중복 확인
- 예: 동일한 orderId에 대해 중복 결제 요청이 실행되지 않도록 데이터베이스에서 체크
- Dead Letter Queue(DLQ) 활용:
- 여러 번 재시도 후에도 실패한 이벤트를 별도의 큐(DLQ)에 저장하여 추후 처리
- 예: 결제 실패 이벤트를 DLQ로 이동 후 운영팀이 수동 확인
5. 결론
- Feign Client를 사용하여 서비스 간 동기 호출 시 서킷 브레이커, 재시도, Fallback을 적용하여 데이터 정합성을 유지
- Kafka를 활용하여 이벤트 기반 아키텍처를 구성하고, 장애 발생 시에도 이벤트를 재처리할 수 있도록 보장
- Idempotency 및 Dead Letter Queue(DLQ) 기법을 활용하여 중복 처리 및 데이터 유실 문제 해결
'트러블슈팅&기술선택' 카테고리의 다른 글
MSA 도입했음에도 다시 모놀로틱으로 돌아간 이유 (1) | 2025.03.28 |
---|---|
백엔드에서 "관리자"와 "사장"은 같은가? (1) | 2025.03.28 |
ELK vs Loki (0) | 2025.03.24 |
모놀로틱 아키텍처의 한계와 MSA 적용 이유 (0) | 2025.03.24 |
Elasticsearch 인덱스 확장 (0) | 2025.03.23 |