Computer >> 컴퓨터 >  >> 프로그램 작성 >> 데이터 베이스

HA Redis 배포에서 안정적인 PUBSUB 및 차단 목록 작업

HA Redis 배포에서 안정적인 PUBSUB 및 차단 목록 작업

표준 중복 Redis 솔루션은 장애 조치를 관리하는 Sentinel로 마스터/슬레이브 복제를 실행하는 것입니다. 이것은 a) 클라이언트 지원 및 현재 마스터를 발견하기 위한 Sentinel 사용 또는 b) 마스터를 가리키도록 Sentinel이 관리하는 Redis 포드 앞의 TCP 프록시로 이어질 것으로 예상됩니다. 전자는 Redis Sentinel이 설계된 방식이고 후자는 성장하는 추세이며 ObjectRocket Redis가 구성된 방식입니다.

Redis의 명령에 대한 장애 조치의 영향

대부분의 Redis 작업에서 이는 예상대로 작동합니다. 장애 조치 시 클라이언트 코드는 연결 실패를 감지하고 a) 현재 마스터를 조회하고 다시 연결하거나 b) 위에서 언급한 시나리오에 따라 재연결을 시도할 것으로 예상됩니다. 프록시 기반 시나리오도 Redis 서버 재시작과 동일하므로 재시작할 수 있는 단일 인스턴스 Redis가 있는 경우에도 마찬가지입니다.

그러나 소수의 Redis 명령은 장기 차단 또는 비원자 명령입니다. PUBSUB subscribe 및 psubscribe 명령은 명령이 지정된 채널로 보내진 메시지도 함께 보내도록 요청을 등록하지만 실제 데이터는 훨씬 나중에 도착할 수 있다는 점에서 비원자적입니다. 게시된 후. BL* 명령은 subscribe 명령과 같이 차단하고 제거할 항목을 목록에서 사용할 수 있을 때까지 반환하지 않는다는 점에서 장기 및 2단계 명령입니다.

이러한 명령 간의 주요 공통점은 해당 명령이 실행되고 등록 형식이 서버에서 수행되지만 데이터는 나중에 올 수 있다는 것입니다. 실행 중인 명령과 장애 조치 및 재시작으로 응답할 데이터가 있는 서버 사이에 발생할 수 있습니다. 재시작 또는 장애 조치의 경우 "등록"이 더 이상 서버에 없습니다. 이것은 이러한 시나리오에서 이러한 명령과 관련하여 어떤 일이 발생하는지에 대한 질문으로 이어집니다.

이러한 세부 사항을 자세히 살펴보기 전에 테스트 중에 실행되고 결과와 실패 시나리오에 대한 코딩 방법에 극적인 영향을 미치므로 언급해야 할 또 다른 측면이 있습니다. 실제로 두 가지 유형의 장애 조치가 있습니다. 우리 대부분이 고려하는 장애 조치가 있습니다. 바로 마스터 손실입니다. 이러한 유형의 장애 조치에서 마스터는 응답하지 않습니다. 저는 이것을 "트리거된 장애 조치(failover)"라고 부릅니다. 두 번째 유형의 장애 조치를 "시작된 장애 조치"라고 합니다.

시작된 장애 조치는 관리자가 Sentinel 장애 조치 명령을 Sentinel에 보낸 다음 포드를 재구성하여 마스터를 승격한 다음 원래 마스터를 강등할 때 발생합니다. 표면적으로는 이 두 시나리오가 동일해 보입니다. 대다수의 Redis 명령의 경우 동일하게 취급할 수 있습니다. 그러나 긴 블록 명령의 경우 자체 컨텍스트에서 이해해야 합니다.

이 두 장애 조치 클래스 간의 주요 기능적 차별화 요소는 이벤트의 순서입니다. 트리거된 장애 조치가 발생하는 것은 마스터가 응답하지 않기 때문입니다. 이 경우 데이터가 추가되거나 수정되지 않으며 마스터에 메시지가 게시되지 않습니다. 시작된 장애 조치에는 두 개의 마스터가 있는 매우 짧은 창이 있습니다.

