RabbitMQ 확장 가이드: 클러스터 토폴로지 최적화
클러스터링, 복제, 처리량을 혼동하지 않고 확장 가능한 RabbitMQ 클러스터를 설계하세요.
RabbitMQ 확장 가이드: 클러스터 토폴로지 최적화
RabbitMQ 확장은 불편한 사실 하나에서 시작합니다: 클러스터는 마법처럼 더 큰 브로커가 아닙니다. 메타데이터를 공유하고, 큐 유형에 따라 큐 데이터를 복제할 수 있는 브로커 집합입니다. 단일 큐에 과부하가 걸린 경우, 노드를 추가하면 가용성이 향상될 수 있지만, 해당 큐 하나가 자동으로 더 빠르게 소비되지는 않습니다.
이러한 구분은 많은 잘못된 설계를 방지합니다. 바쁜 RabbitMQ 배포에 두 개의 노드를 추가하고, 아무것도 이동하지 않고, 큐 레이아웃을 변경하지 않은 채, 왜 같은 큐가 매일 오후마다 여전히 백업되는지 의아해하는 팀을 본 적이 있습니다. 큐 리더는 여전히 같은 노드에 있었습니다. 동일한 소비자가 여전히 동일한 작업을 수행하고 있었습니다. 클러스터에는 더 많은 머신이 있었지만, 병목 현상은 이동하지 않았습니다.
RabbitMQ 클러스터 토폴로지는 주로 큐가 어디에 위치할지, 중요한 메시지의 복사본이 몇 개 필요한지, 처리량이 떨어지기 전에 얼마나 많은 장애를 견딜 수 있는지 결정하는 것입니다. 단기 메트릭 파이프라인에 대한 올바른 답변은 결제, 주문 처리 또는 감사 이벤트에 대한 올바른 답변과 동일하지 않습니다.
클러스터링이 실제로 공유하는 것
RabbitMQ 클러스터의 노드는 정의(가상 호스트, 사용자, 권한, 익스체인지, 큐, 바인딩, 정책)와 클러스터 운영에 필요한 런타임 메타데이터를 공유합니다. 한 노드에 연결된 프로듀서는 큐 리더가 다른 노드에 있는 익스체인지에 게시할 수 있습니다. 소비자는 큐를 호스팅하는 노드와 다른 노드에 연결할 수 있습니다.
그렇다고 모든 메시지가 모든 곳에 존재하는 것은 아닙니다.
클래식 큐는 하나의 노드에 리더가 있습니다. 쿼럼 큐는 리더와 복제본이 있습니다. 스트림은 자체 복제 모델이 있습니다. 큐 리더가 이를 사용하는 대부분의 클라이언트와 멀리 떨어져 있으면 RabbitMQ는 클러스터 상호 연결을 통해 트래픽을 이동해야 합니다. 적당히 사용하면 괜찮습니다. 모든 게시자가 노드 A에 연결하고, 모든 핫 큐가 노드 B에 있으며, 모든 소비자가 노드 C에 연결되면 비용이 많이 듭니다.
간단한 첫 번째 규칙이 잘 작동합니다: 애플리케이션을 사용하는 큐와 가까운 노드에 연결하거나, 클러스터 앞에 로드 밸런서를 두고 큐 리더가 합리적으로 분산되었는지 확인하세요. 라운드 로빈 클라이언트 연결이 라운드 로빈 큐 로드를 생성한다고 가정하지 마십시오.
창의력을 발휘하기 전에 3개의 노드를 선호하세요
대부분의 프로덕션 RabbitMQ 클러스터의 경우, 하나의 저지연 리전 또는 가용성 영역 그룹에 있는 3개의 노드가 깔끔한 시작점입니다. 쿼럼 큐에 하나의 노드 장애를 견딜 수 있는 과반수 모델을 제공하고, 인시던트 중에 추론할 수 있을 만큼 클러스터 조정을 단순하게 유지합니다.
2노드 클러스터는 더 저렴해 보이지만 복제된 큐에는 어색합니다. 쿼럼 기반 시스템에서는 과반수가 필요합니다. 두 노드 중 하나가 사라지면 과반수가 없습니다. 일부 분산 시스템에서는 감시 스타일의 세 번째 노드를 추가할 수 있지만, RabbitMQ의 경우 일반적으로 충분한 디스크 및 네트워크 용량을 갖춘 3개의 실제 노드를 실행하는 것이 더 간단하고 안정적입니다.
5개의 노드는 큐가 많거나, 더 많은 배치 옵션이 필요하거나, 더 많은 머신에 로드를 분산하려는 경우 의미가 있습니다. 또한 클러스터 통신 및 운영 표면적의 양이 증가합니다. 3개에서 5개로 이동하기 전에 노드 포화 문제를 해결하고 있는지 아니면 큐 설계 문제를 해결하고 있는지 확인하세요. 하나의 큐가 핫하면 노드를 더 추가해도 해당 큐의 작업이 분할되지 않습니다.
쿼럼 큐는 무료 속도가 아닌 복제된 안정성을 위한 것입니다
새로운 고가용성 워크로드의 경우, 메시지 내구성이 중요한 경우 쿼럼 큐가 일반적으로 올바른 기본값입니다. 합의 프로토콜을 사용하여 메시지를 복제합니다. 3개의 멤버가 있는 쿼럼 큐는 과반수가 정상 상태를 유지하는 한 하나의 멤버를 사용할 수 없어도 계속 작동할 수 있습니다.
트레이드오프는 쓰기 비용입니다. 게시된 영구 메시지는 안전하게 수락된 것으로 간주되기 전에 충분한 멤버에게 복제되어야 합니다. 이는 중요한 작업에 정확히 필요한 것이지만, 일시적인 클래식 큐와 동일한 성능 프로필은 아닙니다.
애플리케이션이 토폴로지를 관리하는 방식에 따라 인수 또는 정책을 사용하여 쿼럼 큐를 선언하세요:
rabbitmqadmin declare queue name=orders durable=true arguments='{"x-queue-type":"quorum"}'
정책의 경우 신중하게 범위를 지정하세요. 광범위한 패턴이 .*와 일치한다고 해서 가상 호스트의 모든 큐를 실수로 쿼럼 큐로 변환하지 마십시오. 좋은 정책 이름과 좁은 큐 접두사는 가장 좋은 의미에서 지루합니다:
rabbitmqctl set_policy qq-orders '^orders\.' '{"queue-type":"quorum"}' --apply-to queues
클래식 미러링된 큐에서 마이그레이션하는 경우 플래그 전환이 아닌 마이그레이션으로 처리하세요. 클래식 미러링된 큐와 쿼럼 큐는 순서, 포이즌 메시지, 메모리 사용 및 장애 조치에 대해 다르게 동작합니다. 새 큐 유형을 만들고, 제어된 트래픽 슬라이스를 라우팅하고, 확인 및 소비자 지연 시간을 관찰한 다음 나머지를 이동하세요.
클래식 큐는 여전히 자리가 있습니다
클래식 큐는 복제가 필요하지 않은 워크로드, 메시지가 일시적인 경우, 또는 큐가 서비스에 로컬이고 다른 소스에서 재구축할 수 있는 경우에 여전히 유용합니다. 또한 노드 장애 중에 몇 개의 메시지를 잃는 것이 허용되는 대량의 저가치 이벤트에도 적합합니다.
클래식 큐를 의도적으로 사용하세요. 클래식 큐가 내구성이 있고 영구 메시지를 수신하는 경우 해당 메시지는 해당 큐를 호스팅하는 노드에 저장됩니다. 해당 노드가 다운되면 노드가 반환될 때까지 큐를 사용할 수 없습니다. 이는 백그라운드 조정 작업에는 괜찮을 수 있습니다. 일반적으로 고객이 볼 수 있는 주문 상태에는 적합하지 않습니다.
긴 백로그의 경우 워크로드가 스트림 또는 다른 스토리지 시스템이어야 하는지 고려하세요. RabbitMQ는 큐를 보유할 수 있지만, 수백만 개의 오래된 메시지가 있는 큐는 종종 소비자 규모가 부족하거나, 다운스트림 시스템이 실패하거나, 비즈니스 프로세스에 큐 의미 체계보다는 재생 의미 체계가 필요하다는 신호입니다.
클러스터 주변에 지연 시간 경계를 설정하세요
RabbitMQ 클러스터링은 저지연, 안정적인 네트워크 링크를 기대합니다. 하나의 클러스터를 먼 리전에 걸쳐 확장하는 것은 일반적으로 좋지 않은 거래입니다. 노드 간 트래픽이 느려지고, 장애 조치를 예측하기 어려워지며, 네트워크 파티션은 피하려고 했던 중단보다 더 해로울 수 있습니다.
실용적인 설계는 리전당 하나의 RabbitMQ 클러스터와, 리전 간 이동이 필요할 때 애플리케이션 수준 라우팅 또는 페더레이션/셔블을 사용하는 것입니다. 이렇게 하면 로컬 게시 및 소비가 빨라집니다. 또한 장애 도메인이 명확해집니다: 리전 A가 비정상이면 리전 B가 동일한 클러스터 멤버십 문제로 끌려가지 않습니다.
하나의 리전 내에서 여러 AZ는 다릅니다. 영역 간 지연 시간이 낮고 안정적이면 세 영역에 걸쳐 3개의 노드가 잘 작동할 수 있습니다. 실제 부하에서 테스트하세요. 클라우드 제공자가 무언가를 가용성 영역이라고 부른다고 해서 메시지 크기, 확인 및 쿼럼 큐가 사용량이 많은 시간 동안 어떻게 작동할지 알 수 없습니다.
노드뿐만 아니라 큐 리더의 균형을 맞추세요
클러스터는 CPU 그래프에서 균형이 잡힌 것처럼 보일 수 있지만 큐 수준에서는 심하게 치우칠 수 있습니다. 하나의 노드는 가장 바쁜 큐의 리더를 소유하고 다른 노드는 대부분 조용한 복제본을 보유할 수 있습니다.
큐 배치를 확인하세요:
rabbitmqctl list_queues name type leader members messages_ready messages_unacknowledged
하나의 노드가 대부분의 핫 리더를 소유한 경우, RabbitMQ의 지원되는 도구를 사용하여 큐를 이동하거나 재조정하세요(버전 및 큐 유형에 따라 다름). 쿼럼 큐의 경우 멤버 배치와 리더 위치가 중요합니다. 클래식 큐의 경우 이전 용어에서는 큐 마스터 배치가 중요했지만, 최신 버전에서는 리더 언어를 더 일관되게 사용합니다.
좋은 토폴로지는 관련 없는 핫 큐를 노드 전체에 분산시킵니다. 예를 들어, email.send, image.resize 및 billing.capture는 각각 트래픽이 많으면 모두 동일한 노드가 리더가 되어서는 안 됩니다. billing.capture가 유일한 핫 큐인 경우, 소비자가 해당 샤드를 안전하게 독립적으로 처리할 수 있는 경우에만 판매자 그룹 또는 리전과 같은 실제 비즈니스 샤드로 분할하세요.
클라이언트 연결 동작 설계
클라이언트 연결 배치는 토폴로지의 일부입니다. 모든 애플리케이션이 영원히 첫 번째 DNS 결과에 연결되면 큐가 클러스터 전체에 분산되어 있어도 하나의 노드가 대부분의 클라이언트 트래픽을 전달할 수 있습니다. 로드 밸런서가 도움이 될 수 있지만, 노드가 AMQP 트래픽에 실제로 사용 가능한지 이해하는 상태 확인을 사용해야 합니다.
연결을 장기간 유지하세요. RabbitMQ는 많은 연결을 처리할 수 있지만, 연결 변동은 CPU, 메모리, 파일 디스크립터 및 TLS 오버헤드를 소모합니다. 웹 요청은 새 AMQP 연결을 열고, 하나의 메시지를 게시하고, 닫아서는 안 됩니다. 클라이언트 라이브러리에 적합한 연결 또는 채널 풀을 사용하세요.
또한 노드 장애 중에 클라이언트가 수행할 작업을 결정하세요. 좋은 클라이언트는 백오프로 다시 연결하고, 채널을 다시 열고, 필요한 경우 개인 토폴로지를 다시 선언하고, 확인 또는 소비를 신중하게 재개합니다. 나쁜 클라이언트는 빡빡한 루프로 다시 연결하여 노드 재시작을 연결 폭풍으로 만듭니다.
소비자의 경우 지역성을 고려하지만 과도하게 맞추지 마세요. 소비자를 큐 리더와 동일한 노드에 연결하면 노드 간 트래픽을 줄일 수 있지만, 큐 리더는 장애 후에 이동할 수 있습니다. 소비자는 수동 변경 없이 이를 견뎌야 합니다.
파티션은 설정이 아닌 운영 이벤트입니다
네트워크 파티션은 클러스터 다이어그램이 테스트되는 곳입니다. RabbitMQ에는 파티션 처리 모드가 있지만, 어떤 설정도 명확한 운영 결정의 필요성을 제거하지 않습니다. 클러스터의 두 측면이 통신할 수 없는 경우 해당 워크로드에 가용성 또는 일관성 중 무엇이 더 중요한지 결정해야 합니다.
쿼럼 큐는 복제본의 과반수가 필요합니다. 이것이 요점입니다: 소수 측은 안전하게 동의할 수 없는 쓰기를 계속 수락해서는 안 됩니다. 이는 모든 생존 노드가 쓰기 가능한 상태로 유지될 것이라고 예상한 팀을 놀라게 할 수 있습니다. 이에 대비하세요. 쿼럼 큐 멤버를 관심 있는 장애에서 과반수가 생존할 수 있는 곳에 배치하세요.
3노드 쿼럼 큐를 세 개의 먼 리전에 분산시키고 일반 인터넷 지연 시간 동안 원활한 동작을 기대하지 마세요. 쿼럼은 멤버 간 네트워크만큼만 쾌적합니다. 저지연 및 낮은 패킷 손실은 있으면 좋은 것이 아니라 용량 요구 사항입니다.
비프로덕션 환경에서 파티션 드릴을 실행하세요. 노드 간 트래픽을 차단하고, 어떤 큐를 사용할 수 있는지 관찰하고, 클라이언트가 다시 연결되는 것을 지켜보고, 복구 단계를 기록하세요. 파티션 동작을 처음 배우는 것은 실제 네트워크 인시던트 중이어서는 안 됩니다.
브로커를 확장하기 전에 소비자를 확장하세요
증상이 증가하는 큐인 경우 브로커가 항상 병목 현상은 아닙니다. 종종 소비자가 게시자보다 단순히 느립니다. RabbitMQ 노드를 추가하기 전에 소비자 사용률, 확인되지 않은 카운트, 처리 시간 및 다운스트림 지연 시간을 확인하세요.
메시지가 준비되었지만 확인되지 않은 경우 RabbitMQ에 메시지가 대기 중이고 소비자가 충분히 빠르게 가져가지 않고 있습니다. 소비자를 추가하거나, 프리페치를 수정하거나, 다운스트림 지연을 제거하세요. 메시지가 대부분 확인되지 않은 경우 소비자가 이미 작업을 수신했으며 이를 확인하는 데 너무 오래 걸리고 있습니다. 브로커 노드를 추가해도 해당 핸들러가 더 빨라지지는 않습니다.
여기서 프리페치가 중요합니다. 느린 작업자에 대한 500의 프리페치는 소비자 프로세스 내에서 백로그를 숨길 수 있습니다. 빠른 로컬 작업자에 대한 1의 프리페치는 왕복 시간을 낭비할 수 있습니다. 작은 값으로 시작하고, 종단 간 지연 시간과 소비자 메모리를 측정한 다음 조정하세요.
지루한 제한 사항을 주시하세요
확장 계획은 종종 토폴로지에 대해 이야기하고 파일 디스크립터, 디스크 알람, 메모리 알람, 연결 변동 및 채널 수를 잊어버립니다. RabbitMQ는 이 모든 것에 민감합니다.
각 노드에 대해 사용된 메모리, 사용 가능한 디스크, 파일 디스크립터, 소켓, 큐 프로세스 메모리, 메시지 속도, 확인 지연 시간 및 Erlang 스케줄러 사용률을 모니터링하세요. 클라이언트 측에서는 재연결 루프 및 채널 생성 속도를 모니터링하세요. 모든 게시에 대해 새 연결을 여는 서비스는 메시지 볼륨이 인상적으로 보이기 오래 전에 클러스터에 해를 끼칠 수 있습니다.
클라이언트 라이브러리에서 지원하는 경우 장기 연결 및 채널을 사용하세요. 연결 제한 및 하트비트 설정을 설계에 포함시키고, 중단 중에 당황한 변경으로 사용하지 마세요.
일반적으로 작동하는 토폴로지
일반적인 비즈니스 애플리케이션의 경우, 네트워크가 좋으면 영역 전체에 분산된 하나의 리전에 3개의 RabbitMQ 노드로 시작하겠습니다. 중요한 내구성 워크플로에는 쿼럼 큐를 사용하세요. 장애 동작이 허용되는 일시적인 작업에는 클래식 큐를 사용하세요. 게시자와 소비자를 클러스터 가까이에 유지하세요. 클라이언트 액세스에는 로드 밸런서를 사용하지만, 로드 밸런서가 브로커 배치를 해결했다고 가정하지 말고 큐 리더 균형을 확인하세요.
그런 다음 추한 경우를 테스트하세요: 하나의 노드를 죽이고, 소비자 그룹을 일시 중지하고, 큐를 채우고, 디스크를 느리게 하고, 게시자를 다시 시작하세요. RabbitMQ 확장은 가장 예쁜 다이어그램보다는 다이어그램이 스트레스를 받을 때 어떤 일이 발생하는지 아는 것에 더 가깝습니다.