paint-brush
.NET에서 이벤트 스트리밍 애플리케이션을 구축하는 방법~에 의해@bbejeck
2,959 판독값
2,959 판독값

.NET에서 이벤트 스트리밍 애플리케이션을 구축하는 방법

~에 의해 Bill Bejeck14m2023/02/13
Read on Terminal Reader
Read this story w/o Javascript

너무 오래; 읽다

스트림 처리는 이벤트를 애플리케이션의 기본 입력 또는 출력으로 보는 소프트웨어 개발에 대한 접근 방식입니다. 이 블로그 게시물에서는 .NET 생산자 및 소비자 클라이언트인 Apache Kafka와 Microsoft의 TPL(Task Parallel Library)을 사용하여 이벤트 스트리밍 애플리케이션을 구축합니다. Kafka 클라이언트와 TPL은 대부분의 무거운 작업을 처리합니다. 비즈니스 로직에만 집중하면 됩니다.
featured image - .NET에서 이벤트 스트리밍 애플리케이션을 구축하는 방법
Bill Bejeck HackerNoon profile picture
0-item


잠시 멈춰서 일상을 생각해 보면 모든 것을 하나의 사건으로 쉽게 볼 수 있습니다. 다음 순서를 고려하십시오.


  1. 자동차의 "연료 부족" 표시등이 켜집니다.
  2. 결과적으로 다음 주유소에 들러 연료를 채우게 됩니다.
  3. 차에 주유하면 할인을 받기 위해 회사의 보상 클럽에 가입하라는 메시지가 표시됩니다.
  4. 안으로 들어가서 가입하고 다음 구매에 사용할 수 있는 크레딧을 받으세요.


우리는 여기에서 계속할 수 있지만 내 요점은 다음과 같습니다. 인생은 일련의 사건입니다. 그렇다면 오늘날 새로운 소프트웨어 시스템을 어떻게 설계하시겠습니까? 다양한 결과를 수집하여 임의의 간격으로 처리하시겠습니까, 아니면 하루가 끝날 때까지 기다려 처리하시겠습니까? 아니요, 그렇지 않을 것입니다. 각 이벤트가 발생하는 즉시 조치를 취하고 싶을 것입니다. 물론, 개별적인 상황에 즉각적으로 대응할 수 없는 경우도 있을 수 있습니다. 하루 분량의 거래를 한 번에 덤프하는 것을 생각해 보세요. 그러나 여전히 데이터를 받자마자 상당한 규모의 일시불 이벤트를 실행하게 됩니다.


그렇다면 이벤트 작업을 위한 소프트웨어 시스템을 어떻게 구현합니까? 대답은 스트림 처리입니다.


스트림 처리란 무엇입니까?

이벤트 데이터를 처리하기 위한 사실상의 기술이 된 스트림 처리는 이벤트를 애플리케이션의 기본 입력 또는 출력으로 보는 소프트웨어 개발에 대한 접근 방식입니다. 예를 들어, 정보에 따라 조치를 취하거나 잠재적인 사기성 신용 카드 구매에 대응하기 위해 기다리는 것은 의미가 없습니다. 다른 경우에는 마이크로서비스에서 들어오는 레코드 흐름을 처리해야 할 수도 있으며 이를 가장 효율적으로 처리하는 것이 애플리케이션에 가장 적합합니다.

사용 사례가 무엇이든 이벤트 스트리밍 접근 방식이 이벤트 처리에 가장 적합한 접근 방식이라고 해도 무방합니다.


이 블로그 게시물에서는 .NET 생산자 및 소비자 클라이언트인 Apache Kafka®와 Microsoft의 TPL(Task Parallel Library)을 사용하여 이벤트 스트리밍 애플리케이션을 구축합니다. 언뜻 보기에 이 세 가지를 모두 함께 작업할 가능성이 있는 후보로 자동으로 통합하지 않을 수도 있습니다. 물론 Kafka와 .NET 클라이언트는 훌륭한 조합이지만 TPL은 어디에 속할까요?