여기에서 논의되는 기술과 시나리오는 일반에서 설명하고 Python Redis 라이브러리 "redis-py"를 사용하여 시연되지만 모든 클라이언트 라이브러리에 적용됩니다. 배경을 가지고 이제 이것이 장블록 명령의 사용에 어떤 영향을 미치는지 살펴보겠습니다. 먼저 PUBSUB를 살펴보겠습니다.

펍서브

Redis PUBSUB는 PUBLISH, SUBSCRIBE 및 PSUBSCRIBE의 세 가지 명령으로 구성됩니다. 첫 번째 명령은 메시지를 보내는 데 사용되는 반면 후자의 두 명령은 PUBLISH 명령을 통해 게시된 메시지를 등록하고 받는 데 사용됩니다. 먼저 PUBLISH 명령에서 어떤 일이 발생하는지 살펴보겠습니다.

PUBLISH를 실행하면 지정된 채널에 즉시 메시지를 게시하고 이를 수신한 클라이언트 수를 반환합니다. 어떤 방식으로 실패 시나리오가 이것을 관련성 있게 합니까? 장애 조치의 경우를 고려하십시오. TCP 연결이 끊어지면 클라이언트는 다시 연결해야 합니다. 프록시와 통신하는 경우 직접 다시 연결하고 다른 서버를 가져옵니다. Sentinel 검색을 수행하는 경우 시간이 조금 더 걸리고 여전히 다른 서버에 연결됩니다. 문제가 있는 곳은 타이밍에 있습니다. 게시자가 구독자보다 먼저 새 서버에 연결하면 메시지가 사라집니다. 하지만 그런 일이 일어날 수 있습니까? 우리는 SUBSCRIBE 명령을 살펴봅니다.

코드가 구독 명령을 실행하면 특정 연결이 구독하는 채널에서 메시지를 가져와야 하는 메모리 내 데이터 구조에 서버가 등록합니다. 이 정보는 슬레이브에 전파되지 않습니다. 따라서 장애가 발생하고 클라이언트가 다시 연결되면 subscribe 명령을 다시 발행해야 합니다. 그렇지 않으면 새 서버는 특정 연결이 특정 메시지를 수신해야 한다는 것을 알지 못합니다. 이제 구독자가 게시자 이후에 새 마스터에 다시 연결할 수 있는 가능성이 있습니다. 이제 메시지 손실 조건이 존재합니다. 그렇다면 이것을 최소화하거나 방지하는 방법은 무엇입니까? 몇 가지 옵션이 있습니다.

먼저 게시자는 PUBLISH가 이를 수신한 클라이언트 수를 반환한다는 사실을 활용할 수 있습니다. 클라이언트가 수신하지 않은 경우 게시자는 다시 시도할 수 있습니다. 이것이 정적인 가입자 수이거나 "최소한 한 명의 가입자"로 충분한 시스템의 경우 메시지 손실을 방지할 수 있습니다. 이 기준을 충족하지 않는 시스템의 경우 덜 강력하더라도 다른 옵션이 있습니다.

두 번째 옵션은 재연결 창을 제어하는 ​​것입니다. 여기에서 경험적 규칙은 게시자 지연이 구독자 지연보다 최소 3배 이상 길도록 하는 것입니다. 이렇게 하면 메시지를 게시하기 전에 구독자가 다시 연결할 수 있는 최소 세 번의 기회가 제공됩니다. 가입자가 먼저 온라인 상태인 한 메시지는 이더로 이동하지 않습니다. 그러나 제어하지 않는 것보다 더 강력하지만 여전히 메시지 손실 가능성이 있습니다.

