Skip to content

فصل ۲ — آشنایی با Kafka

آنچه در این فصل می‌آید

  • معماری سطح بالای Kafka
  • درک گزینه‌های client
  • نحوهٔ ارتباط برنامه‌ها با broker
  • تولید و مصرف اولین پیام
  • استفاده از clientهای Kafka با برنامهٔ Java

اکنون که نمای سطح بالای جایی که Kafka می‌درخشد و چرا باید از آن استفاده کرد را داریم، به اجزای Kafka که کل سیستم را می‌سازند می‌پردازیم. Apache Kafka در ذات distributed system است، اما نصب و اجرای آن روی یک host هم ممکن است. این نقطهٔ شروعی برای ورود به use caseهای نمونهٔ ماست. همان‌طور که اغلب پیش می‌آید، سؤالات واقعی وقتی شروع می‌کنید که دست‌ها به keyboard برسد. تا پایان این فصل می‌توانید اولین پیام Kafka را از command line بفرستید و بگیرید. با Kafka شروع می‌کنیم و کمی بیشتر جزئیات معماری Kafka را بررسی می‌کنیم.

توجه اگر cluster Kafka ندارید یا می‌خواهید یکی را locally روی ماشین‌تان راه بیندازید، به پیوست A مراجعه کنید. پیوست A روی به‌روزرسانی config پیش‌فرض Apache Kafka و راه‌اندازی سه broker که در مثال‌ها استفاده می‌کنیم کار می‌کند. قبل از هر مثالی در این کتاب، مطمئن شوید instanceها up و running هستند! اگر مثالی کار نکرد، source code در GitHub را برای tip، errata و پیشنهاد بررسی کنید.

۲.۱ تولید و مصرف یک پیام

پیام، که record هم نامیده می‌شود، قطعهٔ پایهٔ داده‌ای است که در Kafka جریان دارد. پیام‌ها نمایش دادهٔ شما در Kafka هستند. هر پیام timestamp، value و key اختیاری دارد. در صورت نیاز می‌توان از header سفارشی هم استفاده کرد [۱]. مثال سادهٔ پیام می‌تواند این باشد: ماشین با host ID «1234567» (message key) با پیام «Alert: Machine Failed» (message value) در «2020-10-02T10:34:11.654Z» (message timestamp) fail شد. فصل ۹ مثال استفاده از header سفارشی برای جفت key-value در use case tracing را نشان می‌دهد.

شکل ۲-۱. پیام‌های Kafka از key و value تشکیل شده‌اند (timestamp و headerهای اختیاری نشان داده نشده‌اند)

Kafka record — Key: با key.serializer و key.deserializer تعامل دارد — Value: با value.serializer و value.deserializer تعامل دارد — محتوای داده شما اینجا می‌رود — Key الزامی نیست

اکنون که record داریم، چگونه Kafka از آن باخبر شود؟ این پیام را با فرستادن به چیزی که broker نامیده می‌شود تحویل می‌دهید.

۲.۲ brokerها چیستند؟

brokerها را می‌توان سمت سرور Kafka دانست [۱]. قبل از virtual machine و Kubernetes®، شاید یک سرور فیزیکی یک broker را host می‌کرد. چون تقریباً همهٔ clusterها بیش از یک سرور (یا node) دارند، برای بیشتر مثال‌ها سه سرور Kafka اجرا می‌کنیم. این setup تست local باید خروجی دستورات روی بیش از یک broker را نشان دهد، مشابه اجرا با چند broker روی ماشین‌های مختلف.

برای اولین مثال، یک topic می‌سازیم و اولین پیام را از command line به Kafka می‌فرستیم. نکته این است که Kafka با command line ساخته شده. GUI استفاده نمی‌کنیم، پس باید راهی برای تعامل با command line interface سیستم‌عامل داشته باشیم. دستورات در prompt متنی وارد می‌شوند. vi، Emacs، Nano یا هر چیز دیگری — مطمئن شوید برای ویرایش راحتید.

توجه اگرچه Kafka روی بسیاری OSها قابل استفاده است، اغلب در production روی Linux deploy می‌شود و مهارت command line هنگام استفاده از این محصول مفید است.

Shell helper

اگر کاربر command line هستید و shortcut برای autocomplete دستورات (و کمک با argumentهای موجود) می‌خواهید، پروژهٔ Kafka autocomplete را در http://mng.bz/K48O ببینید. اگر کاربر Zsh هستید، plugin Zsh-completion Kafka را از https://github.com/Dabz/kafka-zsh-completions هم نصب کنید.

برای فرستادن اولین پیام، جایی برای فرستادن لازم است. برای ساخت topic، دستور kafka-topics.sh را در shell با گزینهٔ --create اجرا می‌کنیم (فهرست ۲.۱). این script در directory نصب Kafka است؛ مسیر ممکن است شبیه ~/kafka_2.13-2.7.1/bin باشد. کاربران Windows می‌توانند فایل‌های .bat با همان نام script shell استفاده کنند. مثلاً kafka-topics.sh معادل Windows آن kafka-topics.bat است که در <kafka_install_directory>/bin/windows قرار دارد.

توجه ارجاعات kinaction و ka (مثل kaProperties) در این کتاب مخفف‌های Kafka in Action هستند و به محصول یا شرکت خاصی مربوط نیستند.

فهرست ۲.۱ — ساخت topic kinaction_helloworld

bash
bin/kafka-topics.sh --create --bootstrap-server localhost:9094 \
  --topic kinaction_helloworld --partitions 3 --replication-factor 3

باید خروجی روی console ببینید: Created topic kinaction_helloworld. در فهرست ۲.۱ نام kinaction_helloworld برای topic استفاده شده. هر نامی می‌توانست باشد، اما گزینهٔ محبوب پیروی از قراردادهای نام‌گذاری Unix/Linux، از جمله بدون space، است. با حذف space و کاراکترهای خاص می‌توان از خطاها و warningهای زیاد جلوگیری کرد که با CLI و autocompletion همیشه سازگار نیستند.

چند گزینهٔ دیگر هست که معنایشان شاید هنوز روشن نباشد، اما برای ادامهٔ exploration سریع تعریف می‌کنیم. این topicها در فصل ۶ با جزئیات بیشتر پوشش داده می‌شوند.

