분산 이벤트 소스 시스템에서 일관성을 유지하기위한 패턴?


12

나는 최근 에 이벤트 소싱 에 대해 읽고 있었고 그 뒤에있는 아이디어를 정말로 좋아하지만 다음과 같은 문제가 있습니다.

명령 (예 : 웹 서버)을 수신하고 결과적으로 이벤트를 생성하여 중앙 저장소에 저장하는 N 개의 동시 프로세스가 있다고 가정하십시오. 또한 상점에서 이벤트를 순차적으로 적용하여 모든 임시 애플리케이션 상태가 개별 프로세스의 메모리에 유지된다고 가정하십시오.

이제 다음과 같은 비즈니스 규칙이 있다고 가정하겠습니다. 각 개별 사용자마다 고유 한 사용자 이름이 있어야합니다.

두 프로세스가 동일한 사용자 이름 X에 대한 사용자 등록 명령을 수신하면 둘 다 X가 사용자 이름 목록에 없는지 확인하고 규칙은 두 프로세스 모두에 대해 유효성을 검증하고 둘 다 "사용자 이름 X를 가진 새 사용자"이벤트를 상점에 저장합니다. .

비즈니스 규칙을 위반하여 (일명 사용자 이름이 동일한 두 명의 개별 사용자가 있음) 이제 일관성이없는 글로벌 상태가되었습니다.

기존의 N 서버 <-> 1 RDBMS 스타일 시스템에서 데이터베이스는 이러한 불일치를 방지하는 데 도움이되는 중앙 동기화 지점으로 사용됩니다.

제 질문은 이벤트 소스 시스템이 일반적으로이 문제에 어떻게 접근합니까? 그들은 단순히 모든 명령을 순차적으로 처리합니까 (예 : 상점에 쓸 수있는 프로세스의 양을 1로 제한)?


1
이러한 제한은 코드로 제어됩니까, 아니면 DB 제약입니까? N 이벤트는 순서대로 디스패치 처리되거나 처리되지 않을 수 있습니다 ... N 이벤트는 서로를 구분하지 않고 동시에 유효성 검증을 거칠 수 있습니다. 주문이 중요한 경우 유효성 검사를 동기화해야합니다. 또는 대기열을 사용하여 이벤트를 대기열에
넣는

@ 레이브 맞아. 간단하게하기 위해 데이터베이스가없고 모든 상태가 메모리에 있다고 가정했습니다. 대기열을 통해 특정 유형의 명령을 순차적으로 처리하는 것이 옵션 일 수 있지만 어떤 명령이 다른 명령에 영향을 줄 수 있는지 결정하는 것이 복잡 할 수 있다고 생각합니다. 단일 프로세스 처리 명령을 갖는 것과 동일한 대기열에 모든 명령을 넣을 수 있습니다. : / 예를 들어 블로그 게시물에 댓글을 추가하는 사용자가있는 경우 "사용자 삭제", "사용자 일시 중지", "블로그 게시물 삭제", "블로그 게시물 댓글 비활성화"등이 모두 같은 대기열에 있어야합니다.
Olivier Lalonde

1
대기열이나 세마포어로 작업하는 것은 간단하지 않습니다. 동시성 또는 이벤트 소스 패턴으로 작업하지 마십시오. 그러나 기본적으로 모든 솔루션은 시스템의 이벤트 트래픽을 조정합니다. 그러나 그것은 흥미로운 패러다임입니다. Redis와 같은 튜플을 지향하는 외부 캐시도 있습니다.이 캐시는 엔터티의 마지막 상태 캐시 또는 해당 엔터티가 현재 처리되는 경우와 같이 노드 간에이 트래픽을 관리하는 데 도움이 될 수 있습니다. 공유 캐시는 이러한 종류의 개발에서 매우 일반적입니다. 복잡해 보이지만 포기하지는 않습니다 ;-) 매우 흥미 롭습니다
Laiv

답변:


6

기존의 N 서버 <-> 1 RDBMS 스타일 시스템에서 데이터베이스는 이러한 불일치를 방지하는 데 도움이되는 중앙 동기화 지점으로 사용됩니다.

이벤트 소스 시스템에서 "이벤트 저장소"는 동일한 역할을합니다. 이벤트 소스 오브젝트의 경우, 쓰기는 특정 버전의 이벤트 스트림에 새 이벤트를 추가 한 것입니다. 따라서 동시 프로그래밍과 마찬가지로 명령을 처리 할 때 해당 기록에 대한 잠금을 얻을 수 있습니다. 이벤트 소스 시스템이보다 낙관적 인 접근 방식을 사용하는 것이 더 일반적입니다. 이전 기록을로드하고 새 기록을 계산 한 다음 비교 및 ​​스왑합니다. 다른 명령이 해당 스트림에 기록 된 경우 비교 및 ​​스왑이 실패합니다. 거기에서 명령을 다시 실행하거나 명령을 포기하거나 결과를 기록에 병합 할 수도 있습니다.