대개 처리량은 핵심 요구 사항이며 Kafka 사용과 다운스트림 처리 간의 임피던스 불일치로 인한 병목 현상을 방지하기 위해 일반적으로 기회가 발생할 때마다 프로세스 내 병렬화를 제안합니다.


세 가지 구성 요소가 모두 함께 작동하여 강력하고 효율적인 이벤트 스트리밍 애플리케이션을 구축하는 방법을 알아보려면 계속 읽어보세요. 가장 좋은 점은 Kafka 클라이언트와 TPL이 대부분의 무거운 작업을 처리한다는 것입니다. 비즈니스 로직에만 집중하면 됩니다.


애플리케이션을 살펴보기 전에 각 구성 요소에 대해 간략하게 설명하겠습니다.

아파치 카프카

스트림 처리가 이벤트 스트림 처리를 위한 사실상의 표준이라면 Apache Kafka는 이벤트 스트리밍 애플리케이션 구축을 위한 사실상의 표준입니다. Apache Kafka는 확장성이 뛰어나고 탄력적이며 내결함성이 있고 안전한 방식으로 제공되는 분산 로그입니다. 간단히 말해서 Kafka는 브로커(서버)와 클라이언트를 사용합니다. 브로커는 데이터 센터 또는 클라우드 지역에 걸쳐 있을 수 있는 Kafka 클러스터의 분산 스토리지 계층을 형성합니다. 클라이언트는 브로커 클러스터에서 이벤트 데이터를 읽고 쓰는 기능을 제공합니다. Kafka 클러스터는 내결함성이 있습니다. 브로커 중 하나가 실패하면 다른 브로커가 작업을 맡아 지속적인 운영을 보장합니다.

Confluent .NET 클라이언트

이전 단락에서 클라이언트는 Kafka 브로커 클러스터에 쓰거나 Kafka 브로커 클러스터에서 읽는다고 언급했습니다. Apache Kafka는 Java 클라이언트와 함께 번들로 제공되지만 이 블로그 게시물에서 애플리케이션의 핵심인 .NET Kafka 생산자 및 소비자와 같은 다른 여러 클라이언트도 사용할 수 있습니다. .NET 생산자 및 소비자는 Kafka를 통한 이벤트 스트리밍의 강력한 기능을 .NET 개발자에게 제공합니다. .NET 클라이언트에 대한 자세한 내용은 설명서를 참조하세요.

작업 병렬 라이브러리

작업 병렬 라이브러리( TPL )는 "System.Threading 및 System.Threading.Tasks 네임스페이스의 공개 유형 및 API 세트"로, 동시 애플리케이션 작성 작업을 단순화합니다. TPL은 다음 세부 정보를 처리하여 동시성을 추가하는 작업을 보다 관리하기 쉽게 만듭니다.


1. 작업 분할 처리 2. ThreadPool에서 스레드 예약 3. 취소, 상태 관리 등과 같은 하위 수준 세부 정보


결론은 TPL을 사용하면 애플리케이션의 처리 성능을 최대화하는 동시에 비즈니스 논리에 집중할 수 있다는 것입니다. 특히 TPL의 데이터 흐름 라이브러리 하위 집합을 사용합니다.


데이터 흐름 라이브러리는 진행 중인 메시지 전달 및 파이프라인 작업을 허용하는 행위자 기반 프로그래밍 모델입니다. Dataflow 구성요소는 TPL의 유형 및 예약 인프라를 기반으로 하며 C# 언어와 원활하게 통합됩니다. Kafka에서 읽는 것은 일반적으로 매우 빠르지만 처리(DB 호출 또는 RPC 호출)는 일반적으로 병목 현상이 발생합니다. 주문 보장을 희생하지 않으면서 더 높은 처리량을 달성할 수 있는 모든 병렬화 기회는 고려할 가치가 있습니다.


이 블로그 게시물에서는 .NET Kafka 클라이언트와 함께 이러한 Dataflow 구성 요소를 활용하여 데이터가 사용 가능해지면 이를 처리하는 스트림 처리 애플리케이션을 구축하겠습니다.

