pulsar-rs

repository·master·Indexed 19 days ago

https://github.com/streamnative/pulsar-rs

A pure Rust, future-based client for Apache Pulsar providing an async/await API compatible with Tokio and async-std runtimes. It supports TLS connections, multi-topic consumers via regex or lists, automatic reconnection with exponential backoff, and various compression algorithms including LZ4, zlib, zstd, and Snappy. The library includes a PulsarBuilder for client initialization, a ConsumerBuilder for subscription management, and an optional Admin API for administrative tasks.

Tokens
16.3K
Snippets
46
Records
67
Agent score
61%

What's inside pulsar-rs

  1. Key features of pulsar-rs

    master

    The pulsar-rs client is a pure Rust implementation for Apache Pulsar with the following capabilities:

    • Connection Types: Supports URL-based connections using pulsar:// and pulsar+ssl:// with DNS lookup.
    • Consumer Types: Supports multi-topic consumers using either a regex or a specific list of topics.
    • Security: Supports TLS connections.
    • Async Runtime: Configurable executor supporting Tokio or async-std.
    • Resilience: Automatic reconnection with exponential backoff.
    • Performance: Supports message batching and various compression algorithms (LZ4, zlib, zstd, or Snappy).
    • Observability: Telemetry integration via the tracing crate.

    Note: Compression algorithms and telemetry can be enabled or disabled via Cargo features.

  2. Set up a local Apache Pulsar backend with Docker

    master

    To run the pulsar-rs examples, you must have a running Apache Pulsar instance. You can spin up a standalone instance using Docker with the following command. This command maps the necessary ports (6650 for Pulsar protocol and 8080 for HTTP) and uses persistent volumes for data and configuration.

    docker run -it \
      -p 6650:6650 \
      -p 8080:8080 \
      --mount source=pulsardata,target=/pulsar/data \
      --mount source=pulsarconf,target=/pulsar/conf \
      apachepulsar/pulsar:2.10.0 \
      bin/pulsar standalone
  3. Implement Schema serialization and deserialization

    master

    To use Pulsar's schema features, your data types must implement the SerializeMessage and DeserializeMessage traits. This allows the client to automatically handle the conversion between your Rust types and the Pulsar wire format.

    • SerializeMessage: Defines how to convert your type into a producer::Message (including setting the payload and optional schema_version).
    • DeserializeMessage: Defines how to convert a Payload back into your Rust type.
    #[derive(Serialize, Deserialize)]
    struct TestData {
        age: i32,
        name: String,
    }
    
    impl SerializeMessage for TestData {
        fn serialize_message(input: Self) -> Result<producer::Message, PulsarError> {
            let payload = serde_json::to_vec(&input)
                .map_err(|e| PulsarError::Custom(e.to_string()))?;
            Ok(producer::Message {
                payload,
                ..Default::default()
            })
        }
    }
    
    impl DeserializeMessage for TestData {
        type Output = Result<TestData, serde_json::Error>;
    
        fn deserialize_message(payload: &Payload) -> Self::Output {
            serde_json::from_slice(&payload.data)
        }
    }
  4. How the Executor abstraction works

    master

    The Executor trait provides a unified interface for task spawning and time-based operations, allowing pulsar-rs to be compatible with both Tokio and async-std runtimes. By using this abstraction, the library can perform asynchronous tasks, spawn blocking operations, and handle intervals or delays without being hard-coded to a specific runtime.

    To use a specific runtime, you must enable the corresponding Cargo features:

    • For Tokio: tokio-runtime (or specific rustls variants like tokio-rustls-runtime-aws-lc-rs or tokio-rustls-runtime-ring).
    • For async-std: async-std-runtime (or specific rustls variants like async-std-rustls-runtime-aws-lc-rs or async-std-rustls-runtime-ring).

    If no runtime feature is enabled, attempting to use the executor will result in an unimplemented! panic.

  5. Configure Producer batching

    master

    You can optimize throughput by enabling message batching in ProducerOptions. The following options are available:

    • batch_size: The maximum number of messages to include in a single batch.
    • batch_byte_size: The maximum size in bytes for a single batch.
    • batch_timeout: The maximum time to wait before sending a partial batch.
    // Example of configuring batching in ProducerOptions
    let options = producer::ProducerOptions {
        batch_size: Some(100),
        batch_byte_size: Some(1024 * 1024),
        batch_timeout: Some(std::time::Duration::from_millis(10)),
        ..Default::default()
    };
  6. How ConsumerBuilder handles multiple topics and regex

    master

    The ConsumerBuilder supports two modes of topic discovery:

    1. Explicit Topics: When using with_topic or with_topics, the builder performs a lookup for each topic. If multiple topics are provided, it creates a MultiTopicConsumer which manages several TopicConsumer instances internally.
    2. Regex Matching: When using with_topic_regex, the builder creates a MultiTopicConsumer that periodically refreshes its list of topics based on the provided with_topic_refresh interval (defaulting to 30 seconds). It uses the with_lookup_namespace setting to scope the regex search.

    Constraints:

    • Consumer ID: You cannot specify a consumer_id if you are connecting to multiple topics or partitioned topics.
    • Readers: You cannot create a Reader using into_reader() if more than one topic partition is identified. Readers are limited to a single topic partition.
  7. How routing policies work in Pulsar producers

    master

    Routing policies determine which partition a message is sent to:

    • RoundRobin: Distributes messages across partitions. If a partition_key is provided, messages with the same key are routed to the same partition to maintain order.
    • Single: Sends all messages to the same partition.
    • Custom: Allows implementing the CustomRoutingPolicy trait to define your own logic based on the message and the number of available producers.
  8. How to encode and decode Pulsar messages with Codec

    master

    The Codec struct provides implementations for both tokio_util::codec and asynchronous_codec to handle the serialization and deserialization of Message objects. It manages the Pulsar wire format, including command frames, payload frames, and CRC32c checksum validation.

    Supported Runtimes:

    • Tokio: via tokio_util::codec::Encoder and Decoder (requires tokio-runtime or related TLS features).
    • Async-std: via asynchronous_codec::Encoder and Decoder (requires async-std-runtime or related TLS features).

    Checksumming: The decoder automatically validates the payload using the CRC_CASTAGNOLI (CRC32c) checksum. If the checksum in the payload frame does not match the computed checksum of the metadata and data, a ConnectionError::Decoding error is returned.

    // Example usage with a codec (conceptual, as actual usage depends on the runtime integration)
    // let mut codec = Codec;
    // codec.decode(&mut buffer)?;
    // codec.encode(message, &mut destination_buffer)?;
  9. The proto module and BaseCommand

    master

    The proto module contains the generated Rust code from Pulsar's protobuf definitions. It exposes BaseCommand and Metadata (aliased from MessageMetadata).

    • BaseCommand: The core protobuf message representing any Pulsar command (e.g., Connect, Subscribe, Send, Ack, Producer).
    • Metadata: The metadata associated with a message payload.
    • client_version(): A helper function that returns the current client version string in the format pulsar-rs-v<VERSION>.
    pub mod proto {
        // ... includes pulsar.proto.rs
        pub fn client_version() -> String {
            format!("{}-v{}", "pulsar-rs", env!("CARGO_PKG_VERSION"))
        }
    }
  10. Configure a Pulsar Consumer with ConsumerConfig

    master

    The ConsumerConfig struct defines the complete configuration for a Pulsar consumer. While many fields are internal to the crate, they define the behavior of how a consumer subscribes to a topic and handles messages. Key configuration aspects include:

    • Subscription: The name of the subscription.
    • Subscription Type: The SubType (e.g., Shared, Exclusive, Failover, KeyShared). The default is Shared.
    • Batch Size: The maximum size for batched messages (default is 1000).
    • Consumer Identity: Optional consumer_name (String) and consumer_id (u64).
    • Redelivery Delay: unacked_message_redelivery_delay (Duration) specifies the time after which unacknowledged messages will be sent again.
    • Dead Letter Policy: An optional DeadLetterPolicy to handle messages that fail processing multiple times.
    • Consumer Options: Additional settings via ConsumerOptions.
  11. Serialize and Deserialize Pulsar messages

    master

    The client uses two helper traits to handle data conversion between application types and Pulsar payloads:

    SerializeMessage

    Used by producers to convert input into a producer::Message. Supported types include:

    • producer::Message (identity)
    • () (creates an empty message)
    • &[u8], Vec<u8>, &str, String, &String (converts to byte payload)

    DeserializeMessage

    Used by consumers to convert a Payload into an output type. Supported types include:

    • Vec<u8>: Returns the raw bytes.
    • String: Returns Result<String, FromUtf8Error>.