일반적인 RabbitMQ 구성 문제 해결

RabbitMQ 교환기, 큐, 바인딩, 승인 및 권한 오류를 찾아 수정하여 잘못된 추적을 피하세요.

일반적인 RabbitMQ 구성 문제 해결

대부분의 RabbitMQ 구성 문제는 처음에는 애플리케이션 버그처럼 보입니다. 게시자가 메시지를 보냈다고 말합니다. 소비자는 메시지를 본 적이 없다고 말합니다. 큐 그래프는 비어 있거나, 더 나쁜 경우에는 가득 차서 아무도 이유를 모릅니다. 가장 빠른 해결 방법은 추측을 멈추고 메시지 경로(게시자, 교환기, 바인딩, 큐, 소비자, 승인)를 따르는 것입니다.

RabbitMQ는 토폴로지에 엄격합니다. 다이렉트 교환기는 라우팅 키를 "대부분" 일치시키지 않습니다. 독점으로 선언된 큐는 공유 작업 큐처럼 작동하지 않습니다. 필수로 게시된 메시지는 반환될 수 있지만, mandatory 없이 라우팅할 수 없는 동일한 메시지는 교환기에 의해 단순히 삭제될 수 있습니다. 이러한 세부 사항은 작지만 오후를 낭비하게 만들 수 있습니다.

실제 경로부터 시작하세요

일반적인 AMQP 게시의 경우, 생산자는 라우팅 키와 함께 교환기에 메시지를 보냅니다. 교환기는 유형과 바인딩을 사용하여 어떤 큐가 메시지를 수신해야 하는지 결정합니다. 그런 다음 소비자는 큐에서 전달을 가져와 처리 후 승인합니다.

메시지가 사라지면 네 가지 질문을 하세요:

  • 생산자가 생각한 교환기와 가상 호스트에 게시했습니까?
  • 해당 교환기가 존재하며, 생각한 유형입니까?
  • 해당 교환기에서 의도한 큐로의 바인딩이 있습니까?
  • 라우팅 키가 교환기 유형에 대한 해당 바인딩과 일치합니까?

기본적으로 들리지만, 실제 많은 사고를 잡아냅니다. 스테이징과 프로덕션은 종종 다른 가상 호스트를 사용합니다. 배포 스크립트는 한 환경에서 orders.created를 선언하고 다른 환경에서 order.created를 선언할 수 있습니다. 큐는 한 단어를 놓치는 토픽 패턴에 바인딩될 수 있습니다.

실행 중인 코드가 아니라 관리 UI 또는 CLI를 사용하여 라이브 브로커를 검사하세요:

rabbitmqctl list_exchanges name type durable auto_delete
rabbitmqctl list_bindings source_name source_kind destination_name destination_kind routing_key
rabbitmqctl list_queues name durable auto_delete exclusive messages_ready messages_unacknowledged consumers

여러 가상 호스트를 사용하는 경우 -p를 포함하세요:

rabbitmqctl -p production list_bindings source_name destination_name routing_key

라우팅 키 불일치

다이렉트 교환기는 정확한 바인딩 키 일치가 필요합니다. 큐가 invoice.created로 바인딩된 경우 invoices.created로 게시된 메시지는 도착하지 않습니다. RabbitMQ는 복수형, 대소문자, 점, 대시를 수정하지 않습니다.

토픽 교환기는 한 단어에 *를 사용하고 0개 이상의 단어에 #를 사용합니다. 단어 구분자는 점입니다. logs.* 바인딩은 logs.info와 일치하지만 logs.app.info와는 일치하지 않습니다. logs.# 바인딩은 둘 다 일치합니다.

유용한 문제 해결 트릭은 광범위한 바인딩으로 임시 진단 큐를 추가한 다음 알려진 테스트 메시지를 게시하는 것입니다:

rabbitmqadmin declare queue name=debug.routing durable=false auto_delete=true
rabbitmqadmin declare binding source=events destination=debug.routing destination_type=queue routing_key='#'

프로덕션에서 신중하게 수행하고 완료되면 진단 바인딩을 제거하세요. 목표는 메시지가 교환기에 전혀 도달하는지 증명하는 것입니다.

중요한 게시자의 경우, 라우팅할 수 없는 메시지를 게시자가 볼 수 있도록 mandatory 플래그로 게시자 반환을 활성화하세요. 게시자 확인은 브로커가 게시를 수락했음을 알려줍니다. 반환은 교환기가 큐로 라우팅할 수 없음을 알려줍니다. 그들은 다른 질문에 답합니다.