گزینهٔ --partitions تعیین می‌کند topic به چند بخش split شود. مثلاً چون سه broker داریم، سه partition یعنی یک partition per broker. برای workload تست شاید بر اساس نیاز داده به این همه نیاز نباشد، اما ساخت بیش از یک partition در این مرحله نشان می‌دهد سیستم چگونه داده را روی partitionها پخش می‌کند. --replication-factor هم در این مثال ۳ است. اساساً می‌گوید برای هر partition سه replica می‌خواهیم. این کپی‌ها بخش حیاتی طراحی برای reliability و fault tolerance هستند. --bootstrap-server به broker local Kafka اشاره می‌کند؛ پس broker باید قبل از invoke کردن script در حال اجرا باشد. فعلاً مهم‌ترین هدف تصویر layout است. نحوهٔ تخمین اعداد لازم در use caseهای دیگر را بعداً در جزئیات broker بررسی می‌کنیم.

می‌توانیم همهٔ topicهای موجود را ببینیم و مطمئن شویم topic جدید در لیست است. گزینهٔ --list برای این خروجی است.

فهرست ۲.۲ — تأیید topic

bash
bin/kafka-topics.sh --list --bootstrap-server localhost:9094

برای حس بهتری از شکل topic جدید، فهرست ۲.۳ دستور دیگری نشان می‌دهد که بینش بیشتری از cluster می‌دهد. topic ما مثل topic واحد سنتی در سیستم‌های پیام‌رسانی دیگر نیست: replica و partition داریم. اعداد کنار Leader، Replicas و Isr همان broker.idهای سه broker در فایل‌های config هستند. در خروجی topic سه partition دارد: Partition 0، 1 و 2. هر partition سه‌بار replicate شده همان‌طور که در ساخت topic intended بود.

فهرست ۲.۳ — describe کردن topic kinaction_helloworld

bash
bin/kafka-topics.sh --bootstrap-server localhost:9094 \
  --describe --topic kinaction_helloworld
Topic:kinaction_helloworld PartitionCount:3 ReplicationFactor:3 Configs:
Topic: kinaction_helloworld Partition: 0 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
Topic: kinaction_helloworld Partition: 1 Leader: 1 Replicas: 1,2,0 Isr: 1,2,0
Topic: kinaction_helloworld Partition: 2 Leader: 2 Replicas: 2,0,1 Isr: 2,0,1

خروجی فهرست ۲.۳ در خط اول تعداد کل partition و replica را نشان می‌دهد. خطوط بعدی هر partition را نشان می‌دهند. خط دوم مربوط به partition با برچسب ۰ است و به همین ترتیب. روی partition 0 zoom کنیم: replica leader روی broker 0 است. replica روی broker 1 و 2 هم دارد. ستون آخر Isr یعنی in-sync replicas — brokerهایی که current هستند و از leader عقب نیستند. داشتن replica partition که outdated یا از leader عقب است موضوعی است که بعداً پوشش می‌دهیم. با این حال، سلامت replica در سیستم توزیع‌شده چیزی است که باید مراقب آن باشیم.

شکل ۲-۲. نمای یک broker

Broker 0 فقط برای partition 0 می‌خواند و می‌نویسد — بقیهٔ replicaها کپی را از brokerهای دیگر می‌گیرند — topic kinaction_helloworld از leader هر partition تشکیل شده؛ در این مورد هر broker یک partition leader نگه می‌دارد

برای topic kinaction_helloworld، broker 0 leader replica partition 0 را نگه می‌دارد. همچنین replica partition 1 و 2 را دارد که leader آن‌ها نیست. برای کپی partition 1، داده از broker 1 کپی می‌شود.

توجه وقتی در تصویر به partition leader اشاره می‌کنیم، منظور replica leader است. partition می‌تواند یک یا چند replica داشته باشد، اما فقط یک replica leader است. نقش leader به‌روزرسانی توسط clientهای خارجی است؛ non-leaderها فقط از leader خود به‌روز می‌شوند.

وقتی topic ساخته و تأیید شد، می‌توانیم پیام واقعی بفرستیم! کسانی که قبلاً با Kafka کار کرده‌اند ممکن است بپرسند چرا قبل از ارسال پیام topic ساختیم. configی برای enable/disable autocreation topic هست، اما معمولاً بهتر است ساخت topic action مشخص باشد؛ نمی‌خواهیم topic جدید به‌خاطر typo در نام topic یا retry producer تصادفی ظاهر شود.

برای ارسال پیام، tab یا پنجرهٔ terminal برای اجرای producer به‌عنوان console application که ورودی کاربر می‌گیرد باز می‌کنیم [۲]. دستور فهرست ۲.۴ برنامهٔ interactiveای راه می‌اندازد که shell را می‌گیرد؛ تا Ctrl-C برای quit prompt برنمی‌گردد. تایپ کنید — شاید ساده‌ترین print statement برنامه‌نویس با prefix kinaction (برای Kafka in Action)، در راستای مثال «hello, world» در کتاب The C Programming Language [۳].

فهرست ۲.۴ — دستور console producer Kafka

bash
bin/kafka-console-producer.sh --bootstrap-server localhost:9094 \
  --topic kinaction_helloworld

در فهرست ۲.۴ topic را با پارامتر bootstrap-server مشخص می‌کنیم. این پارامتر می‌تواند یک (یا لیست) از brokerهای فعلی cluster باشد. با این اطلاعات، cluster metadata لازم برای کار با topic را می‌گیرد.

اکنون tab یا پنجرهٔ terminal جدید برای consumer که به‌عنوان console application اجرا می‌شود [۲]. دستور فهرست ۲.۵ برنامه‌ای راه می‌اندازد که shell را می‌گیرد. در این طرف باید پیامی که در producer console نوشتید را ببینید. همان پارامتر topic را در هر دو دستور استفاده کنید؛ وگرنه چیزی نمی‌بینید.

فهرست ۲.۵ — دستور consumer Kafka

bash
bin/kafka-console-consumer.sh --bootstrap-server localhost:9094 \
  --topic kinaction_helloworld --from-beginning

فهرست ۲.۶ — نمونه خروجی consumer برای kinaction_helloworld

kinaction_helloworld
...