이 경쟁 조건을 완화하기 위해 제시할 세 번째 옵션은 잠금 메커니즘을 구축하는 것입니다. 이것은 클라이언트가 연결할 때까지 게시자가 게시하지 못하도록 하는 메커니즘을 구축하거나 사용해야 하므로 확실히 가장 복잡한 경로입니다. 기본 프로세스는 구독자가 수신 준비가 되었다고 등록하고 구독자가 메시지를 게시하기 전에 유효하고 준비된 구독자를 확인하는 공유 위치(예:Zookeeper, 데이터베이스, Consul 등)를 갖는 것입니다. 추가 광고 복잡성을 위해 가입자는 TCP 연결이 끊겼을 때 이 메커니즘에서 스스로를 "설정 해제"해야 합니다.

이것은 모두 트리거된 장애 조치를 위한 것임을 알려야 합니다. 마스터가 응답하지 않으므로 메시지가 전달되지 않기 때문에 작동합니다. 시작된 장애 조치 시나리오에서 마스터는 더 이상 마스터가 아니거나 연결이 끊길 때까지 메시지를 계속 수락합니다. 위의 방법 중 어느 것도 이 시나리오를 적절하고 완전하게 포착하지 못합니다.

장애 조치가 시작된 경우(예:시스템 유지 관리 중에 발생할 수 있음) 첫 번째 옵션과 두 번째 옵션을 모두 사용하고 메시지 손실이 있을 수 있음을 인정하는 것이 가장 큰 도움이 됩니다. 얼마나 많은 메시지 손실이 게시 일정에 따라 완전히 달라집니다. 평균적으로 몇 분마다 메시지를 게시하면 실제로 손실 기간이 발생할 가능성이 매우 낮습니다. 게시 간격이 클수록 위험이 줄어듭니다. 반대로 초당 수백 또는 수천 개의 메시지를 게시하는 경우 확실히 일부를 잃게 됩니다. 관리 프록시 시나리오의 경우 프록시에서 장애 조치가 완료되는 속도에 따라 다릅니다. ObjectRocket의 Redis 플랫폼의 경우 이 창은 최대 1.5초입니다.

따라서 한도에서 실패하는 간격에 대한 한 가지 접근 방식은 구독자가 게시 간격의 1/3 간격으로 다시 연결을 시도하도록 하는 것입니다. 따라서 1분에 한 번씩 메시지를 게시하는 경우 구독자는 최소한 20초마다 다시 연결하고 게시자는 1~2분마다 다시 연결하도록 코딩/구성하십시오. 게시 간격이 3초 표시에 가까워짐에 따라 장애 조치 재연결 프로세스(조회, TCP 핸드셰이크, AUTH, SUBSCRIBE 등)가 총 몇 초가 되기 때문에 달성하기가 더 어려워집니다.

BLPOP과 친구들

이것은 이러한 조건에서 차단 목록 명령이 어떻게 작동하는지에 대한 질문으로 이어집니다. 여기 뉴스는 출판보다 낫습니다. 이 경우 복제되는 데이터 구조를 수정합니다. 또한, 이는 데이터 구조의 수정이기 때문에 재연결 순서로 인해 메시지가 손실될 위험에 대해 동일한 수준이 아닙니다. 생산자가 구독자보다 먼저 다시 연결하면 예상되는 동작에 변경 사항이 없습니다. 생산자는 항목을 목록(또는 POP 및 PUSH)에 푸시하고 소비자가 연결하면 데이터가 거기에 있게 됩니다. 한 가지 예외가 있습니다.

장애 조치가 시작된 경우 간단한 다중 마스터 문제를 고려해야 합니다. 잠시 동안 밀리초 단위로 시작된 장애 조치 중 원래 마스터는 여전히 데이터 수정을 수락하고 슬레이브는 이미 마스터로 승격되었으므로 이러한 수정은 복제되지 않습니다. 이 창은 매우 작습니다. 테스트에서는 한 자릿수 밀리초 정도이지만 존재합니다. 게시와 마찬가지로 이것은 본질적으로 예를 들어 목록 수정 명령을 내리는 생산자와 소비자의 비율이 초당 수천에 도달할 때 생각할 수 있는 조건으로 증가하지만 부실 마스터에게 POP 명령을 실행할 가능성이 거의 없는 코너 조건입니다.