데이터 흐름 블록

우리가 빌드할 애플리케이션에 들어가기 전에; TPL 데이터 흐름 라이브러리를 구성하는 요소에 대한 배경 정보를 제공해야 합니다. 여기에 설명된 접근 방식은 높은 처리량이 필요한 CPU 및 I/O 집약적인 작업이 있는 경우 가장 적합합니다. TPL 데이터 흐름 라이브러리는 들어오는 데이터나 레코드를 버퍼링하고 처리할 수 있는 블록으로 구성되며, 블록은 세 가지 범주 중 하나에 속합니다.


  1. 소스 블록 – 데이터 소스 역할을 하며 다른 블록이 데이터 소스에서 읽을 수 있습니다.

  2. 대상 블록 – 다른 블록에서 쓸 수 있는 데이터 수신기 또는 싱크입니다.

  3. 전파자 블록 – 소스 블록과 대상 블록 모두로 작동합니다.


다양한 블록을 가져와 연결하여 선형 처리 파이프라인이나 더 복잡한 처리 그래프를 형성합니다. 다음 그림을 고려하십시오.



그래프의 각 노드는 서로 다른 처리 또는 계산 작업을 나타냅니다.



데이터 흐름 라이브러리는 버퍼링, 실행, 그룹화라는 세 가지 범주로 분류되는 사전 정의된 여러 블록 유형을 제공합니다. 우리는 이 블로그 게시물을 위해 개발된 프로젝트에 버퍼링 및 실행 유형을 사용하고 있습니다. BufferBlock<T>은 데이터를 버퍼링하는 범용 구조이며 생산자/소비자 애플리케이션에 사용하기에 적합합니다. BufferBlock은 들어오는 데이터를 처리하기 위해 선입 선출 대기열을 사용합니다.


BufferBlock(및 이를 확장하는 클래스)은 메시지를 직접 쓰고 읽을 수 있는 데이터 흐름 라이브러리의 유일한 블록 유형입니다. 다른 유형은 블록에서 메시지를 받거나 블록으로 메시지를 보낼 것으로 예상합니다. 이러한 이유로 우리는 소스 블록을 생성하고 ISourceBlock 인터페이스와 ITargetBlock 인터페이스를 구현하는 싱크 블록을 구현할 때 BufferBlock 대리자로 사용했습니다.


우리 애플리케이션에 사용되는 다른 Dataflow 블록 유형은 TransformBlock <TInput, TOutput> 입니다. 데이터 흐름 라이브러리에 있는 대부분의 블록 유형과 마찬가지로 변환 블록이 수신하는 각 입력 레코드에 대해 실행하는 대리자 역할을 하는 Func<TInput, TOutput> 제공하여 TransformBlock의 인스턴스를 만듭니다.


Dataflow 블록의 두 가지 필수 기능은 버퍼링할 레코드 수와 병렬 처리 수준을 제어할 수 있다는 것입니다.


최대 버퍼 용량을 설정하면 애플리케이션이 처리 파이프라인의 특정 지점에서 장기간 대기하는 경우 자동으로 역압을 적용합니다. 이 배압은 데이터의 과잉 축적을 방지하기 위해 필요합니다. 그런 다음 문제가 가라앉고 버퍼 크기가 줄어들면 데이터가 다시 소비됩니다.


블록의 동시성을 설정하는 기능은 성능에 매우 중요합니다. 하나의 블록이 CPU 또는 I/O 집약적인 작업을 수행하는 경우 처리량을 높이기 위해 작업을 병렬화하는 자연스러운 경향이 있습니다. 그러나 동시성을 추가하면 처리 순서에 문제가 발생할 수 있습니다. 블록의 작업에 스레딩을 추가하면 데이터의 출력 순서를 보장할 수 없습니다. 어떤 경우에는 순서가 중요하지 않지만 중요할 경우에는 동시성을 통한 더 높은 처리량과 처리 순서 출력을 고려하는 것이 심각한 균형을 이룹니다. 다행히 Dataflow 라이브러리를 사용하면 이러한 절충안을 만들 필요가 없습니다.


