Erlang Makefile
Switch branches/tags
v3.7.4-rc.1 v3.7.3 v3.7.3-rc.2 v3.7.3-rc.1 v3.7.2 v3.7.1 v3.7.1-beta.1 v3.7.0 v3.7.0-rc.2 v3.7.0-rc.1 v3.7.0-beta.20 v3.7.0-beta.19 rabbitmq_v3_7_0_milestone18 rabbitmq_v3_7_0_milestone17 rabbitmq_v3_7_0_milestone16 rabbitmq_v3_7_0_milestone15 rabbitmq_v3_7_0_milestone14 rabbitmq_v3_7_0_milestone13 rabbitmq_v3_7_0_milestone12 rabbitmq_v3_7_0_milestone11 rabbitmq_v3_7_0_milestone10 rabbitmq_v3_7_0_milestone9 rabbitmq_v3_7_0_milestone8 rabbitmq_v3_7_0_milestone7 rabbitmq_v3_7_0_milestone6 rabbitmq_v3_7_0_milestone5 rabbitmq_v3_7_0_milestone4 rabbitmq_v3_7_0_milestone3 rabbitmq_v3_7_0_milestone2 rabbitmq_v3_7_0_milestone1 rabbitmq_v3_6_15 rabbitmq_v3_6_15_rc1 rabbitmq_v3_6_15_milestone2 rabbitmq_v3_6_15_milestone1 rabbitmq_v3_6_14 rabbitmq_v3_6_13 rabbitmq_v3_6_13_rc2 rabbitmq_v3_6_13_rc1 rabbitmq_v3_6_13_milestone1 rabbitmq_v3_6_12 rabbitmq_v3_6_12_rc3 rabbitmq_v3_6_12_rc2 rabbitmq_v3_6_12_rc1 rabbitmq_v3_6_11 rabbitmq_v3_6_11_rc3 rabbitmq_v3_6_11_rc2 rabbitmq_v3_6_11_rc1 rabbitmq_v3_6_11_milestone5 rabbitmq_v3_6_11_milestone4 rabbitmq_v3_6_11_milestone3 rabbitmq_v3_6_11_milestone2 rabbitmq_v3_6_11_milestone1 rabbitmq_v3_6_10 rabbitmq_v3_6_10_rc2 rabbitmq_v3_6_10_rc1 rabbitmq_v3_6_10_milestone4 rabbitmq_v3_6_10_milestone3 rabbitmq_v3_6_10_milestone2 rabbitmq_v3_6_10_milestone1 rabbitmq_v3_6_9 rabbitmq_v3_6_8 rabbitmq_v3_6_7 rabbitmq_v3_6_7_rc3 rabbitmq_v3_6_7_rc2 rabbitmq_v3_6_7_rc1 rabbitmq_v3_6_7_milestone6 rabbitmq_v3_6_7_milestone5 rabbitmq_v3_6_7_milestone4 rabbitmq_v3_6_7_milestone3 rabbitmq_v3_6_7_milestone2 rabbitmq_v3_6_7_milestone1 rabbitmq_v3_6_6 rabbitmq_v3_6_6_rc2 rabbitmq_v3_6_6_rc1 rabbitmq_v3_6_6_milestone5 rabbitmq_v3_6_6_milestone4 rabbitmq_v3_6_6_milestone3 rabbitmq_v3_6_6_milestone2 rabbitmq_v3_6_6_milestone1 rabbitmq_v3_6_5 rabbitmq_v3_6_5_milestone2 rabbitmq_v3_6_5_milestone1 rabbitmq_v3_6_4 rabbitmq_v3_6_4_rc1 rabbitmq_v3_6_4_milestone2 rabbitmq_v3_6_4_milestone1 rabbitmq_v3_6_3 rabbitmq_v3_6_3_rc3 rabbitmq_v3_6_3_rc2 rabbitmq_v3_6_3_rc1 rabbitmq_v3_6_3_milestone2 rabbitmq_v3_6_3_milestone1 rabbitmq_v3_6_2 rabbitmq_v3_6_2_rc4 rabbitmq_v3_6_2_rc3 rabbitmq_v3_6_2_rc2 rabbitmq_v3_6_2_rc1 rabbitmq_v3_6_2_milestone5 rabbitmq_v3_6_2_milestone4 rabbitmq_v3_6_2_milestone3
Nothing to show

RabbitMQ Consistent Hash Exchange Type


This plugin adds a consistent-hash exchange type to RabbitMQ. This exchange type uses consistent hashing (intro blog post 1, intro blog post 2) to distribute messages between the bound queues. It is recommended to get a basic understanding of the concept before evaluating this plugin.

rabbitmq-sharding is another plugin that provides a way to partition a stream of messages among a set of consumers while trading off total stream ordering for processing parallelism.

Problem Definition

In various scenarios, you may wish to ensure that messages sent to an exchange are consistently and equally distributed across a number of different queues based on the routing key of the message, a nominated header (see "Routing on a header" below), or a message property (see "Routing on a message property" below). You could arrange for this to occur yourself by using a direct or topic exchange, binding queues to that exchange and then publishing messages to that exchange that match the various binding keys.

However, arranging things this way can be problematic:

  1. It is difficult to ensure that all queues bound to the exchange will receive a (roughly) equal number of messages without baking in to the publishers quite a lot of knowledge about the number of queues and their bindings.

  2. If the number of queues changes, it is not easy to ensure that the new topology still distributes messages between the different queues evenly.

