شکستن سد مقیاس: 1 میلیون پیام با NodeJS و Kafka

آپاچی کافکا یک پلتفرم پخش رویداد توزیع شده است که می تواند به عنوان یک کارگزار Pub-Sub در میکروسرویس های NodeJS استفاده شود. اخیراً در پروژهای کار کردم که در آن ناشر باید به 1 میلیون کاربر پیام بفرستد و پاسخ را پس دهد. در اینجا یک کار جالب می تواند توسط Message Queue انجام شود و ما می توانیم 1 میلیون پیام را در یک صف ارسال کنیم و پیام ها را یکی یکی برای کاربران ارسال می کند. اما، یک pub-sub میتواند برای همه موارد مصرفکننده که مشترک یک موضوع خاص هستند، پخش کند.
جادوی صف های پیام در مقابل Pub-Sub در کافکا
هنگامی که با چنین مقیاس عظیمی از پیامرسانی مواجه میشویم، اغلب دو رویکرد به ذهن متبادر میشوند:
-
صف پیام: ارسال 1 میلیون پیام به یک صف، جایی که هر پیام توسط مصرف کنندگان پردازش می شود.
-
Pub-Sub: یک پیام واحد را برای همه موارد مصرف کنندگان مشترک در یک موضوع خاص پخش کنید.
برای مورد استفاده ما، انعطاف پذیری کافکا در مدیریت هر دو الگو، آن را به انتخاب ایده آل تبدیل کرده است. بیایید به نحوه پیادهسازی سیستم میکروسرویس تولیدکننده-مصرف کننده با کافکا بپردازیم.
کد دمو را بررسی کنید: https://github.com/fahadfahim13/producer-consumer-kafka.git
معماری
میکروسرویس تولید کننده:
بیایید یک میکروسرویس تولیدکننده و یک میکروسرویس مصرف کننده ایجاد کنیم.
کد در producer/index.js
به این صورت خواهد بود:
const { Kafka } = require('kafkajs');
const kafka = new Kafka({
clientId: 'my-producer',
brokers: [process.env.KAFKA_BROKER],
});
const producer = kafka.producer();
(async () => {
await producer.connect();
for (let i = 0; i < 1000000; i++) {
await producer.send({
topic: process.env.TOPIC_NAME,
messages: [{ value: `Message ${i}` }],
});
console.log(`Sent message ${i}`);
}
await producer.disconnect();
})();
در اینجا، میتوانیم ببینیم که تهیهکننده در حال تولید 1 میلیون پیام و ارسال به یک موضوع کافکا است.
ریز سرویس مصرف کننده
کد در میکروسرویس مصرف کننده index.js
فایل به این صورت خواهد بود:
const { Kafka } = require('kafkajs');
const kafka = new Kafka({
clientId: 'my-consumer',
brokers: [process.env.KAFKA_BROKER],
});
const consumer = kafka.consumer({ groupId: 'test-group' });
(async () => {
await consumer.connect();
await consumer.subscribe({ topic: process.env.TOPIC_NAME, fromBeginning: true });
await consumer.run({
eachMessage: async ({ topic, partition, message }) => {
console.log(`Received message: ${message.value} in topic: ${topic} at partition: ${partition}`);
},
});
})();
مصرف کنندگان در یک موضوع کافکا مشترک می شوند و پیام ها را به صورت ناهمزمان دریافت می کنند و آن پیام ها را پردازش می کنند.
فایل Docker-compose
فایل docker-compose.yml شبیه این است که خدمات باغ وحش، کافکا و تولید کننده و مصرف کننده را در کانتینرهای مختلف اجرا می کند.
version: '3.8'
services:
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- "22181:2181"
networks:
- kafka-network
volumes:
- kafka_data:/var/lib/kafka/data
kafka:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
ports:
- "29092:29092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
networks:
- kafka-network
producer:
build:
context: ./producer
dockerfile: Dockerfile
container_name: kafka-producer
depends_on:
- kafka
networks:
- kafka-network
volumes:
- ./producer:/app
environment:
KAFKA_BOOTSTRAP_SERVERS: kafka:9092
KAFKA_BROKER: kafka:9092
TOPIC_NAME: my-topic
consumer:
build:
context: ./consumer
dockerfile: Dockerfile
container_name: kafka-consumer
depends_on:
- kafka
networks:
- kafka-network
volumes:
- ./consumer:/app
environment:
KAFKA_BOOTSTRAP_SERVERS: kafka:9092
KAFKA_BROKER: kafka:9092
TOPIC_NAME: my-topic
networks:
kafka-network:
driver: bridge
volumes:
kafka_data:
بهینه سازی عملکرد کافکا با پارتیشن ها
برای مدیریت توان عملیاتی بالا، افزایش تعداد پارتیشنها در موضوع کافکا ضروری است. پارتیشنهای بیشتر امکان پردازش موازی را فراهم میکند و تولیدکنندگان و مصرفکنندگان را قادر میسازد تا به صورت افقی مقیاس شوند. به عنوان مثال:
kafka-topics --create \
--zookeeper localhost:2181 \
--replication-factor 1 \
--partitions 10 \
--topic my-topic
هر پارتیشن به عنوان یک صف جداگانه عمل می کند و بار را بین مصرف کنندگان توزیع می کند.
کد دمو را بررسی کنید: https://github.com/fahadfahim13/producer-consumer-kafka.git