M 명령이있는 모든 N 서버가 단일 스트림에 쓰려고하면 경합이 중대한 문제가됩니다. 여기서 일반적인 대답은 모델의 각 이벤트 소스 엔티티에 히스토리를 할당하는 것입니다. 따라서 User (Bob)은 User (Alice)와는 별개의 기록을 가지고 있으며, 하나의 쓰기는 다른 쓰기를 차단하지 않습니다.

제 질문은 이벤트 소스 시스템이 일반적으로이 문제에 어떻게 접근합니까? 그들은 단순히 모든 명령을 순차적으로 처리합니까?

세트 유효성 검사에 대한 Greg Young

비즈니스 로직을 서비스 계층으로 이동하지 않고 도메인 개체 속성에 대한 고유 한 제약 조건을 확인하는 우아한 방법이 있습니까?

짧은 대답은, 대부분의 경우, 그 요구 사항을보다 깊이 조사한 결과, (a) 다른 요구 사항에 대한 이해가 부족한 대리인이거나 (b) "규칙"위반이 감지 될 수있는 경우 수용 가능하다는 것입니다 (예외 보고서). , 일정 기간 내에 완화되거나 빈도가 낮습니다 (예 : 클라이언트는 이름을 사용할 수 있도록 명령을 발송하기 전에 이름을 사용할 수 있는지 확인할 수 있음).

이벤트 저장소가 유효성 검증 세트 (예 : 관계형 데이터베이스)에 능숙한 경우, 이벤트를 유지하는 동일한 트랜잭션에서 "고유 이름"테이블에 기록하여 요구 사항을 구현할 수 있습니다.

경우에 따라 모든 사용자 이름을 동일한 스트림에 게시하여 요구 사항을 적용 할 수 있습니다 (도메인 모델의 일부로 메모리의 이름 집합을 평가할 수 있음). -이 경우 두 프로세스가 "the"스트림 히스토리 업데이트 시도를 업데이트하지만 비교 및 ​​스왑 조작 중 하나가 실패하고 해당 명령을 재 시도하면 충돌을 감지 할 수 있습니다.


1) 제안과 참조에 감사드립니다. "비교 및 스왑"이라고 말하면 이벤트를 저장할 때 프로세스가 명령 처리를 시작한 후 도착한 새 이벤트를 감지했음을 의미합니까? "compare-and-swap"시맨틱을 지원하는 이벤트 저장소가 필요하다고 생각합니까? (예 : "마지막 이벤트에 ID X가있는 경우에만이 이벤트를 작성하십시오")?
Olivier Lalonde

2) 또한 일시적인 불일치를 수락하고 결국 복구하는 아이디어가 마음에 들지만 신뢰할 수있는 방식으로 코딩하는 방법을 모르겠습니다 ... 이벤트를 순차적으로 확인하고 롤백 이벤트가 감지되면 전용 프로세스가있을 수 있습니다. 뭔가 잘못 됐어? 감사!
Olivier Lalonde

(1) 나는 "새로운 사건들"보다는 "새로운 역사의 버전"이라고 말하지만, 당신은 생각을 가지고 있습니다. 우리가 기대하는 역사 만 바꾸십시오.
VoiceOfUnreason

(2) 예. 저장소에서 이벤트를 일괄 처리로 읽고 일괄 처리가 끝날 때 예외 보고서 ( "Bob이라는 사용자가 너무 많음")를 브로드 캐스트하거나 문제를 보완하기위한 명령을 전달하는 논리가 있습니다 (올바른 응답이 인간의 개입없이 계산 가능).
VoiceOfUnreason

2

사운드는 (비즈니스 프로세스 구현할 수 같은 saga맥락에서 Domain Driven Design사용자가 취급하는 사용자 등록을) CRDT.

자원

  1. https://doc.akka.io/docs/akka/current/distributed-data.html http://archive.is/t0QIx

  2. "Akka 분산 데이터와 CRDTs" https://www.slideshare.net/markusjura/crdts-with-akka-distributed-data 에 대한 자세한 내용은

    • CmRDTs-조작 기반 CRDT
    • CvRDTs-상태 기반 CRTD
  3. 스칼라 코드 예제 https://github.com/akka/akka-samples/tree/master/akka-sample-distributed-data-scala . 아마도 "쇼핑 카트"가 가장 적합 할 것입니다.

  4. Akka Cluster 둘러보기 – Akka 분산 데이터 https://manuel.bernhardt.io/2018/01/03/tour-akka-cluster-akka-distributed-data/
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.