예를 들어 BRPOPLPUSH를 수행하는 작업자의 경우 결과는 이동 중인 항목이 장애 조치(failover) 후 원래 위치로 "돌아가는" 것과 같습니다. BLPOP의 경우 결과는 본질적으로 동일합니다. 장애 조치가 완료된 후 항목이 다시 대기열에 추가된 것으로 나타납니다. 항목이 멱등성 작업인 경우 문제가 되지 않습니다. 멱등성이 아닌 작업의 경우 이를 설명하기 위해 수행해야 하는 방어 코딩의 양은 수정을 수행하는 빈도와 이 문제가 발생할 가능성을 고려하여 실행 중인 작업 또는 항목이 두 번 처리되는 효과를 결정한 결과입니다. 상황. 또한 이것은 장애 조치가 시작된 경우에만 발생하므로 운영 제어 하에 있어야 하며 가능한 한 유지 관리는 시스템이 최소화하거나 심지어는 최소한으로 사용되는 시간에 수행되어야 한다고 조언합니다. 이 가능성을 제거하십시오.

트리거된 장애 조치의 경우 데이터 손실이 예상되지 않습니다. 트리거된 장애 조치는 마스터가 응답하지 않을 때 발생하므로 마스터가 수정되지 않습니다. 그러나 이러한 각 시나리오와 마찬가지로 모든 장애 조치 시나리오에서 필수인 실제 TCP 재연결을 처리하는 문제가 있습니다.

클라이언트 재연결

트리거되거나 시작된 장애 조치 시나리오에서 클라이언트는 Sentinel 조회 또는 동일한 주소에 대한 짧은 창 후를 감지하고 다시 연결해야 합니다. 관리 프록시 계층을 사용하는 ObjectRocket Redis의 경우 클라이언트는 간단히 다시 연결합니다. 실제 장애 조치 프로세스 자체를 완료하는 데 최대 2초가 소요될 수 있습니다. 따라서 클라이언트 코드는 이를 설명해야 합니다. 이상적으로는 연결이 경로를 따라 어딘가에서 단순히 끊어지는 네트워크 블립을 처리하기 위해 즉각적인 재시도가 발생해야 합니다. 그러나 이것은 서버 다시 시작(독립형 Redis 다시 시작 또는 프록시 다시 시작에서 발생)과 같은 경우를 설명하기 위해 재시도가 포함된 백 알고리즘과 함께 수행되어야 합니다.

재연결 시 모든 가입자는 요청 채널과 새 TCP 연결 간의 링크가 설정되어야 하므로 다시 가입해야 합니다. Redis의 PUBSUB 채널은 구독자 또는 게시자가 액세스를 시도할 때 생성되기 때문에 채널을 "다시 생성"할 필요가 없습니다.

이를 수행하는 방법을 알려줍니다. 대답은 사용 중인 클라이언트 라이브러리와 연결 끊김을 처리하는 방법에 따라 크게 달라집니다. 이상적인 시나리오에서 연결은 경계가 충족되면 반환되는 궁극적인 실패와 함께 자동 재시도에 대해 구성할 수 있습니다. 지금까지 나는 이것을 처리하는 방법에 대해 몇 가지 라이브러리를 테스트했으며 극적으로 다릅니다. 여기에서는 다소 일반적인 것인 redis-py에 대해 설명하겠습니다.

좋은 소식의 첫 번째 비트는 연결이 끊어지면 redis-py가 다시 시도하는 것처럼 보인다는 것입니다. 불행히도 즉시 재시도하므로 장애 조치 중에 연결을 안정적으로 복구하기에는 너무 빠릅니다. 또한 이를 구성할 방법이 없는 것 같습니다. 결과적으로 코드는 실패한 재연결 시도를 포착/감지하고 재연결을 직접 관리해야 합니다.