블록의 병렬 처리를 2개 이상으로 설정하면 프레임워크는 입력 레코드의 원래 순서를 유지하도록 보장합니다(병렬 처리를 통한 순서 유지는 구성 가능하며 기본값은 true입니다). 데이터의 원래 순서가 A, B, C인 경우 출력 순서는 A, B, C가 됩니다. 회의적입니까? 나는 그랬다는 것을 알고 테스트를 했고 그것이 광고한 대로 작동한다는 것을 발견했습니다. 이 게시물의 뒷부분에서 이 테스트에 대해 이야기하겠습니다. 병렬성을 높이는 것은 상태 비저장 작업이나 연관 및 교환 가능한 상태 저장 작업에서만 수행되어야 합니다. 즉, 작업 순서나 그룹화를 변경해도 결과에 영향을 미치지 않습니다.


이 시점에서 이것이 어디로 가는지 알 수 있습니다. 가능한 가장 빠른 방법으로 처리해야 하는 이벤트를 나타내는 Kafka 주제가 있습니다. 따라서 .NET KafkaConsumer가 포함된 소스 블록, 비즈니스 논리를 수행하기 위한 처리 블록, 최종 결과를 Kafka 주제에 다시 쓰기 위한 .NET KafkaProducer가 포함된 싱크 블록으로 구성된 스트리밍 애플리케이션을 구축할 것입니다. 다음은 애플리케이션의 상위 수준 보기에 대한 그림입니다.




애플리케이션의 구조는 다음과 같습니다.


  1. 소스 블록: .NET KafkaConsumer 및 BufferBlock 대리자 래핑
  2. 변환 블록: 역직렬화
  3. 변환 블록: 수신 JSON 데이터를 구매 개체에 매핑
  4. 변환 블록: CPU 집약적 작업(시뮬레이션)
  5. 변환 블록: 직렬화
  6. 대상 블록: .NET KafkaProducer 및 BufferBlock 대리자 래핑


다음은 강력한 이벤트 스트리밍 애플리케이션을 구축하기 위해 Kafka 및 데이터 흐름 라이브러리를 활용하는 데 대한 애플리케이션의 전체 흐름과 몇 가지 중요한 사항에 대한 설명입니다.


이벤트 스트리밍 애플리케이션

시나리오는 다음과 같습니다. 온라인 상점에서 구매 기록을 수신하는 Kafka 주제가 있고 수신 데이터 형식은 JSON입니다. 구매 세부정보에 ML 추론을 적용하여 이러한 구매 이벤트를 처리하려고 합니다. 또한 JSON 레코드를 회사 전체 데이터 형식인 Protobuf 형식으로 변환하려고 합니다. 물론 애플리케이션의 처리량은 필수적입니다. ML 작업은 CPU 집약적이므로 애플리케이션 처리량을 최대화하는 방법이 필요하므로 애플리케이션의 해당 부분을 병렬화하는 이점을 활용하게 됩니다.


파이프라인에서 데이터 사용

소스 블록부터 시작하여 스트리밍 애플리케이션의 중요한 사항을 살펴보겠습니다. 앞서 ISourceBlock 인터페이스 구현에 대해 언급한 바 있으며, BufferBlockISourceBlock 구현하므로 모든 인터페이스 메서드를 충족하기 위해 이를 대리자로 사용하겠습니다. 따라서 소스 블록 구현은 KafkaConsumer와 BufferBlock을 래핑합니다. 소스 블록 내에는 소비자가 소비한 레코드를 버퍼에 전달하는 책임을 단독으로 담당하는 별도의 스레드가 있습니다. 거기에서 버퍼는 레코드를 파이프라인의 다음 블록으로 전달합니다.


