PHP에서의 메시지 손실 및 중복 처리

1. 원인 분석

메시지 손실

  • 생성자가 메시지를 영구적으로 저장하지 않아 시스템 다운 시 손실
  • 메시지 큐가 영구화 설정이 되어 있지 않음
  • 소비자가 메시지를 받았지만 처리 전에 다운되어 메시지 확인 후 손실
  • 네트워크 오류, 타임아웃, 큐 가득 찰 때 등

중복 소비

  • 소비자의 처리 시간 초과 또는 실패로 인해 메시지 재전송
  • 메시지가 올바르게 확인되지 않아 큐에서 자동으로 재시도
  • 메시지 큐의 "최소 한 번" 전달 정책
  • 소비자 비즈니스 로직에서 멱등성 처리 미흡

2. 문제 해결 및 위치 파악

  1. 큐 모니터링: 메시지 누적, 미처리, 처리 실패, 재시도 횟수 확인
  2. 로그 분석: 생성, 소비, ack, nack, 예외 로그 분석
  3. 메시지 ID 추적: 각 메시지에 고유 ID 부여하여 손실/중복 확인

3. 완전한 해결 방안

메시지 손실 방지

생성자 측

  • 메시지 영구화: RabbitMQ/Kafka 등의 메시지 영구화 지원
  • 발송 확인 메커니즘: RabbitMQ의 publisher confirm, Kafka의 acks
  • 실패 재시도: 발송 실패 시 자동 재시도
$msg = new AMQPMessage($data, ['delivery_mode' => 2]); // 2=영구화
$channel->basic_publish($msg, '', 'queue_name');

큐 측

  • 큐 영구화: 큐를 영구화로 선언
  • 고가용성 클러스터: 여러 노드, 마스터-슬레이브, 파티션, 복제본
$channel->queue_declare('queue_name', false, true, false, false); // 세 번째 매개변수 true=영구화

소비자 측

  • 수동 확인: 성공 시 확인, 실패 시 미확인
  • 소비 실패 nack/requeue: 실패 시 메시지를 다시 큐에 넣음
  • 소비 시간 초과/다운 시 자동 재전송
$channel->basic_consume('queue_name', '', false, false, false, false, function($msg) use ($channel) {
    try {
        // 비즈니스 로직 처리
        $channel->basic_ack($msg->delivery_info['delivery_tag']);
    } catch (Exception $e) {
        $channel->basic_nack($msg->delivery_info['delivery_tag'], false, true); // 재입력
    }
});

중복 소비 방지

멱등성 설계

  • 각 메시지에 고유 ID (예: 주문 번호, 메시지 ID, UUID)
  • 소비 전 Redis나 DB에서 중복 확인, 이미 처리된 메시지는 건너뜀
  • 비즈니스 로직 자체가 멱등해야 함 (예: 결제, 배송, 쿠폰 발행 등)
$msgId = $msg['id'];
if ($redis->setnx("processed_msg:$msgId", 1)) {
    $redis->expire("processed_msg:$msgId", 86400);
    // 비즈니스 로직 처리
} else {
    // 이미 처리됨, 건너뜀
}

메시지 유일성 및 중복 제거

  • 생성자가 전역적으로 고유한 메시지 ID 생성
  • 소비자/비즈니스 계층에서 고유 ID를 사용하여 중복 제거

메시지 재시도 및 사망 메시지 큐

  • 소비 실패 또는 시간 초과 메시지는 자동으로 재시도하며, 일정 횟수 이후 사망 메시지 큐(DLQ)로 이동
  • RabbitMQ, Kafka 등은 DLQ를 지원함

모니터링 및 알림

  • 메시지 누적, 소비 실패, 재시도 횟수, 사망 메시지 큐 모니터링
  • 예외 발생 시 자동 알림으로 즉시 처리

태그: RabbitMQ kafka PHP AMQP

6월 11일 01:43에 게시됨