با ارسال پیام بیشتر و تأیید تحویل به consumer application، می‌توانید process را terminate کنید و هنگام restart گزینهٔ --from-beginning را حذف کنید. توجه کنید همهٔ پیام‌های قبلی را ندیدیم — فقط پیام‌هایی که از شروع consumer console تولید شده‌اند. دانستن کدام پیام‌ها بعد خوانده شوند و توانایی consume از offset مشخص ابزارهایی است که در فصل ۵ هنگام بحث consumer استفاده می‌کنیم. اکنون که مثال ساده را دیدیم، پس‌زمینهٔ بیشتری برای بحث قطعات استفاده‌شده داریم.

۲.۳ گشت در Kafka

جدول ۲.۱ اجزای اصلی و نقششان در معماری Kafka را نشان می‌دهد. در بخش‌های بعد هر کدام را بیشتر بررسی می‌کنیم تا foundation محکمی برای فصل‌های بعد داشته باشیم.

جدول ۲.۱ — معماری Kafka

ComponentRole
Producerارسال پیام به Kafka
Consumerدریافت پیام از Kafka
Topicsنام منطقی محل ذخیرهٔ پیام در broker
ZooKeeper ensembleکمک به حفظ consensus در cluster
Brokerمدیریت commit log (نحوهٔ ذخیرهٔ پیام روی disk)

۲.۳.۱ Producerها و consumerها

اولین توقف در گشت ما: producerها و consumerها.

شکل ۲-۳. Producer در برابر consumer

Producer: منبع داده یا پیام‌هایی که به Kafka فرستاده می‌شوند — Consumer: داده‌ای که توسط consumerها یا sinkها از Kafka کشیده می‌شود — ما هم از topicهای kinaction produce و consume می‌کنیم

Producer ابزاری برای فرستادن پیام به topicهای Kafka است [۱]. همان‌طور که در use caseهای فصل ۱ گفتیم، مثال خوب فایل log تولیدشده از یک برنامه است. آن فایل‌ها تا وقتی جمع و به Kafka فرستاده نشوند بخشی از سیستم Kafka نیستند. وقتی به input (یا data) ورودی به Kafka فکر می‌کنید، producer جایی در workflow درگیر است.