레코드를 버퍼로 전달하기 전에 ConsumeRecord ( Consumer.consume 호출에 의해 반환됨)는 키와 값 외에도 애플리케이션에 중요한 원래 파티션과 오프셋을 캡처하는 Record 추상화로 래핑됩니다. 그 이유는 곧 설명하겠습니다. 또한 전체 파이프라인이 Record 추상화와 함께 작동한다는 점도 주목할 가치가 있습니다. 따라서 모든 변환으로 인해 키, 값 및 전체 파이프라인을 통해 보존되는 원래 오프셋과 같은 기타 필수 필드를 래핑하는 새로운 Record 개체가 생성됩니다.


처리 블록

애플리케이션은 처리를 여러 다른 블록으로 나눕니다. 각 블록은 처리 체인의 다음 단계에 연결되므로 소스 블록은 역직렬화를 처리하는 첫 번째 블록에 연결됩니다. .NET KafkaConsumer는 레코드의 역직렬화를 처리할 수 있지만 소비자는 직렬화된 페이로드를 전달하고 Transform 블록에서 역직렬화합니다. 역직렬화는 CPU 집약적일 수 있으므로 이를 처리 블록에 넣으면 필요한 경우 작업을 병렬화할 수 있습니다.


역직렬화 후에 레코드는 JSON 페이로드를 Protobuf 형식의 구매 데이터 모델 개체로 변환하는 다른 Transform 블록으로 흐릅니다. 더 흥미로운 부분은 데이터가 다음 블록으로 들어갈 때 발생하며, 이는 구매 거래를 완전히 완료하는 데 필요한 CPU 집약적 작업을 나타냅니다. 애플리케이션은 이 부분을 시뮬레이션하고 제공된 함수는 1~3초 사이의 임의 시간으로 절전 모드로 전환됩니다.


이 시뮬레이션된 처리 블록에서는 Dataflow 블록 프레임워크의 기능을 활용합니다. Dataflow 블록을 인스턴스화할 때 발생하는 각 레코드에 적용되는 위임 Func 인스턴스와 ExecutionDataflowBlockOptions 인스턴스를 제공합니다. 이전에 Dataflow 블록 구성을 언급했지만 여기서 다시 빠르게 검토하겠습니다. ExecutionDataflowBlockOptions 에는 해당 블록의 최대 버퍼 크기와 최대 병렬화 수준이라는 두 가지 필수 속성이 포함되어 있습니다.


파이프라인의 모든 블록에 대한 버퍼 크기 구성을 10,000개 레코드로 설정하는 동안 시뮬레이션된 CPU 집약적 수준을 제외하고는 기본 병렬화 수준 1을 고수합니다. 여기서는 4로 설정합니다. 기본 Dataflow 버퍼 크기는 다음과 같습니다. 제한 없는. 다음 섹션에서 성능에 미치는 영향에 대해 논의하겠지만 지금은 애플리케이션 개요를 완료하겠습니다.


집중 처리 블록은 싱크 블록을 제공하는 직렬화 변환 블록으로 전달되고, 이 블록은 .NET KafkaProducer를 래핑하고 최종 결과를 Kafka 항목으로 생성합니다. 싱크 블록은 또한 생성을 위해 대리자 BufferBlock 과 별도의 스레드를 사용합니다. 스레드는 버퍼에서 사용 가능한 다음 레코드를 검색합니다. 그런 다음 DeliveryReport 를 래핑하는 Action 대리자를 전달하는 KafkaProducer.Produce 메서드를 호출합니다. 생성 요청이 완료되면 생산자 I/O 스레드가 Action 대리자를 실행합니다.


이로써 애플리케이션에 대한 대략적인 연습이 완료되었습니다. 이제 설정의 중요한 부분인 오프셋 커밋을 처리하는 방법에 대해 논의해 보겠습니다. 이는 소비자로부터 레코드를 파이프라인하는 경우 매우 중요합니다.


오프셋 커밋