먼저 몇 가지 표준 redis-py 게시 및 구독자 코드 블록을 살펴보겠습니다.

### Publisher example code
r.publish(channel,message)

### Subscriber example code
p = r.pubsub()
p.subscribe(channel)
for item in p.listen():
   # do stuff with message (item)

이것은 매우 간단합니다. 그러나 구독자의 for-loop 중 즉각적인 재시도가 실패하면 redis.ConnectionError 예외가 발생합니다. 까다로운 부분은 p.listen() 줄의 항목 "내부"에서 발생한다는 것입니다. 따라서 이를 적절하게 잡으려면 전체 for 문을 try/except 블록으로 래핑해야 합니다. 이것은 기껏해야 문제가 있고 불필요한 코드 복잡성을 초래합니다.

다른 방법은 대신 다음을 수행하는 것입니다.

### Publisher example code
p = r.pubsub()
p.subscribe(channel)
while True:
    try:
        message = p.get_message()
    except redis.ConnectionError:
        # Do reconnection attempts here such as sleeping and retrying
        p = r.pubsub()
        p.subscribe(channel)
    if message:
        # do something with the message
    time.sleep(0.001)  # be nice to the system :)

이 메서드를 사용하여 get_message()를 직접 호출하여 해당 지점에서 예외를 포착하고 'p' 객체의 연결을 다시 설정할 수 있습니다. 잠자기 시간은 코드 요구 사항에 따라 다릅니다. 물론 구독자가 특정 수의 메시지를 처리할 것으로 예상하고 for 루프가 더 잘 작동한다면 여전히 작동할 것입니다. 게시자의 경우 일반적으로 반복자에서 실행되지 않으므로 코드가 더 간단합니다.

### Publisher example code
while True:
    try:
       rcvd = r.publish(channel,message)
       if rcvd >0:
          break
    except redis.ConnectionError:
       # handle reconnect attempts

이 방법을 사용하면 재시도 여부, 시기 및 빈도를 제어할 수 있습니다. 이는 오류 이벤트를 적절하게 투명하게 처리하는 데 중요합니다. 코드가 Sentinel을 직접 사용하거나 문제의 Redis 서버가 다시 시작된 경우에도 동일한 작업이 필요합니다. 따라서 실제로 모든 게시 코드는 이 기본 메커니즘을 따라야 합니다.

BLPOP과 같은 차단 목록 명령의 경우 데이터가 유지되기 때문에 클라이언트 재접속 순서는 그다지 중요하지 않습니다. 위에서 설명한 try/except 메서드는 명령 실행 결과 "redis.ConnectionError" 예외가 발생했을 때 연결을 다시 설정하는 데 필요합니다.

특히 ObjectRocket Redis 플랫폼에 대한 redis-py의 경우 재시도 창과 함께 3초를 포함하는 이러한 기술을 사용하면 트리거된 장애 조치에 대한 데이터 손실 예상이 없으며 시작된 장애 조치 동안 논스톱 게시자의 경우 약 1.5초 동안 잠재적인 메시지 손실이 발생하지 않습니다. 예를 들어 인스턴스의 세로 크기 조정 중입니다.

코드 예제는 redis-py에만 해당되지만 기본 기술은 서버 재연결을 처리해야 하고 PUBSUB 명령 또는 차단 목록 작업을 사용해야 하는 모든 클라이언트 코드와 함께 사용해야 합니다. 이러한 지식으로 무장하면 이제 이러한 기술을 구현하여 고가용성 Redis 포드를 사용할 수 있으며 애플리케이션이 한밤중에 작업 팀을 깨우지 않고도 장애 조치를 처리할 수 있다는 것을 알 수 있습니다.