producer پیش‌فرض به معنای خاص وجود ندارد، اما APIهایی که با Kafka تعامل دارند در implementation خود از producer استفاده می‌کنند. مسیرهای ورود ممکن است ابزار جدا مثل Flume یا APIهای دیگر Kafka مثل Connect و Streams باشد. WorkerSourceTask در source code Apache Kafka Connect (از نسخهٔ ۱.۰) نمونه‌ای است که producer داخلاً در implementation استفاده می‌شود. API سطح بالاتر خود را می‌دهد. این کد نسخهٔ ۱.۰ تحت Apache 2 license (https://github.com/apache/kafka/blob/trunk/LICENSE) در GitHub قابل مشاهده است (http://mng.bz/9N4r). producer برای فرستادن پیام داخل خود Kafka هم استفاده می‌شود. مثلاً اگر از topic خاصی می‌خوانیم و می‌خواهیم به topic دیگر بفرستیم، باز هم producer استفاده می‌کنیم.

برای حس producer خودمان، کد مشابه مفهومی WorkerSourceTask — کلاس Java که قبلاً گفتیم — مفید است. فهرست ۲.۷ کد نمونه را نشان می‌دهد. همهٔ source code برای main method نیست، اما logic ارسال پیام با KafkaProducer استاندارد نشان داده شده. لازم نیست هر بخش را بفهمید؛ فقط با usage producer آشنا شوید.

فهرست ۲.۷ — producer در حال ارسال پیام

java
Alert alert = new Alert(1, "Stage 1", "CRITICAL", "Stage 1 stopped");
ProducerRecord<Alert, String> producerRecord =
  new ProducerRecord<Alert, String>
 ("kinaction_alert", alert, alert.getAlertMessage());

producer.send(producerRecord,
  new AlertCallback());
producer.close();

برای ارسال داده به Kafka، ProducerRecord ساختیم. این object پیام را تعریف و topic (اینجا kinaction_alert) را مشخص می‌کند. object سفارشی Alert را به‌عنوان key در پیام استفاده کردیم. سپس متد send را برای ارسال ProducerRecord فراخوانی کردیم. می‌توانیم منتظر پیام بمانیم یا از callback برای ارسال asynchronous با مدیریت خطا استفاده کنیم. فصل ۴ کل این مثال را با جزئیات می‌دهد.

شکل ۲-۴. مثال producer برای event کاربر

  1. User-generated event — 2. Send — Audit click event — Producer client — Kafka — 3. On completion, asynchronous — User، Client/program application، Kafka

در contrast با producer، consumer ابزاری برای دریافت پیام از Kafka است [۱]. همان‌طور که با producerها، وقتی از گرفتن داده از Kafka صحبت می‌کنیم consumer درگیر است. WorkerSinkTask کلاس دیگری در source code Connect نسخهٔ ۱.۰ است که consumer موازی با مثال producer Connect نشان می‌دهد (http://mng.bz/WrRW). برنامه‌های consuming به topicهای مورد علاقه subscribe می‌کنند و م continuously poll می‌کنند. WorkerSinkTask مثال واقعی است که consumer برای گرفتن record از topicها استفاده می‌شود. فهرست ۲.۸ consumerای است که در فصل ۵ می‌سازیم؛ مفاهیم مشابه WorkerSinkTask.java.

فهرست ۲.۸ — مصرف پیام

java
...
consumer.subscribe(List.of("kinaction_audit"));

while (keepConsuming) {
  var records = consumer.
    poll(Duration.ofMillis(250));
  for (ConsumerRecord<String, String> record : records) {
    log.info("kinaction_info offset = {}, kinaction_value = {}",
              record.offset(), record.value());

      OffsetAndMetadata offsetMeta =
        new OffsetAndMetadata(++record.offset(), "");

      Map<TopicPartition, OffsetAndMetadata> kaOffsetMap = new HashMap<>();
      kaOffsetMap.put(new TopicPartition("kinaction_audit",
        record.partition()), offsetMeta);

      consumer.commitSync(kaOffsetMap);
  }
}
...

فهرست ۲.۸ نشان می‌دهد consumer متد subscribe را با لیست topicها (اینجا kinaction_audit) فراخوانی می‌کند. سپس topic(ها) را poll می‌کند (شکل ۲-۵) و دادهٔ برگشتی را به‌عنوان ConsumerRecords مدیریت می‌کند.

شکل ۲-۵. جریان مثال consumer

  1. Subscribe — Consumer client reading kinaction_audit — 2. Poll — Kafka — Polling تا زمانی که consumer client اجرا می‌شود ادامه دارد — 3. Data برای استفادهٔ برنامه (مثلاً dashboard) در دسترس است

فهرست‌های ۲.۷ و ۲.۸ دو بخش use case مشخص در شکل‌های ۲-۴ و ۲-۵ را نشان می‌دهند. فرض کنید شرکت می‌خواهد بداند چند کاربر روی action جدید factory در صفحهٔ web کلیک کرده‌اند. click eventهای تولیدشده توسط کاربران دادهٔ ورودی به ecosystem Kafka هستند. consumerهای داده factory خود هستند که با برنامه‌هایشان داده را معنا می‌دهند.

گذاشتن داده در Kafka و بیرون آوردن آن با کدی مثل قبلی (یا حتی با Kafka Connect) به کاربران اجازه می‌دهد با داده‌ای کار کنند که روی requirement و goal کسب‌وکار تأثیر می‌گذارد. Kafka روی پردازش داده برای برنامه‌ها تمرکز ندارد؛ برنامه‌های consuming جایی است که داده واقعاً value کسب‌وکار می‌دهد. اکنون که می‌دانیم چگونه داده را وارد و خارج Kafka کنیم، محل فرود در cluster را بررسی می‌کنیم.

۲.۳.۲ نمای کلی topicها

topicها جایی است که بیشتر کاربران منطق «پیام کجا برود» را فکر می‌کنند. topic از واحدهایی به نام partition تشکیل شده [۱]. یک یا چند partition یک topic واحد را می‌سازند. آنچه واقعاً روی disk پیاده می‌شود، partition است که Kafka بیشتر با آن کار می‌کند.

توجه یک replica partition واحد فقط روی یک broker وجود دارد و نمی‌تواند بین brokerها split شود.

شکل ۲-۶. partitionها topic را می‌سازند

topic kinaction_helloworld از سه partition تشکیل شده که احتمالاً بین brokerهای مختلف پخش می‌شوند — برای reliability سه کپی داده می‌خواهید؛ topic خود یک entity (یا فایل واحد) کپی‌شده نیست، بلکه partitionهای مختلف سه‌بار replicate می‌شوند — partition به segment file روی disk هم تقسیم می‌شود (جزئیات در فصل‌های broker) — یکی از replicaهای partition leader نامیده می‌شود

یکی از مهم‌ترین مفاهیم در این نقطه این است که یکی از کپی‌های partition (replica) leader نامیده می‌شود. مثلاً topic با سه partition و سه کپی از هر partition — هر partition یک leader replica منتخب دارد. leader یکی از کپی‌های partition است و دو دیگر (در این مورد) follower هستند که از leader replica partition خود به‌روز می‌شوند [۱]. producerها و consumerها فقط از leader replica هر partition که assign شده در سناریوی happy path (بدون exception یا failure) می‌خوانند یا می‌نویسند. اما producer یا consumer چگونه می‌داند کدام replica leader است؟ در computing توزیع‌شده و failureهای random، پاسخ اغلب با کمک ZooKeeper — توقف بعدی گشت ما — تحت تأثیر است.

۲.۳.۳ استفاده از ZooKeeper

یکی از قدیمی‌ترین منابع پیچیدگی اضافهٔ ترسیده در ecosystem Kafka شاید استفاده از ZooKeeper باشد. Apache ZooKeeper (http://zookeeper.apache.org/) store توزیع‌شده‌ای است که discovery، configuration و synchronization را به‌شکل highly available فراهم می‌کند. در نسخه‌های Kafka از ۰.۹، تغییراتی در ZooKeeper داده شد که consumer گزینه داشت اطلاعات میزان consume (offset) را ذخیره نکند. offsetها را در فصل‌های بعد پوشش می‌دهیم. این کاهش usage نیاز consensus و coordination در سیستم‌های توزیع‌شده را حذف نکرد.

حذف ZooKeeper

برای ساده‌سازی نیازهای اجرای Kafka، پیشنهادی برای جایگزینی ZooKeeper با quorum managed خود Kafka بود [۴]. چون این کار در زمان انتشار کامل نشده بود (early access 2.8.0)، ZooKeeper هنوز در این کتاب بحث می‌شود. این کتاب نسخهٔ 2.7.1 را پوشش می‌دهد و احتمالاً نسخه‌های قدیمی‌تر در production هنوز ZooKeeper دارند تا تغییرات کامل شود. ZooKeeper با Kafka Raft Metadata mode (KRaft) جایگزین می‌شود، اما مفاهیم نیاز به coordination در سیستم توزیع‌شده همچنان معتبر است و نقش فعلی ZooKeeper foundation آن درک را می‌گذارد. Kafka fault tolerance و resilience می‌دهد، اما چیزی باید coordination فراهم کند و ZooKeeper آن قطعه را enable می‌کند. internals ZooKeeper را عمیق پوشش نمی‌دهیم، اما استفادهٔ Kafka از آن در فصل‌های بعد لمس می‌شود.

cluster Kafka ما بیش از یک broker (سرور) دارد. برای عملکرد به‌عنوان یک برنامهٔ درست، brokerها باید با هم communicate کنند و به agreement برسند. توافق روی اینکه کدام replica leader یک partition است نمونهٔ عملی ZooKeeper در ecosystem Kafka است. برای مقایسهٔ دنیای واقعی، بیشتر ما ساعت‌های out-of-sync و غیرممکن شدن تشخیص زمان درست وقتی چند ساعت زمان متفاوت نشان می‌دهند را دیده‌ایم. agreement بین brokerهای جدا challenging است. چیزی لازم است Kafka را coordinated نگه دارد در success و failure.

نکته اگر با znode آشنا هستید یا تجربهٔ ZooKeeper دارید، ZkUtils.scala در source code Kafka نقطهٔ خوبی برای شروع interactions است.

برای هر use case production، ZooKeeper ensemble خواهد بود، اما در setup local فقط یک سرور اجرا می‌کنیم [۵].

شکل ۲-۷. تعامل ZooKeeper

ZooKeeper ensemble با سه instance — در setup local فقط یک process ZooKeeper — تعامل Kafka با brokerها است، نه clientها — در client versionهای اخیر، تعامل واقعی با ZooKeeper فقط با message broker است؛ clientها دیگر offset در ZooKeeper ذخیره نمی‌کنند

دانستن fundamentals مفاهیم بالا توانایی ساخت application عملی با Kafka را افزایش می‌دهد. همچنین می‌بینیم سیستم‌های موجود که Kafka استفاده می‌کنند چگونه برای use caseهای واقعی تعامل می‌کنند.

۲.۳.۴ معماری سطح بالای Kafka

به‌طور کلی، core Kafka را می‌توان processهای application Scala روی Java virtual machine (JVM) دانست. اگرچه برای مدیریت میلیون‌ها پیام سریع شناخته شده، چه چیزی در طراحی Kafka این را ممکن می‌سازد؟ یکی از کلیدها استفاده از page cache سیستم‌عامل است (شکل ۲-۸). با اجتناب از cache در JVM heap، brokerها از برخی مشکلات heap بزرگ (مثلاً pauseهای طولانی یا م frequent garbage collection) جلوگیری می‌کنند [۶].

ملاحظهٔ طراحی دیگر الگوی دسترسی داده است. وقتی پیام‌های جدید flood می‌کنند، احتمالاً latest messageها برای بسیاری از consumerها جالب‌ترند و از cache سرو می‌شوند. سرو از page cache به‌جای disk در بیشتر موارد سریع‌تر است. در استثناها، RAM بیشتر کمک می‌کند workload بیشتری در page cache بیفتد.

همان‌طور که گفتیم، Kafka پروتکل خود را استفاده می‌کند [۷]. استفاده از پروتکل موجود مثل AMQP توسط خالقان Kafka به‌عنوان نقش بیش از حد بزرگ در impacts پیاده‌سازی واقعی یاد شد. مثلاً fieldهای جدید به message header برای exactly-once semantics انتشار 0.11 اضافه شد. همان انتشار message format را برای فشرده‌سازی مؤثرتر rework کرد. پروتکل می‌تواند تغییر کند و مخصوص نیازهای خالقان Kafka باشد.

شکل ۲-۸. page cache سیستم‌عامل

بدون cache در JVM heap — Kafka به page cache تکیه می‌کند نه heap — Stored in RAM، Transparent cache from disk — Hard drive — Kafka application process

تقریباً به پایان گشت رسیدیم — یک توقف دیگر: brokerها و commit log.

۲.۳.۵ commit log

یکی از مفاهیم core برای تسلط بر foundation Kafka درک commit log است. مفهوم ساده اما قدرتمند است. logی که منظور ماست همان log use case مربوط به aggregate خروجی loggerهای برنامه مثل LOGGER.error در Java نیست.

شکل ۲-۹. commit log

مثال افزودن دو پیام (۷ و ۸) به topic مثل kinaction_alert — پیام‌ها دریافت و اضافه می‌شوند — با هر پیام جدید به انتهای log اضافه می‌شود

همان‌طور که پیام‌ها در طول زمان اضافه می‌شوند مکانیک بیشتری هم هست (مثلاً وقتی log file باید از broker failure برگردد)، اما این مفهوم پایه برای درک Kafka حیاتی است. log در Kafka فقط detail پنهان در سیستم‌های مشابه (مثل write-ahead log پایگاه داده) نیست؛ front and center است و کاربران از offset برای دانستن موقعیت در log استفاده می‌کنند.

commit log به‌خاطر ماهیت append-only خاص است — eventها همیشه به انتها اضافه می‌شوند. persistence به‌عنوان log برای storage بخش major است که Kafka را از message brokerهای دیگر جدا می‌کند. خواندن پیام آن را از سیستم حذف نمی‌کند یا از consumerهای دیگر exclude نمی‌کند.

سؤال رایج: چقدر می‌توان داده را در Kafka retain کرد؟ امروز در شرکت‌های مختلف، بعد از رسیدن داده در commit log Kafka به اندازه یا دورهٔ retention قابل config، اغلب به store دائمی منتقل می‌شود. اما به فضای disk و workflow پردازش بستگی دارد. New York Times یک partition واحد با کمتر از ۱۰۰ GB دارد [۹]. Kafka برای حفظ performance سریع حتی با نگه‌داشتن پیام‌ها ساخته شده. جزئیات retention هنگام بحث broker در فصل ۶ پوشش داده می‌شود. فعلاً بدانید retention log با propertyهای config بر اساس age یا size کنترل می‌شود.

۲.۴ بسته‌های source code مختلف و کارکردشان

Kafka اغلب در عنوان APIهای مختلف ذکر می‌شود. برخی componentها standalone product توصیف می‌شوند. برخی را بررسی می‌کنیم تا گزینه‌ها را ببینیم. بسته‌های بخش‌های بعد APIهایی در همان repository source code core Kafka هستند، به‌جز ksqlDB [۱۰].

۲.۴.۱ Kafka Streams

Kafka Streams نسبت به core Kafka توجه زیادی گرفته. این API در directory streams پروژهٔ source code Kafka است و بیشتر Java نوشته شده. sweet spot Kafka Streams این است که cluster پردازش جدا لازم نیست. library سبک برای استفاده در برنامه است. برای workload نیازی به cluster یا resource management software مثل Apache Hadoop نیست. با این حال ویژگی‌های قدرتمند دارد: local state با fault tolerance، پردازش one-at-a-time پیام و پشتیبانی exactly-once [۱۰]. در طول کتاب foundation نحوهٔ استفادهٔ Kafka Streams API از core موجود Kafka برای کار هیجان‌انگیز را می‌فهمید.

این API برای آسان کردن ساخت streaming application و API fluent مشابه Stream API Java 8 (DSL) ساخته شد. Kafka Streams روی قطعات core Kafka کار می‌کند و stateful processing و distributed join و غیره را بدون complexity یا overhead زیاد اضافه می‌کند [۱۰].

طراحی microservice هم تحت تأثیر این API است. به‌جای isolate شدن داده در برنامه‌های مختلف، به برنامه‌هایی کشیده می‌شود که مستقل از داده استفاده می‌کنند.

شکل ۲-۱۰. طراحی microservice

بالا: microserviceها مستقیم با هم — پایین: microserviceها با Kafka — kinaction_ServiceA تا D — Kafka — با Kafka Streams داده share می‌شود در حالی که پردازش مستقل است

بالای شکل ۲-۱۰ (بدون Kafka) هر برنامه مستقیماً با چند interface با دیگران صحبت می‌کند. پایین رویکرد Kafka را نشان می‌دهد. Kafka نه‌تنها داده را بدون service munging برای همه expose می‌کند، interface واحد برای consume همهٔ برنامه‌ها می‌دهد. نبود وابستگی مستقیم به هر برنامه نشان می‌دهد Kafka چگونه dependency بین برنامه‌های خاص را شل می‌کند.

۲.۴.۲ Kafka Connect

Kafka Connect در folder core Kafka Connect است و بیشتر Java. این framework برای آسان‌تر کردن integration با سیستم‌های دیگر ساخته شد [۱۰]. به‌طور زیادی می‌تواند جایگزین ابزارهایی مثل Apache Gobblin™ و Apache Flume شود. اگر با Flume آشنا هستید، اصطلاحات آشنا به نظر می‌رسند.

source connectorها داده را از منبع به Kafka import می‌کنند. مثلاً برای انتقال داده از جدول MySQL® به topicهای Kafka از Connect source استفاده می‌کنیم. sink connectorها داده را از Kafka به سیستم‌های دیگر export می‌کنند. مثلاً برای نگه‌داری بلندمدت پیام topic از sink connector استفاده می‌کنیم.

شکل ۲-۱۱. use case Connect

source connector به‌روزرسانی جدول را به topic Kafka stream می‌کند — MySQL — Connect با producer و consumer داده را جابه‌جا می‌کند — sink connector به cloud storage — داده alert trend فصل ۳ دلیل خوبی برای این flow است

جایگزین مستقیم همهٔ featureهای Flume احتمالاً هدف یا goal اصلی Kafka Connect نیست. Connect agent per Kafka node setup ندارد و برای integrate خوب با stream-processing framework برای copy داده طراحی شده. Kafka Connect انتخاب عالی برای pipeline داده سریع و ساده بین سیستم‌های رایج است.

۲.۴.۳ بسته AdminClient

Kafka اخیراً AdminClient API را معرفی کرد. قبل از آن، scriptها و برنامه‌هایی که action اداری می‌خواستند باید shell script (که Kafka می‌دهد) اجرا می‌کردند یا classهای internal که shell scriptها استفاده می‌کردند. این API بخشی از kafka-clients.jar است — JAR متفاوت از APIهای قبلی. interface مشابه config producer و consumer دارد. source code در package org/apache/kafka/clients/admin است [۱۰].

۲.۴.۴ ksqlDB

اواخر ۲۰۱۷، Confluent preview SQL engine برای Kafka به نام KSQL (بعد ksqlDB) منتشر کرد. توسعه‌دهندگان و data analystهایی که بیشتر SQL برای تحلیل داده استفاده می‌کردند می‌توانستند stream را با interface آشنا leverage کنند. syntax شاید somewhat آشنا باشد، اما تفاوت‌های significant هست.

بیشتر queryهایی که کاربران relational database می‌شناسند on-demand یا one-time با lookup هستند. shift ذهنی به continuous query روی data stream significant است. مثل Kafka Streams API، ksqlDB استفاده از قدرت continuous data flow را آسان‌تر می‌کند. interface برای data engineer grammar SQL-like آشناست، اما queryها continuously running و updating هستند — use caseهایی مثل dashboard outage به‌جای SELECT point-in-time.

۲.۵ clientهای Confluent

به‌خاطر محبوبیت Kafka، انتخاب زبان برای تعامل با Kafka معمولاً مشکل نیست. در تمرین‌ها و مثال‌ها از clientهای Java پروژهٔ core Kafka استفاده می‌کنیم. Confluent clientهای زیاد دیگری هم پشتیبانی می‌کند [۱۳].

چون همهٔ clientها از نظر feature یکسان نیستند، Confluent matrix featureهای پشتیبانی‌شده به زبان برنامه‌نویسی دارد: https://docs.confluent.io/current/clients/index.html. clientهای open source دیگر برای توسعه client خود یا یادگیری زبان جدید مفیدند.

client محتمل‌ترین راه تعامل با Kafka در برنامه‌هاست. client Java (فهرست ۲.۹) را می‌بینیم — همان produce-and-consume که با command line کردیم.

فهرست ۲.۹ — producer client Java

java
public class HelloWorldProducer {
  public static void main(String[] args) {
    Properties kaProperties = new Properties();
    kaProperties.put("bootstrap.servers",
      "localhost:9092,localhost:9093,localhost:9094");
    kaProperties.put("key.serializer",
      "org.apache.kafka.common.serialization.StringSerializer");
    kaProperties.put("value.serializer",
      "org.apache.kafka.common.serialization.StringSerializer");

    try (Producer<String, String> producer =
      new KafkaProducer<>(kaProperties)) {

        ProducerRecord<String, String> producerRecord =
          new ProducerRecord<>("kinaction_helloworld",
            null, "hello world again");

        producer.send(producerRecord);
    }
  }
}

کد فهرست ۲.۹ producer ساده است. اولین گام ساخت producer تنظیم propertyهای config است — به‌شکل map.

bootstrap.servers یک config ضروری است. لیست brokerهای Kafka است؛ لازم نیست هر سرور باشد — بعد از connect، client اطلاعات بقیهٔ brokerهای cluster را پیدا می‌کند.

key.serializer و value.serializer هم مهم‌اند. classی برای serialize داده هنگام ورود به Kafka لازم است. key و value serializer یکسان لازم نیستند.

شکل ۲-۱۲. جریان producer

Producer record به topic kinaction_helloworld — JVM — Thread-safe Kafka producer — Key: Null، Value: hello world again — Kafka — Partition 0، 1، 2 — در این مثال به partition 1 assign شده

پیامی که به‌عنوان آخرین argument می‌فرستیم با اولین پیام console producer متفاوت است. چرا پیام متفاوت؟ با همان topic کار می‌کنیم و consumer جدید داریم — باید پیام قدیمی را هم بگیریم. وقتی پیام آماده است، asynchronously با producer می‌فرستیم. چون فقط یک پیام می‌فرستیم، producer را close می‌کنیم که منتظر complete شدن requestهای قبلی بماند و gracefully shutdown شود.

قبل از اجرای مثال‌های Java client، entry زیر در pom.xml لازم است [۱۴]. در همهٔ مثال‌های کتاب Apache Maven™ استفاده می‌کنیم.

فهرست ۲.۱۰ — entry POM client Java

xml
<dependency>
  <groupId>org.apache.kafka</groupId>
  <artifactId>kafka-clients</artifactId>
  <version>2.7.1</version>
</dependency>

فهرست ۲.۱۱ — consumer client Java

java
public class HelloWorldConsumer {

  final static Logger log =
    LoggerFactory.getLogger(HelloWorldConsumer.class);

  private volatile boolean keepConsuming = true;

  public static void main(String[] args) {
    Properties kaProperties = new Properties();
    kaProperties.put("bootstrap.servers",
      "localhost:9092,localhost:9093,localhost:9094");
    kaProperties.put("group.id", "kinaction_helloconsumer");
    kaProperties.put("enable.auto.commit", "true");
    kaProperties.put("auto.commit.interval.ms", "1000");
    kaProperties.put("key.deserializer",
      "org.apache.kafka.common.serialization.StringDeserializer");
    kaProperties.put("value.deserializer",
      "org.apache.kafka.common.serialization.StringDeserializer");

      HelloWorldConsumer helloWorldConsumer = new HelloWorldConsumer();
      helloWorldConsumer.consume(kaProperties);
      Runtime.getRuntime().
        addShutdownHook(new Thread(helloWorldConsumer::shutdown));
  }

  private void consume(Properties kaProperties) {
    try (KafkaConsumer<String, String> consumer =
      new KafkaConsumer<>(kaProperties)) {
      consumer.subscribe(
         List.of(
           "kinaction_helloworld"
         )
      );

          while (keepConsuming) {
            ConsumerRecords<String, String> records =
              consumer.poll(Duration.ofMillis(250));
            for (ConsumerRecord<String, String> record :
              records) {
              log.info("kinaction_info offset = {}, kinaction_value = {}",
                record.offset(), record.value());
            }
          }
      }

  private void shutdown() {
    keepConsuming = false;
  }
}

یکی از چیزهای برجسته infinite loop عمدی در فهرست ۲.۱۱ است — برای handle کردن infinite stream داده. consumer مشابه producer map property می‌گیرد، اما برخلاف producer، Java consumer client thread-safe نیست [۱۵]. هنگام scale past one consumer باید در نظر بگیریم. کد ما مسئول synchronized بودن access است؛ گزینهٔ ساده یک consumer per Java thread است. producer را گفتیم کجا پیام بفرستد؛ consumer به topicهای مورد علاقه subscribe می‌کند. subscribe می‌تواند بیش از یک topic را یک‌جا subscribe کند.

مهم‌ترین بخش poll است — actively پیام را به برنامه می‌آورد. zero، one یا many message می‌تواند با یک poll برگردد؛ logic باید بیش از یک نتیجه per poll را در نظر بگیرد.

در پایان می‌توانیم Ctrl-C consumer را بزنیم. این مثال‌ها به propertyهای config زیادی که پیش‌فرض enable هستند تکیه دارند. در فصل‌های بعد بیشتر کاوش می‌کنیم.

۲.۶ پردازش stream و اصطلاحات

نظریهٔ distributed systems یا تعاریف با معانی مختلف را challenge نمی‌کنیم؛ می‌بینیم Kafka چگونه کار می‌کند. وقتی Kafka را به کار می‌برید، اصطلاحات زیر را می‌بینید — امیدواریم توضیحات زیر lens برای mindset پردازش باشد.

شکل ۲-۱۳. نمای کلی Kafka

Producer clients (Database، IoT، web events، Logs) — Data in to partition — Kafka core، OS page cache، Flushed to disk — Topic، Partition 0 با offset 0-4 — Replay — Consumer clients (HDFS، S3، web apps، metrics، analytics) — Data out from partition — Log append-only — ZooKeeper ensemble — Message brokers cluster — یک broker controller است

شکل ۲-۱۳ نمای سطح بالای Kafka را می‌دهد. Kafka قطعات متحرک زیادی دارد که به دادهٔ ورودی و خروجی core وابسته‌اند. Producer داده را می‌فرستد؛ Kafka distributed system برای reliability و scale با log به‌عنوان پایهٔ storage. consumerها به کاربران کمک می‌کنند داده را در برنامه‌ها و use caseهای دیگر استفاده کنند. brokerها cluster را می‌سازند و با ZooKeeper cluster برای metadata coordinate می‌کنند. چون Kafka روی disk ذخیره می‌کند، replay در صورت failure برنامه بخشی از feature set است.

۲.۶.۱ پردازش stream

پردازش stream تعاریف مختلف در پروژه‌های مختلف دارد. اصل streaming این است که داده مدام می‌آید و پایان ندارد [۱۶]. کد باید همیشه پردازش کند و منتظر request یا time frame برای اجرا نماند. infinite loop در کد hint این جریان constant بدون endpoint تعریف‌شده بود.

این رویکرد batch نمی‌کند و گروهی پردازش نمی‌کند. ایدهٔ run شبانه یا ماهانه بخشی از workflow نیست. مثل waterfall بی‌پایان — گاهی حجم زیاد، گاهی کم، اما continuously بین مقصدها جریان دارد.

شکل ۲-۱۴. پردازش stream

Event messages — Kafka — Kafka Streams — End user view (www.) — داده current نشان داده می‌شود، نه query از داده static — در فصل ۵ view به‌روز برای latest alert کمک می‌کند

Kafka Streams API به core Kafka وابسته است. وقتی event messages به cluster می‌آیند، consumer application اطلاعات به‌روز را continuously به کاربر نهایی می‌دهد به‌جای انتظار برای query snapshot static. دیگر refresh صفحه بعد از ۵ دقیقه لازم نیست!

۲.۶.۲ exactly-once دقیقاً چه معنایی دارد

یکی از هیجان‌انگیزترین و شاید پربحث‌ترین featureهای Kafka exactly-once semantics است. نظریه پشت viewها را بحث نمی‌کنیم؛ معنای everyday usage برای Kafka را لمس می‌کنیم.

ساده‌ترین راه حفظ exactly-once ماندن درون دیوارهای Kafka (و topicها) است. سیستم بسته که به‌عنوان transaction complete شود دلیل است که Streams API یکی از آسان‌ترین مسیرها به exactly-once است. connectorهای Kafka Connect مختلف هم exactly-once پشتیبانی می‌کنند — مثال‌های عالی برای بیرون آوردن داده از Kafka چون همیشه endpoint نهایی همهٔ داده در هر scenario نیست.

خلاصه

  • پیام‌ها دادهٔ شما در Kafka را نمایندگی می‌کنند. cluster brokerهای Kafka این داده را handle می‌کند و با سیستم‌ها و clientهای خارجی تعامل دارد.
  • استفادهٔ Kafka از commit log در درک کلی سیستم کمک می‌کند.
  • پیام‌های append شده به انتهای log چارچوب ذخیره و استفادهٔ مجدد داده را مشخص می‌کند. شروع از ابتدای log به برنامه‌ها اجازه reprocess با ترتیب مشخص برای use caseهای مختلف را می‌دهد.
  • Producerها clientهایی هستند که داده را به ecosystem Kafka می‌برند. populate کردن اطلاعات موجود از منابع دیگر مثل پایگاه داده به Kafka دادهٔ once siloed را expose می‌کند.
  • Consumer clientها پیام را از Kafka می‌گیرند. consumerهای زیاد می‌توانند هم‌زمان همان داده را بخوانند. شروع خواندن از موقعیت‌های مختلف انعطاف consumption از topicهای Kafka را نشان می‌دهد.
  • جریان continuously داده بین مقصدها با Kafka به redesign سیستم‌هایی که به batch یا workflow با تأخیر زمانی محدود بودند کمک می‌کند.

منابع

  1. «Main Concepts and Terminology.» Apache Software Foundation (n.d.). https://kafka.apache.org/documentation.html#intro_concepts_and_terms (accessed May 22, 2019).
  2. «Apache Kafka Quickstart.» Apache Software Foundation (2017). https://kafka.apache.org/quickstart (accessed July 15, 2020).
  3. B. Kernighan and D. Ritchie. The C Programming Language, 1st ed. Englewood Cliffs, NJ, USA: Prentice Hall, 1978.
  4. KIP-500: «Replace ZooKeeper with a Self-Managed Metadata Quorum.» Wiki for Apache Kafka. Apache Software Foundation (July 09, 2020). https://cwiki.apache.org/confluence/display/KAFKA/KIP-500%3A+Replace+ZooKeeper+with+a+Self-Managed+Metadata+Quorum (accessed August 22, 2020).
  5. «ZooKeeper Administrator's Guide.» Apache Software Foundation. (n.d.). https://zookeeper.apache.org/doc/r3.4.5/zookeeperAdmin.html (accessed June 10, 2020).
  6. «Kafka Design: Persistence.» Confluent documentation (n.d.). https://docs.confluent.io/platform/current/kafka/design.html#persistence (accessed November 19, 2020).
  7. «A Guide To The Kafka Protocol: Some Common Philosophical Questions.» Wiki for Apache Kafka. Apache Software Foundation (n.d.). https://cwiki.apache.org/confluence/display/KAFKA/A+Guide+To+The+Kafka+Protocol (accessed August 21, 2019).
  8. «Documentation: Topics and Logs.» Apache Software Foundation (n.d.). https://kafka.apache.org/23/documentation.html#intro_topics (accessed May 25, 2020).
  9. B. Svingen. «Publishing with Apache Kafka at The New York Times.» Confluent blog (September 6, 2017). https://www.confluent.io/blog/publishing-apache-kafka-new-york-times/ (accessed September 25, 2018).
  10. «Documentation: Kafka APIs.» Apache Software Foundation (n.d.). https://kafka.apache.org/documentation.html#intro_apis (accessed June 15, 2021).
  11. «Microservices Explained by Confluent.» Confluent. Web presentation (August 23, 2017). https://youtu.be/aWI7iU36qv0 (accessed August 9, 2021).
  12. R. Moffatt. «The Simplest Useful Kafka Connect Data Pipeline in the World…or Thereabouts – Part 1.» Confluent blog (August 11, 2017). https://www.confluent.io/blog/simplest-useful-kafka-connect-data-pipeline-world-thereabouts-part-1/ (accessed December 17, 2017).
  13. «Kafka Clients.» Confluent documentation (n.d.). https://docs.confluent.io/current/clients/index.html (accessed June 15, 2020).
  14. «Kafka Java Client.» Confluent documentation (n.d.). https://docs.confluent.io/clients-kafka-java/current/overview.html (accessed June 21, 2021).
  15. «Class KafkaConsumer<K,V>.» Apache Software Foundation (November 09, 2019). https://kafka.apache.org/24/javadoc/org/apache/kafka/clients/consumer/KafkaConsumer.html (accessed November 20, 2019).
  16. «Streams Concepts.» Confluent documentation (n.d.). https://docs.confluent.io/platform/current/streams/concepts.html (accessed June 17, 2020).

بخش ۲ — اعمال Kafka

در بخش ۲، روی mental model Kafka از بخش ۱ build می‌کنیم و دانش را به action می‌گذاریم. foundation Kafka را می‌بینیم و با موضوعات بنیادین producer و consumer client شروع می‌کنیم. حتی اگر فقط برنامهٔ Kafka Streams یا ksqlDB plan دارید، بخش ۲ ارزش وقت دارد. قطعات core این بخش زیربیشتر کتابخانه‌ها و abstractionهای سطح بالاتر ecosystem Kafka خواهند بود:

  • در فصل ۳ پروژهٔ نمونه طراحی می‌کنیم و یاد می‌گیریم Kafka را روی آن اعمال کنیم. schemaها در فصل ۱۱ با جزئیات بیشتر پوشش داده می‌شوند، اما requirement پروژه این نیاز را زود نشان می‌دهد.
  • در فصل ۴ جزئیات استفاده از producer برای انتقال داده به Kafka، config optionهای مهم و تأثیرشان روی منابع داده.
  • در فصل ۵ مصرف داده از Kafka با consumer — parallels و تفاوت‌ها با producer فصل ۴.
  • در فصل ۶ نقش broker در cluster — leader، controller و رابطه با clientها.
  • در فصل ۷ topic و partition — compacted topic هم معرفی می‌شود.
  • در فصل ۸ ابزار و معماری برای داده‌ای که باید retain یا reprocess شود.
  • در فصل ۹ که بخش ۲ را تمام می‌کند، log و metric ضروری برای admin و سلامت cluster.

پس از بخش ۲ باید درک محکمی از قطعات core Kafka و نحوهٔ استفاده در use caseها داشته باشید. شروع کنیم!