Kafka로 데이터를 처리할 때 애플리케이션이 특정 지점까지 성공적으로 처리한 레코드의 오프셋(오프셋은 Kafka 주제에 있는 레코드의 논리적 위치)을 주기적으로 커밋합니다. 그렇다면 왜 오프셋을 커밋합니까? 대답하기 쉬운 질문입니다. 소비자가 제어된 방식으로 또는 오류로 인해 종료되면 마지막으로 알려진 커밋된 오프셋에서 처리를 재개합니다. 주기적으로 오프셋을 커밋함으로써 소비자는 레코드를 다시 처리하지 않거나 몇 개의 레코드를 처리한 커밋하기 전에 애플리케이션이 종료되는 경우 최소한의 레코드를 다시 처리하지 않습니다. 이 접근 방식을 최소 한 번 처리라고 합니다. 이는 레코드가 최소 한 번 처리되도록 보장하고, 오류가 발생한 경우 일부가 재처리될 수도 있지만 데이터 손실 위험이 있는 경우에는 훌륭한 옵션입니다. Kafka는 또한 정확히 1회 처리 보장을 제공하며, 이 블로그 게시물에서는 트랜잭션에 대해 다루지 않지만 Kafka 트랜잭션에 대한 자세한 내용은 다음에서 읽을 수 있습니다. 이 블로그 게시물 .


오프셋을 커밋하는 방법에는 여러 가지가 있지만 가장 간단하고 기본적인 방법은 자동 커밋 방식입니다. 소비자는 레코드를 읽고 애플리케이션은 이를 처리합니다. 구성 가능한 시간(레코드 타임스탬프 기준)이 지나면 소비자는 이미 소비된 레코드의 오프셋을 커밋합니다. 일반적으로 자동 커밋이 합리적인 접근 방식입니다. 일반적인 소비 프로세스 루프에서는 이전에 소비한 모든 레코드를 성공적으로 처리할 때까지 소비자에게 반환되지 않습니다. 예기치 않은 오류나 종료가 발생한 경우 코드는 소비자에게 반환되지 않으므로 커밋이 발생하지 않습니다. 그러나 여기 애플리케이션에서는 파이프라인을 수행합니다. 소비된 레코드를 가져와서 버퍼에 푸시하고 반환하여 더 많은 레코드를 소비하므로 성공적인 처리를 기다리지 않습니다.


파이프라이닝 접근 방식을 사용하면 최소 한 번 처리를 어떻게 보장할 수 있나요? 단일 매개변수( TopicPartitionOffset )를 작업하고 다음 커밋을 위해 이를 (다른 오프셋과 함께) 저장하는 IConsumer.StoreOffset 메서드를 활용하겠습니다 . 오프셋 관리에 대한 이 접근 방식은 자동 커밋이 Java API에서 작동하는 방식과 대조됩니다.


따라서 커밋 절차는 다음과 같이 작동합니다. 즉, 싱크 블록이 Kafka에 생성하기 위해 레코드를 검색할 때 이를 Action 대리자에게도 제공합니다. 생산자가 콜백을 실행할 때 원래 오프셋을 소비자(소스 블록의 동일한 인스턴스)에게 전달하고 소비자는 StoreOffset 메서드를 사용합니다. 여전히 소비자에 대해 자동 커밋이 활성화되어 있지만 소비자가 이 시점까지 사용했던 최신 오프셋을 맹목적으로 커밋하도록 하는 대신 커밋할 오프셋을 제공합니다.



오프셋 커밋


따라서 애플리케이션이 파이프라이닝을 사용하더라도 브로커로부터 확인을 받은 후에만 커밋합니다. 즉, 브로커와 최소 복제본 브로커 집합이 레코드를 저장했음을 의미합니다. 이러한 방식으로 작업하면 블록이 작업을 수행하는 동안 소비자가 지속적으로 파이프라인을 가져오고 공급할 수 있으므로 애플리케이션이 더 빠르게 진행될 수 있습니다. 이 접근 방식은 .NET 소비자 클라이언트가 스레드로부터 안전하므로(일부 메서드는 그렇지 않으며 이와 같이 문서화되어 있음) 단일 소비자가 소스 및 싱크 블록 스레드 모두에서 안전하게 작업하도록 할 수 있기 때문에 가능합니다.


