나는 실제로 호기심에서 실제 소스를 연구하는 데 시간이 걸렸으며 그 뒤에있는 아이디어는 매우 간단합니다. 이 글을 쓸 당시의 최신 버전은 3.2.1입니다.
소비자가 읽을 수 있도록 데이터를 보유 할 사전 할당 된 이벤트를 저장하는 버퍼가 있습니다.
버퍼는 버퍼 슬롯의 가용성을 설명하는 길이의 플래그 배열 (정수 배열)로 백업됩니다 (자세한 내용은 추가 참조). 배열은 java # AtomicIntegerArray처럼 액세스되므로이 설명을 위해 배열을 하나라고 가정 할 수 있습니다.
여러 개의 생산자가있을 수 있습니다. 생산자가 버퍼에 쓰려고 할 때 AtomicLong # getAndIncrement 호출과 같이 긴 숫자가 생성됩니다. Disruptor는 실제로 자체 구현을 사용하지만 동일한 방식으로 작동합니다. 이것을 길게 생성자 (producerCallId)라고하자. 비슷한 방식으로 consumerCallId는 소비자 ENDS가 버퍼에서 슬롯을 읽을 때 생성됩니다. 가장 최근의 consumerCallId에 액세스합니다.
(소비자가 많은 경우 ID가 가장 낮은 통화가 선택됩니다.)
그런 다음이 ID를 비교하고 둘 사이의 차이가 버퍼 쪽보다 작 으면 생산자가 쓸 수 있습니다.
(producerCallId가 최근 consumerCallId + bufferSize보다 큰 경우 버퍼가 가득 차서 스팟을 사용할 수있을 때까지 생산자가 버스 대기하도록 강제합니다.)
그런 다음 생산자는 자신의 callId (prducerCallId modulo bufferSize)를 기준으로 버퍼의 슬롯이 할당되지만 bufferSize는 항상 2의 제곱 (버퍼 생성시 제한)이므로 실제로 사용되는 작업은 producerCallId & (bufferSize-1)입니다. )). 그런 다음 해당 슬롯에서 이벤트를 자유롭게 수정할 수 있습니다.
(실제 알고리즘은 최적화를 위해 별도의 원자 참조로 최근 consumerId를 캐싱하는 것을 포함하여 조금 더 복잡합니다.)
이벤트가 수정되면 변경이 "게시"됩니다. 플래그 배열에서 각 슬롯을 게시하면 업데이트 된 플래그로 채워집니다. 플래그 값은 루프 수 (producerCallId를 bufferSize로 나눈 값입니다 (bufferSize는 2의 거듭 제곱이므로 실제 작업은 올바른 시프트 임).
유사한 방식으로 임의의 수의 소비자가있을 수 있습니다. 소비자가 버퍼에 액세스하려고 할 때마다 consumerCallId가 생성됩니다 (소비자에 소비자가 추가 된 방식에 따라 id 생성에 사용 된 원자가 각각에 대해 공유되거나 분리 될 수 있음). 그런 다음이 consumerCallId를 가장 최근의 producentCallId와 비교하고 둘 중 작은 값이면 독자가 진행할 수 있습니다.
(producerCallId가 consumerCallId의 경우에도 마찬가지입니다. 이는 버퍼가 비어 있고 소비자가 기다려야 함을 의미합니다. 대기 방식은 방해 요소 작성 중 WaitStrategy에 의해 정의됩니다.)
개별 소비자 (자체 아이디 생성기가있는 소비자)의 경우 다음으로 확인해야 할 것은 배치 소비 능력입니다. 버퍼의 슬롯은 각각 consumerCallId (인덱스는 생산자와 동일한 방식으로 결정됨)에서 최근의 producerCallId에 이르는 슬롯까지 순서대로 검사됩니다.
이들은 플래그 배열에 작성된 플래그 값을 consumerCallId에 대해 생성 된 플래그 값과 비교하여 루프에서 검사됩니다. 플래그가 일치하면 슬롯을 채우는 생산자가 변경 사항을 커밋했음을 의미합니다. 그렇지 않으면 루프가 끊어지고 커밋 된 가장 높은 changeId가 반환됩니다. ConsumerCallId에서 changeId로 수신 된 슬롯은 일괄 적으로 소비 될 수 있습니다.
소비자 그룹이 함께 공유하는 경우 (공유 ID 생성기가있는 그룹), 각 그룹은 단일 callId 만 가져 오며 해당 단일 callId의 슬롯 만 확인하여 리턴합니다.