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를 이용한 데이터 정합성 보장 방법

  1. 서킷 브레이커 적용: 서비스 장애 발생 시 빠르게 감지하고, 대체 로직을 실행하여 데이터 불일치 방지
  2. 재시도(Retry) 메커니즘 도입: 일시적인 네트워크 장애 시 자동으로 재시도하여 요청 실패 방지
  3. 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를 이용한 데이터 정합성 보장 방법

  1. 이벤트 저장 및 재처리:
    • Kafka는 메시지를 저장하므로, 서비스 장애 발생 시 이벤트를 다시 처리할 수 있음
    • 예: 결제 서비스가 다운되었을 경우, 복구 후 동일한 결제 요청을 다시 처리 가능
  2. Idempotency(멱등성) 보장:
    • 이벤트 소비 시 동일한 요청이 중복 실행되지 않도록 결제 요청 ID를 기준으로 중복 확인
    • 예: 동일한 orderId에 대해 중복 결제 요청이 실행되지 않도록 데이터베이스에서 체크
  3. Dead Letter Queue(DLQ) 활용:
    • 여러 번 재시도 후에도 실패한 이벤트를 별도의 큐(DLQ)에 저장하여 추후 처리
    • 예: 결제 실패 이벤트를 DLQ로 이동 후 운영팀이 수동 확인

5. 결론

  • Feign Client를 사용하여 서비스 간 동기 호출 시 서킷 브레이커, 재시도, Fallback을 적용하여 데이터 정합성을 유지
  • Kafka를 활용하여 이벤트 기반 아키텍처를 구성하고, 장애 발생 시에도 이벤트를 재처리할 수 있도록 보장
  • Idempotency 및 Dead Letter Queue(DLQ) 기법을 활용하여 중복 처리 및 데이터 유실 문제 해결

 

+ Recent posts