생성 단계 중 오류가 발생하면 애플리케이션은 오류를 기록하고 해당 레코드를 중첩된 BufferBlock 에 다시 넣으므로 생산자는 레코드를 브로커에 다시 보내려고 합니다. 그러나 이 재시도 논리는 맹목적으로 수행되므로 실제로는 보다 강력한 솔루션이 필요할 것입니다.

성능에 미치는 영향

이제 애플리케이션 작동 방식을 다루었으므로 성능 수치를 살펴보겠습니다. 모든 테스트는 macOS Big Sur(11.6) 노트북에서 로컬로 실행되었으므로 이 시나리오에서는 마일리지가 다를 수 있습니다. 성능 테스트 설정은 간단합니다.


  1. JSON 형식으로 Kafka 주제에 대한 100만 개의 레코드를 생성합니다. 이 단계는 미리 수행되었으며 테스트 측정에는 포함되지 않았습니다.

  2. Kafka Dataflow 지원 애플리케이션을 시작하고 모든 블록의 병렬화를 1(기본값)로 설정합니다.

  3. 애플리케이션은 100만 개의 레코드를 성공적으로 처리할 때까지 실행된 다음 종료됩니다.

  4. 모든 기록을 처리하는 데 걸린 시간을 기록합니다.


두 번째 라운드의 유일한 차이점은 시뮬레이션된 CPU 집약적 블록의 MaxDegreeOfParallelism을 4로 설정한 것입니다.

결과는 다음과 같습니다.


레코드 수

동시성 요소

시간(분)

100만

1

38

100만

4

9


그래서 간단히 구성을 설정함으로써 이벤트 순서를 유지하면서 처리량을 크게 향상시켰습니다. 따라서 최대 병렬 처리 수준을 4로 활성화하면 예상되는 속도 향상이 4배 이상 증가합니다. 그러나 이러한 성능 향상의 중요한 부분은 올바르게 수행하기 어려운 동시 코드를 작성하지 않았다는 것입니다.


블로그 게시물 앞부분에서 Dataflow 블록과의 동시성이 이벤트 순서를 유지하는지 확인하는 테스트에 대해 언급했으므로 이제 이에 대해 이야기해 보겠습니다. 재판에는 다음 단계가 포함되었습니다.


  1. Kafka 주제에 1M 정수(0-999,999)를 생성합니다.

  2. 정수 유형을 사용하도록 참조 애플리케이션 수정

  3. 시뮬레이션된 원격 프로세스 블록에 대해 동시성 수준 1로 애플리케이션을 실행하여 Kafka 주제로 생성

  4. 동시성 수준 4로 애플리케이션을 다시 실행하고 다른 Kafka 주제에 숫자를 생성합니다.

  5. 프로그램을 실행하여 두 결과 항목의 정수를 모두 사용하고 이를 메모리 배열에 저장합니다.

  6. 두 어레이를 비교하고 순서가 동일한지 확인합니다.


이 테스트 결과 두 배열 모두 0부터 999,999까지의 정수가 포함되어 있어 병렬 처리 수준이 2 이상인 Dataflow 블록을 사용하면 들어오는 데이터의 처리 순서가 유지된다는 것이 입증되었습니다. Dataflow 병렬 처리에 대한 자세한 내용은 문서 에서 확인할 수 있습니다.

요약

이 게시물 에서는 .NET Kafka 클라이언트와 작업 병렬 라이브러리를 사용하여 강력하고 처리량이 높은 이벤트 스트리밍 애플리케이션을 구축하는 방법을 소개했습니다. Kafka는 고성능 이벤트 스트리밍을 제공하고 작업 병렬 라이브러리는 모든 세부 사항을 처리하는 버퍼링을 사용하여 동시 애플리케이션을 생성하기 위한 구성 요소를 제공하므로 개발자는 비즈니스 논리에 집중할 수 있습니다. 응용 프로그램의 시나리오는 약간 부자연스럽기는 하지만 두 기술을 결합하면 유용하다는 것을 알 수 있기를 바랍니다. 시도 해봐- 여기 GitHub 저장소가 있습니다 .



여기에도 게시되었습니다.