교환기 유형 실수

교환기 유형 변경은 기존 교환기를 다른 속성으로 선언하면 실패하기 때문에 일반적인 혼란의 원인입니다. 한 서비스가 eventstopic으로 선언하고 다른 서비스가 eventsdirect로 선언하면 두 번째 선언은 사전 조건 실패를 받아야 합니다.

그 실패는 좋습니다. 두 애플리케이션이 라우팅에 대해 조용히 의견 차이를 보이는 것을 방지합니다. 해결 방법은 예외를 잡아 무시하는 것이 아닙니다. 해결 방법은 토폴로지 소유권을 명확히 하는 것입니다. 일반적으로 하나의 배포 단계 또는 인프라 모듈이 공유 교환기와 큐를 선언해야 하며, 애플리케이션은 개인 응답 큐만 선언하거나 예상 토폴로지를 멱등적으로 확인해야 합니다.

팬아웃 교환기는 라우팅 키를 무시합니다. 헤더 교환기는 라우팅 키가 아닌 헤더로 라우팅합니다. 테스트 메시지에 올바른 라우팅 키가 있지만 큐가 수신하지 않으면 모든 바인딩을 편집하기 전에 교환기 유형을 확인하세요.

사람들을 놀라게 하는 큐 속성

내구성은 큐 정의가 브로커 재시작 후에도 유지됨을 의미합니다. 큐 내부의 모든 메시지가 유지된다는 의미는 아닙니다. 메시지가 재시작 후에도 유지되려면 큐가 내구성이 있어야 하고 메시지가 영구적으로 게시되어야 합니다. 그럼에도 불구하고 게시자는 RabbitMQ가 메시지를 안전하게 수락했는지 알아야 하는 경우 확인을 사용해야 합니다.

자동 삭제 큐는 마지막 소비자가 사라진 후 제거됩니다. 임시 구독에는 유용하지만 공유 작업 큐에는 적합하지 않습니다. 독점 큐는 선언하는 연결로 범위가 지정되며 해당 연결이 닫히면 사라집니다. 응답 큐 및 개인 소비자에게 유용하지만 여러 작업자 인스턴스에는 적합하지 않습니다.

큐가 "무작위로 사라지는" 것처럼 보이면 다음 플래그를 확인하세요:

rabbitmqctl list_queues name durable auto_delete exclusive consumers

또한 애플리케이션 코드가 시작 시 기존 큐와 다른 인수로 큐를 선언하는지 확인하세요. RabbitMQ는 큐 유형, 데드 레터 교환기, 최대 길이 및 일부 내구성 관련 설정과 같은 큐 인수를 선언 계약의 일부로 취급합니다. 불일치는 사전 조건 실패로 채널을 닫을 수 있습니다.

메시지는 준비되었지만 소비자는 아무것도 하지 않음

messages_ready가 높고 consumers가 0이면 RabbitMQ가 기다리고 있습니다. 소비자 애플리케이션이 다운되었거나, 잘못된 가상 호스트에 연결되었거나, 잘못된 큐 이름을 사용하거나, 권한에 의해 차단되었을 수 있습니다.

소비자가 연결되었지만 전달이 발생하지 않는 경우, 프리페치 및 소비자 용량을 확인하세요:

rabbitmqctl list_consumers queue_name channel_pid consumer_tag ack_required prefetch_count active

수동 승인 및 가득 찬 프리페치 창이 있는 소비자는 이미 가지고 있는 메시지 중 일부를 승인하거나 거부할 때까지 더 많은 메시지를 수신하지 않습니다. 이는 종종 RabbitMQ가 전달을 중단한 것처럼 보이지만 실제로는 소비자가 승인되지 않은 작업을 보유하고 있는 경우입니다.

messages_unacknowledged가 높으면 소비자 로그 및 다운스트림 시스템을 살펴보세요. 느린 데이터베이스, 막힌 HTTP 종속성 또는 승인 없이 예외를 잡는 핸들러는 모두 승인되지 않은 메시지의 벽을 만들 수 있습니다.

승인 버그

수동 승인은 안정적인 처리를 위한 일반적인 선택입니다. 소비자는 작업이 완료된 후에만 승인해야 합니다. 실패하면 의도적인 재큐 결정과 함께 거부하거나 nack해야 합니다.

