node-rdkafka

repository·master·Indexed 24 days ago

https://github.com/blizzard/node-rdkafka

A high-performance Node.js client for Apache Kafka that acts as a wrapper around the native C/C++ librdkafka library. It provides both Standard and Stream APIs for producers and consumers, supporting features such as transactional producers, rebalance callbacks, and offset commit tracking. Version v3.6.1.

Tokens
16.3K
Snippets
22
Records
74
Agent score
79%

What's inside node-rdkafka

  1. Use the Standard API (Flowing vs Non-flowing modes)

    master

    The Standard API allows manual management of callbacks and events. You can operate in two modes:

    1. Flowing mode: Messages flow continuously via an infinite loop in the event loop. This is triggered by calling consumer.consume() without a callback (or with only a callback).
    2. Non-flowing mode: You manually request messages. This is triggered by calling consumer.consume(number, cb) where number is the amount of messages to fetch.

    Important: consumer.consume() uses background threads. The number of threads is limited by UV_THREADPOOL_SIZE (default 4). If using multiple consumers, increase UV_THREADPOOL_SIZE or use the (number, cb) variant to avoid blocking the application.

    // Flowing mode example
    consumer.connect();
    
    consumer
      .on('ready', () => {
        consumer.subscribe(['librdtesting-01']);
        consumer.consume();
      })
      .on('data', (data) => {
        console.log(data.value.toString());
      });
    
    // Non-flowing mode example
    consumer.connect();
    
    consumer
      .on('ready', () => {
        consumer.subscribe(['librdtesting-01']);
        setInterval(() => {
          consumer.consume(1);
        }, 1000);
      })
      .on('data', (data) => {
        console.log(data.value.toString());
      });
  2. Use the Stream API to consume messages

    master

    The Stream API is the simplest way to consume messages. Use KafkaConsumer.createReadStream(globalConfig, topicConfig, options) to create a readable stream.

    Note: Each call to createReadStream creates a new stream. You can access the underlying consumer via stream.consumer to call methods like .commit().

    // Read from the librdtesting-01 topic... note that this creates a new stream on each call!
    const stream = KafkaConsumer.createReadStream(globalConfig, topicConfig, {
      topics: ['librdtesting-01']
    });
    
    stream.on('data', (message) => {
      console.log('Got message');
      console.log(message.value.toString());
    });
    
    // Accessing the consumer from the stream
    stream.consumer.commit(); // Commits all locally stored offsets
  3. Install node-rdkafka on Windows

    master

    On Windows, node-rdkafka is not compiled from source. Instead, it links against a static binary from librdkafka.redist downloaded via NuGet.

    Requirements:

    Customizing the download source: You can change the NuGet download URL by setting the NODE_RDKAFKA_NUGET_BASE_URL environment variable. The default is https://globalcdn.nuget.org/packages/.

  4. Use the Producer Standard API

    master

    The Standard API is more performant for high-volume message handling but requires manual lifecycle management.

    Key Requirements:

    1. Call .connect() to connect to the broker.
    2. Wait for the ready event before calling .produce().
    3. Crucial: You must call .poll() regularly or set a polling interval using .setPollInterval(interval) to process delivery reports and handle reconnections. If you don't, the internal queue will eventually fill up and stop sending messages.
    const producer = new Kafka.Producer({
      'metadata.broker.list': 'localhost:9092',
      'dr_cb': true
    });
    
    // Connect to the broker manually
    producer.connect();
    
    // Wait for the ready event before proceeding
    producer.on('ready', () => {
      try
        // Topic to send the message to
        'topic',
        // optionally we can manually specify a partition for the message
        // this defaults to -1 - which will use librdkafka's default partitioner (consistent random for keyed messages, random for unkeyed messages)
        null,
        // Message to send. Must be a buffer
        Buffer.from('Awesome message'),
        // for keyed messages, we also specify the key - note that this field is optional
        'Stormwind',
        // you can send a timestamp here. If your broker version supports it, 
        // it will get added. Otherwise, we default to 0
        Date.now(),
        // you can send an opaque token here, which gets passed along
        // to your delivery reports
        ,
      );
      } catch (err) {
        console.error('A problem occurred when sending our message');
        console.error(err);
      }
    });
    
    // Any errors we encounter, including connection errors
    producer.on('event.error', (err) => {
      console.error('Error from producer');
      console.error(err);
    })
    
    // We must either call .poll() manually after sending messages
    // or set the producer to poll on an interval (.setPollInterval).
    // Without this, we do not get delivery events and the queue
    // will eventually fill up.
    producer.setPollInterval(100);
  5. Install node-rdkafka on Alpine Linux via Docker

    master

    When using Alpine Linux in Docker, you must install specific library dependencies because Alpine uses musl instead of glibc. To successfully build and run node-rdkafka, you need to install build tools (g++, make, python3) and development headers for SASL, SSL, and compression.

    It is recommended to use a multi-stage build or a single Dockerfile that installs both the runtime dependencies and the build-time dependencies (like gcc and zlib-dev) to keep the image size optimized.

    FROM node:14-alpine
    
    RUN apk --no-cache add \
          bash \
          g++ \
          ca-certificates \
          lz4-dev \
          musl-dev \
          cyrus-sasl-dev \
          openssl-dev \
          make \
          python3
    
    RUN apk add --no-cache --virtual .build-deps gcc zlib-dev libc-dev bsd-compat-headers py-setuptools bash
    
    # Create app directory
    RUN mkdir -p /usr/local/app
    
    # Move to the app directory
    WORKDIR /usr/local/app
    
    # Install node-rdkafka
    RUN npm install node-rdkafka
    # Copy package.json first to check if an npm install is needed
  6. Run tests for node-rdkafka

    master

    The project uses a Makefile to run two types of tests. Before running tests, ensure submodules are initialized:

    git submodule init
    git submodule update

    Test Commands:

    • Unit Tests: Run make lint or make test.
    • End-to-End (E2E) Integration Tests: Run make e2e.

    E2E Requirements:

    • A running Kafka installation is required.
    • By default, tests connect to localhost:9092. You can override this by setting the KAFKA_HOST environment variable.
  7. Use the Producer Stream API

    master

    The Producer can be used as a Node.js writable stream. This is convenient for piping data into Kafka. Use Kafka.Producer.createWriteStream to create a stream for a specific topic.

    Important: You must listen to the error event on the stream. If you don't, errors will bubble up as uncaught exceptions and potentially crash your process. Note that stream.write() returning false only indicates that the internal queue is full; it does not guarantee the message reached Kafka.

    // Our producer with its Kafka brokers
    // This call returns a new writable stream to our topic 'topic-name'
    const stream = Kafka.Producer.createWriteStream({
      'metadata.broker.list': 'kafka-host1:9092,kafka-host2:9092'
    }, {}, {
      topic: 'topic-name'
    });
    
    // Writes a message to the stream
    const queuedSuccess = stream.write(Buffer.from('Awesome message'));
    
    if (queuedSuccess) {
      console.log('We queued our message!');
    } else {
      // Note that this only tells us if the stream's queue is full,
      // it does NOT tell us if the message got to Kafka!  See below...
      console.log('Too many messages in our queue already');
    }
    
    // NOTE: MAKE SURE TO LISTEN TO THIS IF YOU WANT THE STREAM TO BE DURABLE
    // Otherwise, any error will bubble up as an uncaught exception.
    stream.on('error', (err) => {
      // Here's where we'll know if something went wrong sending to Kafka
      console.error('Error in our kafka stream');
      console.error(err);
    })
  8. Configure OpenSSL for Mac OS High Sierra / Mojave

    master

    On Mac OS High Sierra and Mojave, Homebrew does not overwrite default system libraries. When building node-rdkafka, you must explicitly tell the linker where to find OpenSSL by setting CPPFLAGS and LDFLAGS before running npm install.

    export CPPFLAGS=-I/usr/local/opt/openssl/include
    export LDFLAGS=-L/usr/local/opt/openssl/lib
    
    npm install
  9. Configure node-rdkafka with librdkafka options

    master

    The library allows you to pass many configuration options directly to the underlying librdkafka engine.

    Important Notes:

    • A full list of available configuration keys can be found in the librdkafka Configuration documentation.
    • Configuration keys ending in _cb are designated as callbacks (e.g., dr_cb, event_cb).
    • The library will throw an error if an invalid value is provided for a configuration key.

    Supported Callbacks:

    • partitioner_cb
    • dr_cb or dr_msg_cb
    • event_cb
    • rebalance_cb
    • offset_commit_cb
  10. Use the Producer class to send messages

    master

    The Producer class is the primary entry point for writing data to Kafka. It requires a global configuration object and an optional default topic configuration.

    Lifecycle Note: After instantiating a Producer, you must connect to it (via the Client base class methods) to fetch metadata and ensure the connection is valid before producing messages.

    To ensure messages are actually delivered, you should either configure acks settings or use delivery reports by listening to the delivery-report event.

  11. Consume Kafka messages using a Readable Stream

    master

    You can consume Kafka messages as a Node.js Readable stream. This is useful for piping Kafka data into other systems and ensures backpressure is respected (you only read as fast as you write).

    Note: You should generally not instantiate KafkaConsumerStream directly. Instead, use the KafkaConsumer.createReadStream() method provided by the KafkaConsumer class.

    Key Characteristics

    • Object Mode: By default, the stream operates in objectMode, emitting {Consumer~Message} objects. If objectMode: false is explicitly set, it will stream the message value (buffers).
    • Automatic Connection: The stream detects if the consumer is already connected. If not, it will connect and begin reading once ready.
    • Backpressure: Unlike continuous subscribe callbacks, the stream implementation ensures you read only as fast as you can process the data.
    • Performance Trade-off: The stream implementation is slower than using the continuous subscribe callback. If performance is more critical than backpressure, use the callback method instead.