Work with producer

After setting up your clients, you can explore more to start working with producers.

Create the producer

This example shows how to create a producer.

  • Java
  • C++
  1. Producer<String> producer = pulsarClient.newProducer(Schema.STRING)
  2. .topic("my-topic")
  3. .create();
  1. Producer producer;
  2. Result result = client.createProducer("my-topic", producer);

Send messages

This example shows how to send messages using producers.

  • Java
  • C++
  • Go
  • Node.js
  • C#
  1. producer.newMessage()
  2. .key("my-message-key")
  3. .value("my-sync-message")
  4. .send();

You can terminate the builder chain with sendAsync() and get a future return.

  1. Message msg = MessageBuilder()
  2. .setContent("content")
  3. .setPartitionKey("my-message-key")
  4. .build();
  5. Result res = producer.send(msg);
  1. msg := pulsar.ProducerMessage{
  2. Payload: []byte("Here is some message data"),
  3. Key: "message-key",
  4. Properties: map[string]string{
  5. "foo": "bar",
  6. },
  7. EventTime: time.Now(),
  8. ReplicationClusters: []string{"cluster1", "cluster3"},
  9. }
  10. if _, err := producer.send(msg); err != nil {
  11. log.Fatalf("Could not publish message due to: %v", err)
  12. }
  1. For all methods of the `ProducerMessage` object, see [here](https://pkg.go.dev/github.com/apache/pulsar-client-go/pulsar#ProducerMessage).
  1. const msg = {
  2. data: Buffer.from('Hello, Pulsar'),
  3. partitionKey: 'key1',
  4. properties: {
  5. 'foo': 'bar',
  6. },
  7. eventTimestamp: Date.now(),
  8. replicationClusters: [
  9. 'cluster1',
  10. 'cluster2',
  11. ],
  12. }
  13. await producer.send(msg);

The following keys are available for producer message objects:

ParameterDescription
dataThe actual data payload of the message.
propertiesA Object for any application-specific metadata attached to the message.
eventTimestampThe timestamp associated with the message.
sequenceIdThe sequence ID of the message.
partitionKeyThe optional key associated with the message (particularly useful for things like topic compaction).
replicationClustersThe clusters to which this message is replicated. Pulsar brokers handle message replication automatically; you should only change this setting if you want to override the broker default.
deliverAtThe absolute timestamp at or after which the message is delivered.
deliverAfterThe relative delay after which the message is delivered.

Message object operations

In Pulsar Node.js client, you can receive (or read) message objects as consumers (or readers).

The message object has the following methods available:

MethodDescriptionReturn type
getTopicName()Getter method of topic name.String
getProperties()Getter method of properties.Array<Object>
getData()Getter method of message data.Buffer
getMessageId()Getter method of message id object.Object
getPublishTimestamp()Getter method of publish timestamp.Number
getEventTimestamp()Getter method of event timestamp.Number
getRedeliveryCount()Getter method of redelivery count.Number
getPartitionKey()Getter method of partition key.String

Message ID object operations

In Pulsar Node.js client, you can get message id objects from message objects.

The message id object has the following methods available:

MethodDescriptionReturn type
serialize()Serialize the message id into a Buffer for storing.Buffer
toString()Get message id as String.String

The client has a static method of message id object. You can access it as Pulsar.MessageId.someStaticMethod.

The following static methods are available for the message id object:

MethodDescriptionReturn type
earliest()MessageId representing the earliest, or oldest available message stored in the topic.Object
latest()MessageId representing the latest, or last published message in the topic.Object
deserialize(Buffer)Deserialize a message id object from a Buffer.Object
  1. var data = Encoding.UTF8.GetBytes("Hello World");
  2. await producer.Send(data);

Send messages with customized metadata

  • Send messages with customized metadata by using the builder.

    • Java
    • C++
    • C#
    1. producer.newMessage()
    2. .value("my-sync-message")
    3. .property("my-key", "my-value")
    4. .property("my-other-key", "my-other-value")
    5. .send();
    1. Message msg = MessageBuilder()
    2. .setContent("content")
    3. .setProperty("my-key", "my-value")
    4. .setProperty("my-other-key", "my-other-value")
    5. .build();
    6. Result res = producer.send(msg);
    1. var messageId = await producer.NewMessage()
    2. .Property("SomeKey", "SomeValue")
    3. .Send(data);

Async send messages

You can publish messages asynchronously using the Java client. With async send, the producer puts the message in a blocking queue and returns it immediately. Then the client library sends the message to the broker in the background. If the queue is full (max size configurable), the producer is blocked or fails immediately when calling the API, depending on arguments passed to the producer.

The following is an example.

  • Java
  • C++
  1. producer.sendAsync("my-async-message".getBytes()).thenAccept(msgId -> {
  2. System.out.println("Message with ID " + msgId + " successfully sent");
  3. });
  1. Message msg = MessageBuilder()
  2. .setContent("content")
  3. .build();
  4. producer.sendAsync(msg, [](Result result, MessageId messageId) {
  5. std::cout << "Result: " << result << "; Message ID:" << messageId;
  6. });

As you can see from the example above, async send operations return a MessageId wrapped in a CompletableFuture.

Publish messages to partitioned topics

By default, Pulsar topics are served by a single broker, which limits the maximum throughput of a topic. Partitioned topics can span multiple brokers and thus allow for higher throughput.

You can publish messages to partitioned topics using Pulsar client libraries. When publishing messages to partitioned topics, you must specify a routing mode. If you do not specify any routing mode when you create a new producer, the round-robin routing mode is used.

Use built-in message router

You can specify the routing mode in the ProducerConfiguration object to configure your producer. The routing mode determines which partition (internal topic) each message should be published to.

The following is an example:

  • Java
  • C++
  • Go
  1. String pulsarBrokerRootUrl = "pulsar://localhost:6650";
  2. String topic = "persistent://my-tenant/my-namespace/my-topic";
  3. PulsarClient pulsarClient = PulsarClient.builder().serviceUrl(pulsarBrokerRootUrl).build();
  4. Producer<byte[]> producer = pulsarClient.newProducer()
  5. .topic(topic)
  6. .messageRoutingMode(MessageRoutingMode.SinglePartition)
  7. .create();
  8. producer.send("Partitioned topic message".getBytes());
  1. #include "lib/RoundRobinMessageRouter.h" // Make sure include this header file
  2. Producer producer;
  3. Result result = client.createProducer(
  4. "persistent://public/default/my-topic",
  5. ProducerConfiguration().setMessageRouter(std::make_shared<RoundRobinMessageRouter>(
  6. ProducerConfiguration::BoostHash, true, 1000, 100000, boost::posix_time::seconds(1))),
  7. producer);
  1. client, err := pulsar.NewClient(pulsar.ClientOptions{
  2. URL: "pulsar://localhost:6650",
  3. })
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. defer client.Close()
  8. producer, err := client.CreateProducer(pulsar.ProducerOptions{
  9. Topic: "my-partitioned-topic",
  10. MessageRouter: func(msg *pulsar.ProducerMessage, tm pulsar.TopicMetadata) int {
  11. fmt.Println("Topic has", tm.NumPartitions(), "partitions. Routing message ", msg, " to partition 2.")
  12. // always push msg to partition 2
  13. return 2
  14. },
  15. })
  16. if err != nil {
  17. log.Fatal(err)
  18. }
  19. defer producer.Close()
  20. for i := 0; i < 10; i++ {
  21. if msgId, err := producer.Send(context.Background(), &pulsar.ProducerMessage{
  22. Payload: []byte(fmt.Sprintf("message-%d", i)),
  23. }); err != nil {
  24. log.Fatal(err)
  25. } else {
  26. log.Println("Published message: ", msgId)
  27. }
  28. }
  29. // subscribe a specific partition of a topic
  30. // for demos only, not recommend to subscribe a specific partition
  31. consumer, err := client.Subscribe(pulsar.ConsumerOptions{
  32. // pulsar partition is a special topic has the suffix '-partition-xx'
  33. Topic: "my-partitioned-topic-partition-2",
  34. SubscriptionName: "my-sub",
  35. Type: pulsar.Shared,
  36. })
  37. if err != nil {
  38. log.Fatal(err)
  39. }
  40. defer consumer.Close()
  41. for i := 0; i < 10; i++ {
  42. msg, err := consumer.Receive(context.Background())
  43. if err != nil {
  44. log.Fatal(err)
  45. }
  46. fmt.Printf("Received message msgId: %#v -- content: '%s'\n", msg.ID(), string(msg.Payload()))
  47. consumer.Ack(msg)
  48. }

Customize message router

  • Java
  • C++

To use a custom message router, you need to provide an implementation of the MessageRouter interface, which has just one choosePartition method:

  1. public interface MessageRouter extends Serializable {
  2. int choosePartition(Message msg);
  3. }

To use a custom message router, you need to provide an implementation of the `MessageRoutingPolicy interface, which has just one getPartition` method:

  1. class MessageRouter : public MessageRoutingPolicy {
  2. public:
  3. MessageRouter() : {}
  4. int getPartition(const Message& msg, const TopicMetadata& topicMetadata) {
  5. // The implementation of getPartition
  6. }
  7. };

The following router routes every message to partition 10:

  • Java
  • C++
  1. public class AlwaysTenRouter implements MessageRouter {
  2. public int choosePartition(Message msg) {
  3. return 10;
  4. }
  5. }
  1. class MessageRouter : public MessageRoutingPolicy {
  2. public:
  3. MessageRouter() {}
  4. int getPartition(const Message& msg, const TopicMetadata& topicMetadata) {
  5. return 10;
  6. }
  7. };

With that implementation, you can send messages to partitioned topics as below.

  • Java
  • C++
  1. String pulsarBrokerRootUrl = "pulsar://localhost:6650";
  2. String topic = "persistent://my-tenant/my-cluster-my-namespace/my-topic";
  3. PulsarClient pulsarClient = PulsarClient.builder().serviceUrl(pulsarBrokerRootUrl).build();
  4. Producer<byte[]> producer = pulsarClient.newProducer()
  5. .topic(topic)
  6. .messageRouter(new AlwaysTenRouter())
  7. .create();
  8. producer.send("Partitioned topic message".getBytes());
  1. Producer producer;
  2. Result result = client.createProducer(
  3. "persistent://public/default/my-topic",
  4. ProducerConfiguration().setMessageRouter(std::make_shared<MessageRouter>()),
  5. producer);
  6. Message msg = MessageBuilder().setContent("content").build();
  7. result = producer.send(msg);

Choose partitions when using a key

If a message has a key, it supersedes the round robin routing policy. The following java example code illustrates how to choose the partition when using a key.

  1. // If the message has a key, it supersedes the round robin routing policy
  2. if (msg.hasKey()) {
  3. return signSafeMod(hash.makeHash(msg.getKey()), topicMetadata.numPartitions());
  4. }
  5. if (isBatchingEnabled) { // if batching is enabled, choose partition on `partitionSwitchMs` boundary.
  6. long currentMs = clock.millis();
  7. return signSafeMod(currentMs / partitionSwitchMs + startPtnIdx, topicMetadata.numPartitions());
  8. } else {
  9. return signSafeMod(PARTITION_INDEX_UPDATER.getAndIncrement(this), topicMetadata.numPartitions());
  10. }

Enable chunking

Message chunking enables Pulsar to process large payload messages by splitting the message into chunks at the producer side and aggregating chunked messages on the consumer side.

The message chunking feature is OFF by default. The following is an example of how to enable message chunking when creating a producer.

  • Java
  • C++
  • Go
  • Python
  1. Producer<byte[]> producer = client.newProducer()
  2. .topic(topic)
  3. .enableChunking(true)
  4. .enableBatching(false)
  5. .create();
  1. ProducerConfiguration conf;
  2. conf.setBatchingEnabled(false);
  3. conf.setChunkingEnabled(true);
  4. Producer producer;
  5. client.createProducer("my-topic", conf, producer);
  1. client, err := pulsar.NewClient(pulsar.ClientOptions{
  2. URL: serviceURL,
  3. })
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. defer client.Close()
  8. // The message chunking feature is OFF by default.
  9. // By default, a producer chunks the large message based on the max message size (`maxMessageSize`) configured at the broker side (for example, 5MB).
  10. // Client can also configure the max chunked size using the producer configuration `ChunkMaxMessageSize`.
  11. // Note: to enable chunking, you need to disable batching (`DisableBatching=true`) concurrently.
  12. producer, err := client.CreateProducer(pulsar.ProducerOptions{
  13. Topic: "my-topic",
  14. DisableBatching: true,
  15. EnableChunking: true,
  16. })
  17. if err != nil {
  18. log.Fatal(err)
  19. }
  20. defer producer.Close()
  1. producer = client.create_producer(
  2. topic,
  3. chunking_enabled=True
  4. )

By default, producer chunks the large message based on max message size (maxMessageSize) configured at broker (eg: 5MB). However, client can also configure max chunked size using producer configuration chunkMaxMessageSize.

Work with producer - 图1note

To enable chunking, you need to disable batching (enableBatching\=false) concurrently.

Configure delayed message delivery

The following is an example of how to configure delayed message delivery for a producer.

  • Java
  • C++
  • Go
  1. // message to be delivered at the configured delay interval
  2. producer.newMessage().deliverAfter(3L, TimeUnit.Minute).value("Hello Pulsar!").send();
  1. Message msg = MessageBuilder().setContent("content")
  2. .setDeliverAfter(std::chrono::minutes(3))
  3. .build();
  4. producer.send(msg);
  1. client, err := pulsar.NewClient(pulsar.ClientOptions{
  2. URL: "pulsar://localhost:6650",
  3. })
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. defer client.Close()
  8. topicName := "topic-1"
  9. producer, err := client.CreateProducer(pulsar.ProducerOptions{
  10. Topic: topicName,
  11. DisableBatching: true,
  12. })
  13. if err != nil {
  14. log.Fatal(err)
  15. }
  16. defer producer.Close()
  17. consumer, err := client.Subscribe(pulsar.ConsumerOptions{
  18. Topic: topicName,
  19. SubscriptionName: "subName",
  20. Type: pulsar.Shared,
  21. })
  22. if err != nil {
  23. log.Fatal(err)
  24. }
  25. defer consumer.Close()
  26. ID, err := producer.Send(context.Background(), &pulsar.ProducerMessage{
  27. Payload: []byte(fmt.Sprintf("test")),
  28. DeliverAfter: 3 * time.Second,
  29. })
  30. if err != nil {
  31. log.Fatal(err)
  32. }
  33. fmt.Println(ID)
  34. ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
  35. msg, err := consumer.Receive(ctx)
  36. if err != nil {
  37. log.Fatal(err)
  38. }
  39. fmt.Println(msg.Payload())
  40. cancel()
  41. ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
  42. msg, err = consumer.Receive(ctx)
  43. if err != nil {
  44. log.Fatal(err)
  45. }
  46. fmt.Println(msg.Payload())
  47. cancel()

Intercept messages

ProducerInterceptor intercepts and possibly mutates messages received by the producer before they are published to the brokers.

The interface has three main events:

  • eligible checks if the interceptor can be applied to the message.
  • beforeSend is triggered before the producer sends the message to the broker. You can modify messages within this event.
  • onSendAcknowledgement is triggered when the message is acknowledged by the broker or the sending failed.

To intercept messages, you can add a ProducerInterceptor or multiple ones when creating a Producer as follows.

  • Java
  • C++
  1. Producer<byte[]> producer = client.newProducer()
  2. .topic(topic)
  3. .intercept(new ProducerInterceptor {
  4. @Override
  5. boolean eligible(Message message) {
  6. return true; // process all messages
  7. }
  8. @Override
  9. Message beforeSend(Producer producer, Message message) {
  10. // user-defined processing logic
  11. }
  12. @Override
  13. void onSendAcknowledgement(Producer producer, Message message, MessageId msgId, Throwable exception) {
  14. // user-defined processing logic
  15. }
  16. })
  17. .create();

Implement the custom interceptor:

  1. class MyInterceptor : public ProducerInterceptor {
  2. public:
  3. MyInterceptor() {}
  4. Message beforeSend(const Producer& producer, const Message& message) override {
  5. // Your implementation code
  6. return message;
  7. }
  8. void onSendAcknowledgement(const Producer& producer, Result result, const Message& message,
  9. const MessageId& messageID) override {
  10. // Your implementation code
  11. }
  12. void close() override {
  13. // Your implementation code
  14. }
  15. };

Configue the producer:

  1. ProducerConfiguration conf;
  2. conf.intercept({std::make_shared<MyInterceptor>(),
  3. std::make_shared<MyInterceptor>()}); // You can add multiple interceptors to the same producer
  4. Producer producer;
  5. client.createProducer(topic, conf, producer);

Work with producer - 图2note

Multiple interceptors apply in the order they are passed to the intercept method.

Configure encryption policies

The Pulsar C# client supports four kinds of encryption policies:

  • EnforceUnencrypted: always use unencrypted connections.
  • EnforceEncrypted: always use encrypted connections)
  • PreferUnencrypted: use unencrypted connections, if possible.
  • PreferEncrypted: use encrypted connections, if possible.

This example shows how to set the EnforceUnencrypted encryption policy.

  • C#
  1. using DotPulsar;
  2. var client = PulsarClient.Builder()
  3. .ConnectionSecurity(EncryptionPolicy.EnforceEncrypted)
  4. .Build();

Configure access mode

Access mode allows applications to require exclusive producer access on a topic to achieve a “single-writer” situation.

This example shows how to set producer access mode.

  • Java
  • C++

Work with producer - 图3note

This feature is supported in Java client 2.8.0 or later versions.

  1. Producer<byte[]> producer = client.newProducer()
  2. .topic(topic)
  3. .accessMode(ProducerAccessMode.Exclusive)
  4. .create();

Work with producer - 图4note

This feature is supported in C++ client 3.1.0 or later versions.

  1. Producer producer;
  2. ProducerConfiguration producerConfiguration;
  3. producerConfiguration.setAccessMode(ProducerConfiguration::Exclusive);
  4. client.createProducer(topicName, producerConfiguration, producer);