Bunny RabbitMQ Ruby Client

repository·main·Indexed 23 days ago

https://github.com/ruby-amqp/bunny

A feature-complete RabbitMQ client for Ruby that implements the AMQP 0-9-1 protocol. It supports CRuby (3.2 through 4.0) and TruffleRuby, focusing on ease of use and minimal dependencies. The library provides tools for managing connections, channels, queues, and exchanges, with specific design patterns for concurrency, publisher confirms, and manual message acknowledgment.

Tokens
23.9K
Snippets
69
Records
132
Agent score
80%

What's inside Bunny

  1. Handling Bunny::ChannelLevelException

    main

    AMQP channel exceptions are indications from the broker that an issue occurred during a specific operation. These are typically not fatal to the entire connection and can be recovered from.

    Common causes include:

    • Re-declaring an exchange with different attributes (e.g., changing durability).
    • Re-declaring a queue with different attributes (e.g., changing auto-delete status).
    • Attempting to bind a queue to an exchange that does not exist.

    These exceptions will be subclasses of Bunny::ChannelLevelException. You should implement logging for these to identify configuration mismatches.

  2. What are AMQP exchanges and bindings?

    main

    In AMQP 0.9.1, messages are not published directly to queues. Instead, they are published to exchanges.

    An exchange acts as a mailbox that accepts messages from producers and routes them to queues based on specific criteria. To receive messages, a queue must have a binding—an association between the queue and an exchange.

    Exchanges have several attributes:

    • Name
    • Type (e.g., direct, fanout, topic, headers, or custom x- types)
    • Durability
    • Auto-delete status
    • Metadata (X-arguments)
  3. Configure automatic connection and topology recovery

    main

    Bunny includes an automatic recovery feature to handle network failures and server-initiated connection closures.

    Connection Recovery

    By default, when Bunny detects a TCP connection failure, it attempts to reconnect every 5 seconds indefinitely. To disable this, pass :automatic_recovery => false to Bunny.new.

    Topology Recovery

    When a connection is recovered, Bunny automatically performs topology recovery by:

    1. Re-opening channels.
    2. Re-declaring exchanges (except predefined ones) for each channel.
    3. Re-declaring queues for each channel.
    4. Recovering all bindings for each queue.
    5. Recovering all consumers for each queue.

    Server-Initiated Closures

    Bunny can recover from server-sent connection.close commands. To prevent Bunny from recovering from these specific closures, pass recover_from_connection_close: false to Bunny.new.

  4. Supported Ruby Environments

    main

    Bunny supports the following Ruby implementations:

    • CRuby: Versions 3.2 through 4.0 (inclusive).
    • TruffleRuby.

    Important Notes:

    • JRuby: Bunny does not support JRuby. If you are using JRuby, use March Hare instead.
    • TLS/SSL: For environments using TLS, your Ruby installation must use a recent enough OpenSSL version that includes support for TLS 1.3.
  5. Understand Concurrency in Bunny

    main
    When building multi-threaded applications, understand how Bunny handles concurrency. This includes knowing the correctness and concurrency safety of key public API classes and methods to avoid race conditions or connection issues.
  6. How RabbitMQ routing and bindings work

    main

    Bindings are rules that exchanges use to route messages to queues.

    The Routing Process:

    1. Consult Bindings: RabbitMQ looks at the exchange's binding list to find suitable queues.
    2. Mandatory Check (Step 1): If no suitable queues are found and the message was published as mandatory, the message is returned to the publisher.
    3. Queue Placement: If suitable queues are found, a copy of the message is placed into each one.
    4. Mandatory Check (Step 2): If the message was published as mandatory but there are no active consumers on the target queues, the message is returned to the publisher.
    5. Delivery: If active consumers exist and basic.qos settings permit, the message is pushed to them.

    Unroutable Messages: If a message cannot be routed, it is either dropped or returned to the producer. You can use RabbitMQ extensions like Alternate Exchanges to route unroutable messages to a different exchange.

  7. Use the Default Exchange for direct queue routing

    main

    The Default Exchange is a pre-declared direct exchange with no name (represented as an empty string "" in Bunny).

    It has a special property: every queue is automatically bound to it using the queue's name as the routing key. This allows you to deliver messages directly to a specific queue by publishing to the default exchange with the queue's name as the routing_key.

    Example: Sending a message to a specific queue via the default exchange

    ch = conn.create_channel
    q  = ch.queue("bunny.examples.hello_world", :auto_delete => true)
    
    q.subscribe do |delivery_info, properties, payload|
      puts "Received #{payload}"
    end
    
    # Publishing to the default exchange (empty string) using the queue name as the routing key
    q.publish("Hello!", :routing_key => q.name)
  8. Thread safety: Using channels in multi-threaded environments

    main

    When using Bunny in a multi-threaded application, avoid sharing channels across threads.

    Each thread (for both publishers and consumers) should create and use its own dedicated channel to ensure thread safety and prevent unexpected behavior.

  9. How Topic Exchanges route messages

    main

    Topic exchanges route messages to one or many queues based on matching between a message routing key and a pattern used to bind a queue to the exchange. This is commonly used for multicast routing and various publish/subscribe patterns.

    Routing patterns consist of words separated by dots (e.g., asia.southeast.thailand.bangkok). Matching is controlled by two special characters:

    • * (asterisk): Matches exactly one word.
    • # (hash): Matches zero or more words.

    Example patterns:

    • americas.south.# matches americas.south, americas.south.brazil, and americas.south.brazil.saopaulo.
    • americas.south.* matches americas.south.brazil but not americas.south or americas.south.brazil.saopaulo.
    q = ch.queue("americas.south", :auto_delete => true).bind(x, :routing_key => "americas.south.#")
  10. Implement explicit message acknowledgements

    main

    By default, Bunny uses the automatic acknowledgement model. To use the explicit acknowledgement model (where the application decides when a message is safely processed), set :manual_ack => true in your subscription.

    To acknowledge a message, use Bunny::Channel#acknowledge.

    Rules for Acknowledgements:

    • Acknowledgements are channel-specific. You must acknowledge a message on the same channel it was received on.
    • Do not acknowledge the same message more than once; this will cause a 406 (PRECONDITION_FAILED) error.
    • If you acknowledge multiple messages at once (by passing false as the second argument to acknowledge), the delivery_tag is treated as 'up to and including' that tag.
  11. How fanout exchanges route messages

    main

    A fanout exchange implements broadcast routing. It routes a copy of every received message to all queues bound to it. The message's routing_key is ignored by fanout exchanges.

    Common Use Cases:

    • Broadcasting global events (e.g., MMO leaderboard updates).
    • Distributing real-time score updates to multiple clients.
    • Broadcasting state or configuration updates in distributed systems.
  12. Understand the relationship between entity durability and message persistence

    main

    In Bunny/AMQP, durability and persistence are distinct concepts:

    1. Entity Durability (Exchanges and Queues): Determines if the exchange or queue definition survives a broker restart.
    2. Message Persistence: Determines if the actual message content is written to disk.

    To ensure a message is not lost during a broker restart, you must use both a durable queue and persistent messages. Simply using a durable exchange or a durable queue is not sufficient to guarantee message recovery if the messages themselves are not marked as persistent.