위험한 패턴은 실패할 수 있는 작업에 대해 auto_ack=true입니다. 자동 승인을 사용하면 RabbitMQ는 메시지가 전달되는 즉시 처리된 것으로 간주합니다. 소비자가 메시지를 수신한 후 충돌하면 메시지는 큐에서 사라집니다.

반대 버그는 절대 승인하지 않는 것입니다. 소비자는 메시지를 성공적으로 처리하고 데이터베이스에 기록할 수도 있지만 basic_ack을 잊어버립니다. RabbitMQ는 채널이 닫힐 때까지 전달을 승인되지 않은 상태로 유지한 다음 재전달합니다. 이는 중복 작업과 증가하는 승인되지 않은 수를 만듭니다.

감사하기 쉬운 간단한 핸들러 모양:

def handle(ch, method, properties, body):
    try:
        process(body)
    except RetryableError:
        ch.basic_nack(method.delivery_tag, requeue=True)
    except Exception:
        ch.basic_nack(method.delivery_tag, requeue=False)
    else:
        ch.basic_ack(method.delivery_tag)

모든 실패를 영원히 재큐하면 하나의 잘못된 메시지가 끝없이 반복될 수 있습니다. 독 메시지에는 데드 레터 교환기 또는 재시도 디자인을 사용하세요.

권한 및 가상 호스트

RabbitMQ 권한은 가상 호스트별로 범위가 지정됩니다. 사용자가 연결할 수 있지만 큐 또는 교환기에 대한 구성, 쓰기 또는 읽기 권한이 여전히 없을 수 있습니다. 이는 항상 친숙한 애플리케이션 오류가 아니라 클라이언트 로그의 채널 예외로 나타날 수 있습니다.

권한을 직접 확인하세요:

rabbitmqctl list_permissions -p production
rabbitmqctl list_user_permissions app_user

게시만 하는 서비스의 경우 필요한 교환기 패턴에 대한 쓰기 권한을 부여하고 광범위한 구성 권한은 피하세요. 소비자의 경우 큐에 대한 읽기 권한을 부여하고 데드 레터 경로에 nack을 게시하거나 응답 패턴을 사용해야 하는 경우 쓰기 권한을 부여하세요. 지나치게 광범위한 권한은 오늘 문제 해결을 더 쉽게 만들고 내일 보안 검토를 더 어렵게 만듭니다.

데드 레터 구성 실수

데드 레터 교환기는 실패를 표시해야 합니다. 잘못 구성된 데드 레터링은 그 반대의 효과를 냅니다: 메시지가 실패하고, 거부된 다음 바인딩이 없는 교환기로 사라집니다.

큐 이름뿐만 아니라 큐 인수를 확인하세요:

rabbitmqctl list_queues name arguments

실패한 작업을 데드 레터해야 하는 큐의 경우 x-dead-letter-exchange 및 때로는 x-dead-letter-routing-key와 같은 인수가 표시되어야 합니다. 그런 다음 기본 경로를 검사하는 것과 같은 방식으로 해당 교환기와 바인딩을 검사하세요.

일반적인 실수는 jobs.dlx라는 데드 레터 교환기를 구성하지만 데드 레터 큐를 다른 교환기의 jobs.failed에 바인딩하는 것입니다. 또 다른 실수는 x-dead-letter-routing-key를 일치하는 바인딩이 없는 값으로 설정하는 것입니다. RabbitMQ는 다른 게시와 마찬가지로 데드 레터된 메시지를 데드 레터 교환기를 통해 라우팅합니다. 일치하는 항목이 없으면 메시지는 유용한 곳으로 갈 수 없습니다.

재시도 큐도 동일한 주의가 필요합니다. TTL과 데드 레터링으로 재시도를 구축하는 경우 종이에 경로를 그리세요:

main queue -> reject -> retry exchange -> retry queue -> TTL expires -> main exchange -> main queue

그런 다음 모든 교환기, 큐, 바인딩 및 라우팅 키를 확인하세요. 재시도 루프는 실수로 쉽게 생성됩니다. 하나의 손상된 페이로드가 영원히 회전하지 않도록 메시지 헤더 또는 애플리케이션 상태에 시도 횟수 제한을 두세요.

정책 놀라움

정책은 애플리케이션 코드에서 언급하지 않고 큐 동작을 변경할 수 있습니다. 정책은 큐 유형, 최대 길이, TTL, 데드 레터 교환기 또는 기타 선택적 인수를 설정할 수 있습니다. 이는 운영에 유용하지만 큐가 코드 선언과 다르게 동작할 때 디버깅을 혼란스럽게 할 수 있습니다.

