RabbitMQ
Component format
To set up RabbitMQ pub/sub, create a component of type pubsub.rabbitmq. See the pub/sub broker component file to learn how ConsumerID is automatically generated. Read the How-to: Publish and Subscribe guide on how to create and apply a pub/sub configuration.
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: rabbitmq-pubsub
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: connectionString
value: "amqp://localhost:5672"
- name: protocol
value: amqp
- name: hostname
value: localhost
- name: username
value: username
- name: password
value: password
- name: consumerID
value: channel1
- name: durable
value: false
- name: deletedWhenUnused
value: false
- name: autoAck
value: false
- name: deliveryMode
value: 0
- name: requeueInFailure
value: false
- name: prefetchCount
value: 0
- name: reconnectWait
value: 0
- name: concurrencyMode
value: parallel
- name: publisherConfirm
value: false
- name: enableDeadLetter # Optional enable dead Letter or not
value: true
- name: maxLen # Optional max message count in a queue
value: 3000
- name: maxLenBytes # Optional maximum length in bytes of a queue.
value: 10485760
- name: exchangeKind
value: fanout
- name: saslExternal
value: false
- name: ttlInSeconds
value: 60
- name: clientName
value: {podName}
- name: heartBeat
value: 10s
- name: publishMessagePropertiesToMetadata
value: "true"
Warning
The above example uses secrets as plain strings. It is recommended to use a secret store for the secrets as described here.Spec metadata fields
| Field | Required | Details | Example |
|---|---|---|---|
| connectionString | Y* | The RabbitMQ connection string. *Mutually exclusive with protocol, hostname, username, password field | amqp://user:pass@localhost:5672 |
| protocol | N* | The RabbitMQ protocol. *Mutually exclusive with connectionString field | amqp |
| hostname | N* | The RabbitMQ hostname. *Mutually exclusive with connectionString field | localhost |
| username | N* | The RabbitMQ username. *Mutually exclusive with connectionString field | username |
| password | N* | The RabbitMQ password. *Mutually exclusive with connectionString field | password |
| consumerID | N | Consumer ID (consumer tag) organizes one or more consumers into a group. Consumers with the same consumer ID work as one virtual consumer; for example, a message is processed only once by one of the consumers in the group. If the consumerID is not provided, the Dapr runtime set it to the Dapr application ID (appID) value. | Can be set to string value (such as "channel1" in the example above) or string format value (such as "{podName}", etc.). See all of template tags you can use in your component metadata. |
| durable | N | Whether or not to use durable queues. Defaults to "false" | "true", "false" |
| deletedWhenUnused | N | Whether or not the queue should be configured to auto-delete Defaults to "true" | "true", "false" |
| autoAck | N | Whether or not the queue consumer should auto-ack messages. Defaults to "false" | "true", "false" |
| deliveryMode | N | Persistence mode when publishing messages. Defaults to "0". RabbitMQ treats "2" as persistent, all other numbers as non-persistent | "0", "2" |
| requeueInFailure | N | Whether or not to requeue when sending a negative acknowledgement in case of a failure. Defaults to "false" | "true", "false" |
| prefetchCount | N | Number of messages to prefetch. Consider changing this to a non-zero value for production environments. Defaults to "0", which means that all available messages will be pre-fetched. | "2" |
| publisherConfirm | N | If enabled, client waits for publisher confirms after publishing a message. Defaults to "false" | "true", "false" |
| reconnectWait | N | How long to wait (in seconds) before reconnecting if a connection failure occurs | "0" |
| concurrencyMode | N | parallel is the default, and allows processing multiple messages in parallel (limited by the app-max-concurrency annotation, if configured). Set to single to disable parallel processing. In most situations there’s no reason to change this. | parallel, single |
| enableDeadLetter | N | Enable forwarding Messages that cannot be handled to a dead-letter topic. Defaults to "false" | "true", "false" |
| maxLen | N | The maximum number of messages of a queue and its dead letter queue (if dead letter enabled). If both maxLen and maxLenBytes are set then both will apply; whichever limit is hit first will be enforced. Defaults to no limit. | "1000" |
| maxLenBytes | N | Maximum length in bytes of a queue and its dead letter queue (if dead letter enabled). If both maxLen and maxLenBytes are set then both will apply; whichever limit is hit first will be enforced. Defaults to no limit. | "1048576" |
| exchangeKind | N | Exchange kind of the rabbitmq exchange. Defaults to "fanout". "x-consistent-hash" requires the rabbitmq_consistent_hash_exchange plugin to be enabled on the broker. When exchangeDeclareMode is "passive", any exchange kind the broker supports is accepted, since the component does not declare the exchange. | "fanout","topic","direct","headers","x-consistent-hash" |
| exchangeDeclareMode | N | How the component obtains the exchange for a topic. "declare" (the default) creates the exchange if it does not exist. "passive" only asserts that it already exists, and never creates or modifies it. See Use an externally managed topology. | "declare", "passive" |
| queueDeclareMode | N | How the component obtains the queue for a subscription. "declare" (the default) creates the queue and binds it to the exchange. "passive" only asserts that it already exists, and leaves its bindings to the external owner. See Use an externally managed topology. | "declare", "passive" |
| saslExternal | N | With TLS, should the username be taken from an additional field (for example, CN). See RabbitMQ Authentication Mechanisms. Defaults to "false". | "true", "false" |
| ttlInSeconds | N | Set message TTL at the component level, which can be overwritten by message level TTL per request. | "60" |
| caCert | Required for using TLS | Certificate Authority (CA) certificate in PEM format for verifying server TLS certificates. | "-----BEGIN CERTIFICATE-----\n<base64-encoded DER>\n-----END CERTIFICATE-----" |
| clientCert | Required for using TLS | TLS client certificate in PEM format. Must be used with clientKey. | "-----BEGIN CERTIFICATE-----\n<base64-encoded DER>\n-----END CERTIFICATE-----" |
| clientKey | Required for using TLS | TLS client key in PEM format. Must be used with clientCert. Can be secretKeyRef to use a secret reference. | "-----BEGIN RSA PRIVATE KEY-----\n<base64-encoded PKCS8>\n-----END RSA PRIVATE KEY-----" |
| clientName | N | This RabbitMQ client-provided connection name is a custom identifier. If set, the identifier is mentioned in RabbitMQ server log entries and management UI. Can be set to {uuid}, {podName}, or {appID}, which is replaced by Dapr runtime to the real value. | "app1", {uuid}, {podName}, {appID} |
| heartBeat | N | Defines the heartbeat interval with the server, detecting the aliveness of the peer TCP connection with the RabbitMQ server. Defaults to 10s . | "10s" |
publishMessagePropertiesToMetadata | N | Whether to publish AMQP message properties (headers, message ID, etc.) to the metadata. | “true”, “false” |
Communication using TLS
To configure communication using TLS, ensure that the RabbitMQ nodes have TLS enabled and provide the caCert, clientCert, clientKey metadata in the component configuration. For example:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: rabbitmq-pubsub
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: host
value: "amqps://localhost:5671"
- name: consumerID
value: myapp
- name: durable
value: false
- name: deletedWhenUnused
value: false
- name: autoAck
value: false
- name: deliveryMode
value: 0
- name: requeueInFailure
value: false
- name: prefetchCount
value: 0
- name: reconnectWait
value: 0
- name: concurrencyMode
value: parallel
- name: publisherConfirm
value: false
- name: enableDeadLetter # Optional enable dead Letter or not
value: true
- name: maxLen # Optional max message count in a queue
value: 3000
- name: maxLenBytes # Optional maximum length in bytes of a queue.
value: 10485760
- name: exchangeKind
value: fanout
- name: saslExternal
value: false
- name: caCert
value: ${{ myLoadedCACert }}
- name: clientCert
value: ${{ myLoadedClientCert }}
- name: clientKey
secretKeyRef:
name: myRabbitMQClientKey
key: myRabbitMQClientKey
Note that while the caCert and clientCert values may not be secrets, they can be referenced from a Dapr secret store as well for convenience.
Enabling message delivery retries
The RabbitMQ pub/sub component has no built-in support for retry strategies. This means that the sidecar sends a message to the service only once. When the service returns a result, the message will be marked as consumed regardless of whether it was processed correctly or not. Note that this is common among all Dapr PubSub components and not just RabbitMQ.
Dapr can try redelivering a message a second time, when autoAck is set to false and requeueInFailure is set to true.
To make Dapr use more sophisticated retry policies, you can apply a retry resiliency policy to the RabbitMQ pub/sub component.
There is a crucial difference between the two ways to retry messages:
- When using
autoAck = falseandrequeueInFailure = true, RabbitMQ is the one responsible for re-delivering messages and any subscriber can get the redelivered message. If you have more than one instance of your consumer, then it’s possible that another consumer will get it. This is usually the better approach because if there’s a transient failure, it’s more likely that a different worker will be in a better position to successfully process the message. - Using Resiliency makes the same Dapr sidecar retry redelivering the messages. So it will be the same Dapr sidecar and the same app receiving the same message.
Create a RabbitMQ server
You can run a RabbitMQ server locally using Docker:
docker run -d --hostname my-rabbit --name some-rabbit rabbitmq:3
You can then interact with the server using the client port: localhost:5672.
The easiest way to install RabbitMQ on Kubernetes is by using the Helm chart:
helm install rabbitmq stable/rabbitmq
Look at the chart output and get the username and password.
This will install RabbitMQ into the default namespace. To interact with RabbitMQ, find the service with: kubectl get svc rabbitmq.
For example, if installing using the example above, the RabbitMQ server client address would be:
rabbitmq.default.svc.cluster.local:5672
Use topic exchange to route messages
Setting exchangeKind to "topic" uses the topic exchanges, which are commonly used for the multicast routing of messages. In order to route messages using topic exchange, you must set the following metadata:
routingKey:
Messages with a routing key are routed to one or many queues based on therouting keydefined in the metadata when subscribing.queueName:
If you don’t set thequeueName, only one queue is created, and all routing keys will route to that queue. This means all subscribers will bind to that queue, which won’t give the desired results.
For example, if an app is configured with a routing key keyA and queueName of queue-A:
apiVersion: dapr.io/v2alpha1
kind: Subscription
metadata:
name: orderspubsub
spec:
topic: B
routes:
default: /B
pubsubname: pubsub
metadata:
routingKey: keyA
queueName: queue-A
It will receive messages with routing key keyA, and messages with other routing keys are not received.
// publish messages with routing key `keyA`, and these will be received by the above example.
client.PublishEvent(context.Background(), "pubsub", "B", []byte("this is a message"), dapr.PublishEventWithMetadata(map[string]string{"routingKey": "keyA"}))
// publish messages with routing key `keyB`, and these will not be received by the above example.
client.PublishEvent(context.Background(), "pubsub", "B", []byte("this is another message"), dapr.PublishEventWithMetadata(map[string]string{"routingKey": "keyB"}))
Bind multiple routingKey
Multiple routing keys can be separated by commas.
The example below binds three routingKey: keyA, keyB, and "". Note the binding method of empty keys.
apiVersion: dapr.io/v2alpha1
kind: Subscription
metadata:
name: orderspubsub
spec:
topic: B
routes:
default: /B
pubsubname: pubsub
metadata:
routingKey: keyA,keyB,
For more information see rabbitmq exchanges.
Use a consistent hash exchange for partitioned ordering
Setting exchangeKind to "x-consistent-hash" partitions a topic across consumers while preserving the order of messages that share a routing key. Every message for a given key is routed to the same queue, so that key is processed in order, while other keys are processed in parallel on other queues.
Note
This exchange kind is provided by therabbitmq_consistent_hash_exchange plugin, which must be enabled on the broker before it can be used.The routingKey metadata means something different on each side of publishing/subscribing:
- When publishing, it is the partition key. The exchange hashes it to choose a queue, so any messages that must stay in order relative to one another must share a key.
- When subscribing, it is the queue’s bucket weight. A single positive integer that represents the number of buckets the queue occupies on the hash ring, so the key space is divided between the bound queues in proportion to their weights. Unlike a topic exchange, a consistent hash exchange has one effective binding per queue, so only one value is accepted here.
Each consumer binds its own queue, so give each one a distinct queueName on the Subscription (or a distinct consumerID on the Component), and set concurrencyMode to single so that each partition is processed in order:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: order-pub-sub
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: connectionString
value: "amqp://localhost:5672"
- name: exchangeKind
value: "x-consistent-hash"
- name: concurrencyMode
value: "single"
apiVersion: dapr.io/v2alpha1
kind: Subscription
metadata:
name: orderspubsub
spec:
topic: orders
routes:
default: /orders
pubsubname: order-pub-sub
metadata:
queueName: orders-partition-0
routingKey: "10"
Publish with the partition key in the routingKey metadata. In each example both messages carry the same partition key, so both are routed to the same queue and processed in the order they were published.
with DaprClient() as client:
client.publish_event(
pubsub_name='order-pub-sub',
topic_name='orders',
data='created',
publish_metadata={'routingKey': 'tenant-42/serial-7'},
)
client.publish_event(
pubsub_name='order-pub-sub',
topic_name='orders',
data='shipped',
publish_metadata={'routingKey': 'tenant-42/serial-7'},
)
const client = new DaprClient();
await client.pubsub.publish("order-pub-sub", "orders", "created", {
metadata: { routingKey: "tenant-42/serial-7" },
});
await client.pubsub.publish("order-pub-sub", "orders", "shipped", {
metadata: { routingKey: "tenant-42/serial-7" },
});
client.PublishEvent(context.Background(), "order-pub-sub", "orders", []byte("created"), dapr.PublishEventWithMetadata(map[string]string{"routingKey": "tenant-42/serial-7"}))
client.PublishEvent(context.Background(), "order-pub-sub", "orders", []byte("shipped"), dapr.PublishEventWithMetadata(map[string]string{"routingKey": "tenant-42/serial-7"}))
Note
Bucket weights are relative, and small weights divide the key space coarsely. Binding two queues with a weight of1 each can leave the split noticeably uneven whereas a weight in the tens spreads keys more evenly across the same number of queues.Use an externally managed topology
By default the component declares the topology it needs: an exchange named after the topic, a queue for each subscription, and the binding between them. If your exchanges and queues are instead managed declaratively, for instance by the RabbitMQ Cluster Kubernetes Operator’s Topology Operator, by Terraform, or by any other provisioning step, that declaration will collide.
An AMQP declaration is idempotent only when every property matches. Where the two disagree, RabbitMQ returns PRECONDITION_FAILED (406) and the component cannot use that topic at all. Because the defaults on each side differ, this happens out of the box rather than only in unusual configurations.
To avoid this collision, set either mode to "passive" to make the external owner the single source of truth:
exchangeDeclareMode: passiveasserts that the topic exchange exists and never creates or modifies it.queueDeclareMode: passiveasserts that the consumer queue exists, and leavesqueue.bindto the external owner, since a binding belongs to whoever owns the queue.
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: order-pub-sub
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: connectionString
value: "amqp://localhost:5672"
- name: exchangeDeclareMode
value: "passive"
- name: queueDeclareMode
value: "passive"
Dead lettering follows queueDeclareMode, not exchangeDeclareMode, because the dead letter exchange and queue are named after the consumer at runtime: they belong to the queue rather than to the topology an external owner defines up front. With queueDeclareMode: declare the component creates them as before. With queueDeclareMode: passive it creates nothing and enableDeadLetter has no effect, so dead lettering becomes part of the queue definition, set up by whoever owns the queue and under whatever names they choose.
exchangeDeclareMode and queueDeclareMode are independent, so set whichever matches what the external owner actually manages. A common combination is an externally managed exchange with queues the component still declares, which is what a consistent hash setup needs when consumer queue names are only known at runtime:
- name: exchangeDeclareMode
value: "passive"
- name: exchangeKind
value: "x-consistent-hash"
Note
UnderqueueDeclareMode: passive the component does not send queue arguments, so settings such as maxLen, maxLenBytes, maxPriority, queueType and enableDeadLetter become the responsibility of whoever declares the queue. If an exchange or queue that the component expects to already exist is missing, it fails with an error naming the missing object and the mode that stopped it being created.Use priority queues
Dapr supports RabbitMQ priority queues. To set a priority for a queue, use the maxPriority topic subscription metadata.
Declarative priority queue example
apiVersion: dapr.io/v2alpha1
kind: Subscription
metadata:
name: pubsub
spec:
topic: checkout
routes:
default: /orders
pubsubname: order-pub-sub
metadata:
maxPriority: 3
Programmatic priority queue example
@app.route('/dapr/subscribe', methods=['GET'])
def subscribe():
subscriptions = [
{
'pubsubname': 'pubsub',
'topic': 'checkout',
'routes': {
'default': '/orders'
},
'metadata': {'maxPriority': '3'}
}
]
return jsonify(subscriptions)
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.json({ type: 'application/*+json' }));
const port = 3000
app.get('/dapr/subscribe', (req, res) => {
res.json([
{
pubsubname: "pubsub",
topic: "checkout",
routes: {
default: '/orders'
},
metadata: {
maxPriority: '3'
}
}
]);
})
package main
"encoding/json"
"net/http"
const appPort = 3000
type subscription struct {
PubsubName string `json:"pubsubname"`
Topic string `json:"topic"`
Metadata map[string]string `json:"metadata,omitempty"`
Routes routes `json:"routes"`
}
type routes struct {
Rules []rule `json:"rules,omitempty"`
Default string `json:"default,omitempty"`
}
// This handles /dapr/subscribe
func configureSubscribeHandler(w http.ResponseWriter, _ *http.Request) {
t := []subscription{
{
PubsubName: "pubsub",
Topic: "checkout",
Routes: routes{
Default: "/orders",
},
Metadata: map[string]string{
"maxPriority": "3"
},
},
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(t)
}
Setting a priority when publishing a message
To set a priority on a message, add the publish metadata key maxPriority to the publish endpoint or SDK method.
curl -X POST http://localhost:3601/v1.0/publish/order-pub-sub/orders?metadata.priority=3 -H "Content-Type: application/json" -d '{"orderId": "100"}'
with DaprClient() as client:
result = client.publish_event(
pubsub_name=PUBSUB_NAME,
topic_name=TOPIC_NAME,
data=json.dumps(orderId),
data_content_type='application/json',
metadata= { 'priority': '3' })
await client.pubsub.publish(PUBSUB_NAME, TOPIC_NAME, orderId, { 'priority': '3' });
client.PublishEvent(ctx, PUBSUB_NAME, TOPIC_NAME, []byte(strconv.Itoa(orderId)), map[string]string{"priority": "3"})
Use quorum queues
By default, Dapr creates classic queues. To create quorum queues, add the following metadata to your pub/sub subscription
apiVersion: dapr.io/v2alpha1
kind: Subscription
metadata:
name: pubsub
spec:
topic: checkout
routes:
default: /orders
pubsubname: order-pub-sub
metadata:
queueType: quorum
Time-to-live
You can set a time-to-live (TTL) value at either the message or component level. Set default component-level TTL using the component spec ttlInSeconds field in your component.
Note
If you set both component-level and message-level TTL, the default component-level TTL is ignored in favor of the message-level TTL.Single Active Consumer
The RabbitMQ Single Active Consumer setup ensures that only one consumer at a time processes messages from a queue and switches to another registered consumer if the active one is canceled or fails. This approach might be required when it is crucial for messages to be consumed in the exact order they arrive in the queue and if distributed processing with multiple instances is not supported. When this option is enabled on a queue by Dapr, an instance of the Dapr runtime will be the single active consumer. To allow another application instance to take over in case of failure, Dapr runtime must probe the application’s health and unsubscribe from the pub/sub component.
Note
This pattern will prevent the application to scale as only one instance can process the load. While it might be interesting for Dapr integration with legacy or sensible applications, you should consider a design allowing distributed processing if you need scalability.apiVersion: dapr.io/v2alpha1
kind: Subscription
metadata:
name: pubsub
spec:
topic: orders
routes:
default: /orders
pubsubname: order-pub-sub
metadata:
singleActiveConsumer: "true"
Publishing message properties to metadata
To enable message properties being published in the metadata, set the publishMessagePropertiesToMetadata field to "true" in the component spec.
This will include properties such as message ID, timestamp, and headers in the metadata of the published message.
Related links
- Basic schema for a Dapr component in the Related links section
- Read this guide for instructions on configuring pub/sub components
- Pub/Sub building block