Consistent Hashing is a hashing technique whereby each bucket appears at multiple points throughout the hash space, and the bucket selected is the nearest higher (or lower, it doesn't matter, provided it's consistent) bucket to the computed hash (and the hash space wraps around). The effect of this is that when a new bucket is added or an existing bucket removed, only a very few hashes change which bucket they are routed to.

How Consistent Hashing Routing Works

In the case of Consistent Hashing as an exchange type, the hash is calculated from the hash of the routing key of each message received. Thus messages that have the same routing key will have the same hash computed, and thus will be routed to the same queue, assuming no bindings have changed.

When you bind a queue to a consistent-hash exchange, the binding key is a number-as-a-string which indicates the number of points in the hash space at which you wish the queue to appear. The actual points are generated randomly.

The hashing distributes routing keys among queues, not messages among queues; all messages with the same routing key will go the same queue. So, if you wish for queue A to receive twice as many routing keys routed to it than are routed to queue B, then you bind the queue A with a binding key of twice the number (as a string -- binding keys are always strings) of the binding key of the binding to queue B. Note this is only the case if your routing keys are evenly distributed in the hash space. If, for example, only two distinct routing keys are used on all the messages, there's a chance both keys will route (consistently!) to the same queue, even though other queues have higher values in their binding key. With a larger set of routing keys used, the statistical distribution of routing keys approaches the ratios of the binding keys.

Each message gets delivered to at most one queue. Normally, each message gets delivered to exactly one queue, but there is a race between the determination of which queue to send a message to, and the deletion/death of that queue that does permit the possibility of the message being sent to a queue which then disappears before the message is processed. Hence in general, at most one queue.

The exchange type is "x-consistent-hash".

Supported RabbitMQ Versions

This plugin supports RabbitMQ 3.3.x and later versions.



Here is an example using the Erlang client:


test() ->
    {ok, Conn} = amqp_connection:start(#amqp_params_network{}),
    {ok, Chan} = amqp_connection:open_channel(Conn),
    Queues = [<<"q0">>, <<"q1">>, <<"q2">>, <<"q3">>],
                  #'exchange.declare' {
                    exchange = <<"e">>, type = <<"x-consistent-hash">>
    [amqp_channel:call(Chan, #'queue.declare' { queue = Q }) || Q <- Queues],
    [amqp_channel:call(Chan, #'queue.bind' { queue = Q,
                                             exchange = <<"e">>,
                                             routing_key = <<"10">> })
        || Q <- [<<"q0">>, <<"q1">>]],
    [amqp_channel:call(Chan, #'queue.bind' { queue = Q,
                                             exchange = <<"e">>,
                                             routing_key = <<"20">> })
        || Q <- [<<"q2">>, <<"q3">>]],
    Msg = #amqp_msg { props = #'P_basic'{}, payload = <<>> },
                     exchange = <<"e">>,
                     routing_key = list_to_binary(
                   }, Msg) || _ <- lists:seq(1,100000)],

As you can see, the queues q0 and q1 get bound each with 10 points in the hash space to the exchange e which means they'll each get roughly the same number of routing keys. The queues q2 and q3 however, get 20 points each which means they'll each get roughly the same number of routing keys too, but that will be approximately twice as many as q0 and q1. We then publish 100,000 messages to our exchange with random routing keys, the queues will get their share of messages roughly equal to the binding keys ratios. After this has completed, running rabbitmqctl list_queues should show that the messages have been distributed approximately as desired.

Note the routing_keys in the bindings are numbers-as-strings. This is because AMQP specifies the routing_key must be a string.

The more points in the hash space each binding has, the closer the actual distribution will be to the desired distribution (as indicated by the ratio of points by binding). However, large numbers of points (many thousands) will substantially decrease performance of the exchange type.

Equally, it is important to ensure that the messages being published to the exchange have a range of different routing_keys: if a very small set of routing keys are being used then there's a possibility of messages not being evenly distributed between the various queues. If the routing key is a pseudo-random session ID or such, then good results should follow.

Routing on a header

Under most circumstances the routing key is a good choice for something to hash. However, in some cases you need to use the routing key for some other purpose (for example with more complex routing involving exchange to exchange bindings). In this case you can configure the consistent hash exchange to route based on a named header instead. To do this, declare the exchange with a string argument called "hash-header" naming the header to be used. For example using the Erlang client as above:

      Chan, #'exchange.declare' {
              exchange  = <<"e">>,
              type      = <<"x-consistent-hash">>,
              arguments = [{<<"hash-header">>, longstr, <<"hash-me">>}]

If you specify "hash-header" and then publish messages without the named header, they will all get routed to the same (arbitrarily-chosen) queue.

Routing on a message property

In addition to a value in the header property, you can also route on the message_id, correlation_id, or timestamp message property. To do so, declare the exchange with a string argument called "hash-property" naming the property to be used. For example using the Erlang client as above:

      Chan, #'exchange.declare' {
              exchange  = <<"e">>,
              type      = <<"x-consistent-hash">>,
              arguments = [{<<"hash-property">>, longstr, <<"message_id">>}]

Note that you can not declare an exchange that routes on both "hash-header" and "hash-property". If you specify "hash-property" and then publish messages without a value in the named property, they will all get routed to the same (arbitrarily-chosen) queue.

Getting Help

Any comments or feedback welcome, to the RabbitMQ mailing list.

Continuous Integration

Build Status

Copyright and License

(c) 2013-2015 Pivotal Software Inc.

Released under the Mozilla Public License 1.1, same as RabbitMQ. See LICENSE for details.