문제 해결 중에 정책을 나열하세요:

rabbitmqctl list_policies

패턴과 우선 순위를 살펴보세요. .*와 같은 광범위한 정책은 관련 없는 팀이 나중에 생성한 큐에 영향을 줄 수 있습니다. 큐가 오래된 메시지를 삭제하는 경우 max-length 또는 오버플로 설정을 확인하세요. 메시지가 예상보다 빨리 만료되면 큐 수준 TTL 및 메시지별 만료를 확인하세요.

애플리케이션이 한 세트의 인수를 선언하고 정책이 다른 인수를 적용하는 경우 RabbitMQ의 규칙은 설정에 따라 다릅니다. 일부 선택적 인수는 정책으로 제어할 수 있습니다. 다른 인수는 선언과 일치해야 합니다. 안전한 운영 습관은 큐 동작을 한 곳에 명확하게 유지하고 의도적으로 애플리케이션 기본값을 재정의하는 모든 정책을 문서화하는 것입니다.

생산자와 소비자가 토폴로지를 선언할 때

많은 클라이언트 라이브러리는 모든 서비스가 시작 시 교환기, 큐 및 바인딩을 쉽게 선언할 수 있도록 합니다. 이는 개발에서 편리할 수 있습니다. 프로덕션에서는 소유권 문제를 만들 수 있습니다.

생산자와 소비자 모두 동일한 큐를 선언하는 경우 모든 중요한 속성에 동의해야 합니다. 한 배포가 큐를 자동 삭제에서 내구성으로 변경하거나 데드 레터 인수를 변경하면 다음에 시작하는 서비스가 사전 조건 오류로 실패할 수 있습니다. 이는 침묵하는 드리프트보다 낫지만 여전히 배포를 중단시킬 수 있습니다.

공유 토폴로지의 경우 한 명의 소유자(Terraform, Ansible, 마이그레이션 작업 또는 명확히 책임 있는 하나의 서비스)를 선호하세요. 애플리케이션 시작은 여전히 예상 토폴로지가 존재하는지 확인할 수 있지만 아무도 검토하지 않은 기본값으로 공유 큐를 무심코 생성해서는 안 됩니다.

개인 토폴로지는 다릅니다. 임시 응답 큐 또는 독점 구독 큐를 생성하는 서비스는 해당 큐를 직접 소유할 수 있습니다. 차이점은 다른 서비스가 큐 이름과 동작에 의존하는지 여부입니다.

하나의 알려진 양호한 게시 경로 유지

중요한 시스템의 경우 예상 교환기, 라우팅 키 및 가상 호스트를 통해 무해한 메시지를 보내는 작은 진단 게시자 또는 runbook 명령을 유지하세요. 실제 애플리케이션과 동일한 자격 증명 클래스를 사용하거나 라우팅 및 액세스 문제를 잡을 수 있을 만큼 가까운 권한 세트를 사용해야 합니다.

해당 알려진 양호한 경로는 배포 중에 유용합니다. 진단 메시지가 라우팅되지만 애플리케이션 메시지가 라우팅되지 않는 경우 앱의 실제 라우팅 키, 헤더 및 가상 호스트를 비교하세요. 진단 메시지도 실패하면 문제는 아마도 토폴로지, 권한 또는 브로커 상태일 것입니다.

실용적인 사고 체크리스트

메시지가 누락되거나 중단된 경우 모든 것을 다시 시작하기 전에 라이브 상태를 수집하세요:

rabbitmqctl -p production list_queues name messages_ready messages_unacknowledged consumers state
rabbitmqctl -p production list_bindings source_name destination_name routing_key
rabbitmqctl -p production list_exchanges name type durable
rabbitmqctl -p production list_connections name user vhost state

그런 다음 고유 ID로 하나의 알려진 테스트 메시지를 보내고 로그를 통해 추적하세요. 경로가 이미 명확하지 않은 임의의 프로덕션 메시지로 테스트하지 마세요.

대부분의 RabbitMQ 구성 문제는 선언된 토폴로지를 게시자 및 소비자 동작과 일치시키면 신비롭지 않습니다. 브로커는 일반적으로 지시받은 대로 정확히 수행하고 있습니다. 작업은 지시받은 내용이 팀이 의도한 것과 다른 위치를 찾는 것입니다.