분산 시스템에서 데이터 일관성을 보장하는 6가지 방법
편집자 주: 이 기사는 "고가용성 아키텍처 Back Garden" 그룹의 토론을 토대로 편집되었습니다.
사람이 있는 곳에 강과 호수가 있다
강과 호수가 있는 곳에 분쟁이 있다
전자상거래와 기타 사업에서는 시스템은 일반적으로 여러 개의 독립적인 서비스 구성으로 구성됩니다. 분산 호출 중에 데이터 일관성을 해결하는 방법은 무엇입니까?
구체적인 비즈니스 시나리오는 다음과 같습니다. 예를 들어 비즈니스 운영이 서비스 A, B, C를 동시에 호출하는 경우 동시에 성공하거나 동시에 실패해야 합니다. A, B, C는 서로 다른 여러 부서에서 개발하고 서로 다른 서버에 배포한 원격 서비스일 수 있습니다.
분산 시스템에서 일관성을 희생하고 싶지 않다면 CAP 이론은 가용성만 포기할 수 있다고 말하는데 이는 분명히 용납할 수 없는 일입니다. 문제에 대한 논의를 용이하게 하기 위해 데이터 일관성의 기본 이론을 간략하게 소개하겠습니다.
강력한 일관성
약한 일관성
최종 일관성
엔지니어링 실무에서는 시스템의 가용성을 보장하기 위해 대부분의 인터넷 시스템 강력한 일관성 요구 사항을 최종 일관성 요구 사항으로 변환하고 시스템을 통해 멱등성 보장을 구현하여 데이터의 궁극적인 일관성을 보장합니다. 그러나 전자 상거래와 같은 시나리오에서는 데이터 일관성 솔루션과 일반적인 인터넷 시스템(예: MySQL 마스터-슬레이브 동기화) 간에 특정 차이점이 있습니다. 그룹 토론은 다음 6가지 솔루션으로 나누어졌습니다.
비즈니스 통합 솔루션은 주로 인터페이스를 로컬 실행에 통합하는 방법을 채택합니다. 문제 시나리오를 예로 들면, 서비스 A, B, C는 비즈니스를 위한 서비스 D로 통합될 수 있습니다. 그런 다음 이 서비스 D는 로컬 트랜잭션으로 변환될 수 있습니다. 예를 들어, 서비스 D에는 로컬 서비스와 서비스 E가 포함됩니다. 서비스 E는 서비스 A ~ C의 통합입니다.
장점: 분산 트랜잭션을 해결(회피)합니다.
단점: 당연히 원래 계획했던 분할 사업이 서로 결합되어 있고 사업 책임이 명확하지 않아 유지 관리에 도움이 되지 않습니다.
이 방법의 명백한 단점으로 인해 일반적으로 사용을 권장하지 않습니다.
이 솔루션의 핵심은 메시지 로그를 통해 분산 처리가 필요한 작업을 비동기적으로 실행하는 것입니다. 메시지 로그는 로컬 텍스트, 데이터베이스 또는 메시지 대기열에 저장한 다음 비즈니스 규칙을 통해 자동으로 또는 수동으로 다시 시도할 수 있습니다. 수동 재시도는 조정 시스템을 통해 이벤트 후 문제를 처리하기 위해 결제 시나리오에서 더 일반적으로 사용됩니다.
메시지 로그 솔루션의 핵심은 서비스 인터페이스의 멱등성을 보장하는 것입니다.
네트워크 통신 실패, 데이터 패킷 손실 등의 이유를 고려할 때 인터페이스가 멱등성을 보장할 수 없다면 데이터의 고유성을 보장하기 어려울 것입니다.
이베이 접근방식의 주요 아이디어는 다음과 같다.
Base: Acid의 대안
이 솔루션은 eBay 설계자 Dan Pritchett가 2008년 ACM에 게시한 기사입니다. BASE 원리에 대한 설명이거나 궁극적으로 고전적인 기사입니다. 일관성에. 이 기사에서는 데이터 일관성을 보장하는 데 있어 BASE 원칙과 ACID 원칙의 기본적인 차이점을 설명합니다.
ACID가 분할된 데이터베이스에 대한 일관성 옵션을 제공하는 경우 가용성은 어떻게 달성됩니까? 답은
BASE(기본적으로 사용 가능, 소프트 상태, 최종 일관성)입니다.
BASE의 가용성은 전역 시스템 오류가 아닌 로컬 오류를 지원하여 달성됩니다. 간단한 예는 다음과 같습니다. 사용자가 5개의 데이터베이스 서버에 걸쳐 분할된 경우 BASE 설계는 유사한 처리를 권장하므로 한 사용자의 데이터베이스 오류는 해당 특정 호스트의 사용자 중 20%에게만 영향을 미칩니다. 여기에는 마법이 포함되지 않지만 시스템 가용성이 더 높아집니다.
이 문서에서는 거래가 발생하면 거래 테이블에 기록을 추가하고 사용자 테이블의 금액을 수정해야 하는 가장 일반적인 시나리오 중 하나를 설명합니다. 이 두 테이블은 서로 다른 원격 서비스에 속하므로 분산 트랜잭션 일관성 문제가 있습니다.
이 기사에서는 로컬 트랜잭션에서 주요 수정 작업과 사용자 테이블 업데이트 메시지를 완료하는 고전적인 솔루션을 제안합니다. 동시에 사용자 테이블 메시지의 반복적인 소비로 인해 발생하는 문제를 방지하고 여러 번의 재시도에 대한 멱등성을 달성하기 위해 업데이트 레코드 테이블인 presents_applied를 추가하여 처리된 메시지를 기록합니다.
시스템의 실행 의사 코드는 다음과 같습니다
(클릭하면 전체 화면으로 사진이 확대됩니다.)
위의 방법을 바탕으로 첫 번째 단계에서 트랜잭션 보장은 로컬 데이터베이스, 추가된 트랜잭션 테이블 및 메시지 대기열을 통해 이루어집니다.
두 번째 단계에서는 메시지 큐를 각각 읽고(삭제는 아님) 업데이트 레코드 테이블인 presents_applied를 판단하여 해당 레코드가 실행되었는지 여부를 감지하고, 실행되지 않은 레코드는 사용자 테이블을 수정합니다. 그런 다음 작업이 업데이트_적용됨에 기록되고 트랜잭션이 성공적으로 실행된 후 대기열이 삭제됩니다.
위의 방법을 통해 분산 시스템의 궁극적인 일관성이 달성됩니다. eBay의 솔루션에 대해 자세히 알아보려면 기사 마지막에 있는 링크를 참조하세요.
사업 규모가 지속적으로 확장되면서 전자상거래 웹사이트는 일반적으로 분할의 길을 걷고 있습니다. 원래의 단일 애플리케이션을 서로 다른 책임을 가진 여러 하위 시스템으로 분할하는 것입니다. 예를 들어 과거에는 사용자 중심, 고객 중심, 운영 중심 기능을 하나의 시스템에 담았지만 이제는 주문센터, 상담원 관리, 운영시스템, 견적센터, 재고관리 등 여러 하위 시스템으로 분리됐다. .
이별할 때 가장 먼저 마주하는 것은 무엇인가요?
원래 모놀리식 애플리케이션에서는 모든 기능이 함께 있었고 스토리지도 함께 있었습니다. 예를 들어 작업이 주문을 취소하려는 경우 주문 테이블의 상태를 직접 업데이트한 다음 재고 테이블을 업데이트해도 괜찮습니다. 단일 애플리케이션이고 라이브러리가 함께 있기 때문에 모두 하나의 트랜잭션에 포함될 수 있으며 관계형 데이터베이스는 일관성을 보장합니다.
그러나 분할 후에는 다릅니다. 서로 다른 하위 시스템에는 자체 저장소가 있습니다. 예를 들어 주문 센터는 자체 주문 라이브러리만 관리하는 반면 재고 관리도 자체 라이브러리를 갖고 있습니다. 그러다가 운영체제가 주문을 취소하면 직접 운영 라이브러리로 가지 않고 인터페이스 호출 등을 통해 주문센터와 재고관리 서비스를 호출한다. 여기에는 "분산 트랜잭션" 문제가 포함됩니다.
분산 트랜잭션에는 두 가지 솔루션이 있습니다.
1. 비동기 메시지 사용에 우선순위를 둡니다.
위에서 언급한 것처럼 비동기 메시지를 사용하는 소비자 측에서는 멱등성을 구현해야 합니다.
멱등성을 갖는 방법에는 두 가지가 있습니다. 한 가지 방법은 비즈니스 논리가 멱등성을 갖도록 하는 것입니다. 예를 들어, 결제 성공 메시지를 수신한 후 주문 상태가 결제 완료로 변경되며, 현재 상태가 결제 완료인 경우, 또 결제 성공 메시지를 받았다는 것은 해당 메시지가 반복되어 바로 성공 메시지로 처리된다는 의미입니다.
또 다른 방법은 비즈니스 로직이 멱등성을 보장할 수 없는 경우 중복 제거 테이블이나 유사한 구현을 추가하는 것입니다. 생산자 측의 경우 동일한 비즈니스 데이터베이스 인스턴스에 메시지 라이브러리를 배치하고 동일한 로컬 트랜잭션에서 메시지와 비즈니스 작업을 보냅니다. 메시지를 보낼 때 메시지가 즉시 전송되지는 않습니다. 대신 메시지 레코드가 메시지 라이브러리에 삽입된 다음 트랜잭션이 커밋되면 메시지가 비동기적으로 전송됩니다. 메시지 큐가 발견되면 서비스 이상 또는 네트워크 문제가 있는 경우 메시지가 성공적으로 전송되지 않으면 메시지는 여기에 남아 있으며 다른 서비스가 지속적으로 이러한 메시지를 검색하여 다시 보냅니다.
2. 일부 비즈니스는 비동기 메시징에 적합하지 않으며 거래에 관련된 모든 당사자는 동기적으로 결과를 얻어야 합니다. 이 상황의 구현은 실제로 위와 유사합니다. 거래 기록 라이브러리는 각 참가자의 로컬 비즈니스 라이브러리의 동일한 인스턴스에 배치됩니다.
예를 들어 A는 B와 C를 동기적으로 호출합니다. A는 로컬 트랜잭션이 성공하면 로컬 트랜잭션 기록 상태를 업데이트하며, B와 C도 마찬가지입니다. A가 B를 한 번 호출하지 못한 경우 B가 실제로 실패한 것이 실패일 수도 있고, 호출 시간이 초과되어 B가 실제로 성공한 것일 수도 있습니다. 중앙 서비스는 3자의 거래 기록을 비교하고 최종 결정을 내린다. 현재 3자의 거래기록이 A성공, B실패, C성공이라고 가정하자. 그런 다음 특정 시나리오에 따라 최종 결정을 내리는 방법에는 두 가지가 있습니다.
시나리오 b에 대한 특별 설명: 예를 들어 B는 재고 공제 서비스를 호출했을 때 어떤 이유로 실패했습니다. 처음 시도했는데 또 실패했는데, 이때 인벤토리가 0이 되어 재시도를 성공할 수 없습니다. 이때는 A와 C를 롤백하는 방법밖에 없습니다.
그렇다면 어떤 사람들은 메시지 라이브러리나 거래 기록 라이브러리를 비즈니스 라이브러리의 동일한 인스턴스에 두는 것이 비즈니스를 침범할 것이라고 생각할 수 있으며, 비즈니스는 여전히 이 라이브러리에 관심을 기울여야 합니다. ?
실제로 운영 및 유지 관리 방법을 사용하여 개발 침입을 단순화할 수 있습니다. 우리의 방법은 DBA가 프레임워크 계층(메시지 클라이언트)을 통해 회사의 모든 MySQL 인스턴스에서 이 라이브러리를 사전 초기화하도록 하는 것입니다. 또는 트랜잭션 RPC 프레임워크)은 이 라이브러리를 배후에서 투명하게 운영합니다. 비즈니스 개발자는 자신의 비즈니스 로직에만 관심을 가지면 되며 이 라이브러리에 직접 액세스할 필요가 없습니다.
요약하자면 두 방법의 기본 원칙은 실제로 유사합니다. 즉, 분산 트랜잭션을 여러 로컬 트랜잭션으로 변환한 다음 재시도 및 기타 방법을 사용하여 최종 일관성을 달성한다는 것입니다.
트랜잭션 생성의 일반적인 프로세스
트랜잭션 생성 프로세스를 일련의 확장 가능한 기능 포인트로 추상화합니다. 각 기능 포인트는 여러 구현을 가질 수 있습니다(특정 구현: 조합/상호가 있습니다. 그들 사이의 배제 관계). 다양한 기능점을 특정 프로세스에 따라 연결함으로써 트랜잭션 생성 프로세스가 완성됩니다.
직면한 문제
각 기능 포인트의 구현은 외부 서비스에 따라 달라질 수 있습니다.
그렇다면 다양한 서비스 간의 데이터 일관성을 어떻게 보장할 수 있을까요? 예를 들어, 쿠폰 서비스 잠금 호출이 타임아웃되는데, 쿠폰 잠금 성공 여부가 확실하지 않은 경우 어떻게 처리해야 하나요? 또 다른 예는 쿠폰 잠금에 성공했지만 재고 차감에 실패하는 경우입니다.
솔루션 선택
서비스 종속성이 너무 많으면 관리 복잡성이 증가하고 안정성 위험이 증가합니다. 10개의 서비스에 크게 의존한다면 그 중 9개는 성공적으로 실행되고 마지막 9개는 롤백되지 않을까요? 비용은 여전히 매우 높습니다.
따라서 비실시간, 비강력 일관성 관련 비즈니스 작성을 위해 대규모 프로세스를 여러 개의 작은 로컬 트랜잭션으로 분할한다는 전제하에 로컬 트랜잭션이 성공적으로 실행된 후 전송하기로 결정했습니다. 메시지 알림 및 관련 트랜잭션의 비동기 실행을 위한 솔루션입니다.
메시지 알림은 100% 성공을 보장할 수 없는 경우가 많으며 메시지 알림 후에도 수신자의 비즈니스가 성공적으로 실행될 수 있는지 알 수 없습니다. 전자의 문제는 재시도를 통해 해결될 수 있고 후자의 문제는 트랜잭션 메시지를 사용하여 보장될 수 있습니다.
따라서 현재 실시간 동기화 및 Strong Consistency 요구 사항이 필요한 비즈니스 시나리오만 있습니다. 거래 생성 과정에서 쿠폰 잠금과 재고 차감이 두 가지 일반적인 시나리오입니다.
언뜻 보면 여러 시스템 간의 데이터 일관성을 보장하려면 분산 트랜잭션 프레임워크를 도입하여 문제를 해결해야 합니다. 그러나 2단계 커밋과 유사한 매우 무거운 분산 트랜잭션 프레임워크를 도입하면 전자 상거래 분야에서 복잡성이 급격히 증가하므로 절대적인 강력한 일관성이 너무 이상적이므로 준실시간을 선택할 수 있습니다. 최종 일관성.
트랜잭션 생성 과정에서 먼저 보이지 않는 주문을 생성한 후 쿠폰 잠금 및 재고 차감을 동기적으로 호출할 때 호출 예외(실패 또는 타임아웃)에 대한 응답으로 MQ에 취소 메시지를 보냅니다. . 메시지가 전송되지 않으면 메시지를 받은 후 로컬에서 시간별 비동기 재시도가 수행되고 쿠폰 시스템과 인벤토리 시스템은 비즈니스 롤백이 필요한지 여부를 결정하여 준으로 여러 로컬 트랜잭션의 최종 결과를 보장합니다. - 실시간.
또한 업계에서 일반적으로 사용되는 솔루션은 Alipay가 2PC를 기반으로 개선한 Alipay의 xts 솔루션입니다. 주요 아이디어는 다음과 같으며, 대부분의 정보는 공식 홈페이지에서 인용되었습니다.
분산 트랜잭션 서비스 소개
분산 트랜잭션 서비스(DTS)는 대규모 분산 환경에서 트랜잭션의 최종 결과를 보장하는 데 사용되는 분산 트랜잭션 프레임워크입니다. DTS는 구조적으로 xts-client와 xts-server의 두 부분으로 나뉩니다. 전자는 클라이언트 응용 프로그램에 포함된 JAR 패키지로 주로 트랜잭션 데이터를 작성하고 처리하는 역할을 담당합니다. 후자는 독립적인 시스템으로 이상 현상을 주로 담당합니다. 거래가 복구됩니다.
핵심 기능
기존 관계형 데이터베이스의 트랜잭션 모델은 ACID 원칙을 준수해야 합니다. 단일 데이터베이스 모드에서 ACID 모델은 데이터 무결성을 효과적으로 보장할 수 있습니다. 그러나 대규모 분산 환경에서는 비즈니스가 여러 데이터베이스에 걸쳐 있는 경우가 많습니다. 이러한 여러 데이터베이스 간의 데이터 일관성을 보장하려면 다른 작업 라인이 필요합니다. 2PC(2 Phase Commit, two-phase commit)는 JavaEE 사양에서 Cross-DB 환경의 트랜잭션 문제를 처리하는 데 사용되지만 2PC는 확장 방지 모드입니다. 즉, 트랜잭션 프로세스 중에 참가자가 모든 리소스를 보유해야 합니다. 전체 분산 트랜잭션이 종료될 때까지의 시간입니다. 이처럼 사업 규모가 수천만 이상에 이르게 되면 2PC의 한계는 점점 더 뚜렷해지고 시스템 확장성은 매우 열악해진다. 이를 바탕으로 DTS인 2PC와 유사한 분산 트랜잭션 솔루션을 구현하기 위해 BASE라는 아이디어를 채택했습니다. DTS는 데이터 일관성 요구 사항을 고려하면서 분산 환경에서 고가용성과 높은 안정성을 완벽하게 보장합니다. 가장 큰 특징은 데이터의 최종 일관성을 보장하는 것입니다.
간단히 말하면 DTS 프레임워크는 다음과 같은 특징을 가지고 있습니다.
다음은 분산 트랜잭션 프레임워크의 흐름도입니다.
구현
및 2PC 프로토콜 비교
1. 전자상거래 사업
회사의 결제 부서는 다른 제3자의 결제 시스템에 접속하여 사업 부서에 결제 서비스를 제공합니다. Dubbo Serve 기반의 RPC.
사업부서의 경우 전자상거래부서의 주문결제를 호출해야 합니다.
업무규칙상 주문결제의 실시간성과 일관성을 확보해야 합니다. 즉, 포인트 추가가 성공해야 합니다.
우리가 사용하는 방법은 동기 호출이며 로컬 트랜잭션 비즈니스가 먼저 처리됩니다. 포인트 사업은 결제에 비해 상대적으로 단순하고 사업적 영향이 낮다는 점을 고려하여 포인트 플랫폼은 포인트 적립 및 인출을 위한 인터페이스를 제공합니다.
구체적인 프로세스는 먼저 포인트 플랫폼을 호출하여 사용자 포인트를 늘린 다음 결제 처리를 위해 결제 플랫폼을 호출하는 것입니다. 처리에 실패하면 catch 메소드가 포인트 플랫폼의 출금 메소드를 호출하여 출금합니다. 이번에 처리된 포인트 주문입니다.
(이미지를 클릭하면 전체 화면으로 확대됩니다.)
2. 사용자 정보 변경
분산 서비스에는 파생 지원 시스템, 특히 우리의 메시지에 대한 많은 요구 사항이 있습니다. -based, 로그의 최종 일관성 솔루션은 메시지의 백로그, 소비, 모니터링, 경보 등을 고려해야 합니다.
분할된 데이터베이스에서는 가용성을 위해 어느 정도 일관성을 교환하면 확장성이 크게 향상될 수 있습니다.
영어 버전: http://queue.acm.org/detail.cfm?id= 1394128
중국어 버전: http://article.yeeyan.org/view/167444/125572
솔루션을 제공해 주신 Li Yufu, Yu Zhaohui, Mogujie Qigong 및 기타 여러분들께 감사드립니다. 그룹 구성원 또한 이 기사의 내용에 기여했습니다.
이 기사의 편집자는 Li Yufu와 Tim Yang입니다. 재인쇄 출처를 @highavailabilityarchitecture로 명시해 주세요.