Announcement Announcement Module
Collapse
No announcement yet.
Removal of Support for Mandatory and Immediate Flags Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Removal of Support for Mandatory and Immediate Flags

    I was looking for the mandatory flag on RabbitTemplate and saw that it had been removed -- https://jira.springsource.org/browse/AMQP-76 and http://forum.springsource.org/showpo...40&postcount=5.

    I was hoping to make a case for it's resurrection. What I'm after is a way to ensure a message sent by a producer will route to some queue. I do not want the messages to be silently dropped, rather I would like some type of feedback that the message is not routable.

    Is there another way to accomplish this? Is there another way to specify "mandatory" outside of the RabbitTemplate? Would it be possible to restore support for "mandatory" on RabbitTemplate?

    Thank you,

    Harlan

  • #2
    The 'mandatory' feature of AMQP is questionable since it somewhat counteracts the value of loose-coupling that is inherent in messaging solutions. The Exchange is really no more than a stateless router... it checks whether there are any Queues that should receive a given Message. The most common case where you actually want to know that the message is in fact going to arrive in a Queue (regardless if there is a Consumer at this very moment or not) is when you are doing point-to-point messaging (as opposed to publish/subscribe). That is typically accomplished with a Direct Exchange where the routing-key is the Queue name (often relying on the nameless Exchange where all Queues are automatically bound with their name). So, rather than using "immediate" as a flag when producing Messages, it's probably better to actually consider that the particular messaging scenario calls for *point-to-point Messaging directly to a Queue*. In that case, you should simply declare the Queue from the producer side of the application. Even if the Queue is also being declared on the consuming side, there will not be a conflict since the declare operation is itself idempotent.

    Is that convincing?
    -Mark

    Comment


    • #3
      Mark,

      Thank you for the quick reply. I've tried using a point-to-point via direct exchange (producer & consumer delcared queue) but was not able to achieve what I'm looking for.

      The scenario is something like this:

      We have multiple producers (in different applications) and a single consumer (in yet another app). For this to work the producers and consumer need to match up on virtualHost, exchange and routingKey -- so a simple mistake can lead to discarded messages (which we can't have). With that in mind my producers need some assurance that the messages they send will eventually be handled by the consumer. If the message is unroutable (bad exchange, Rabbit MQ down) the producer will use an alternative. I see the coupling here of producer to consumer as simple part of the requirements

      The best way I can see to accomplish this is to have the consumer declare the exchange and queue; the producer declares nothing. The producer sends with the "mandatory" flag set -- if he was incorrectly configured (bad routingkey) he will know the message was unroutable and will be discarded. If the producer were configured incorrectly and he delcared the queue the message would be routable but would end up in a queue without any consumers and the producer would never be aware.

      In short, I want to guard against a misconfigured producer which is silently having his messages discarded or kept in a mystery queue.

      This is my turn at convincing. Thanks again,

      Harlan

      Comment


      • #4
        I'm not convinced yet, but still listening. I'm not a purist, so I'm willing to be pragmatic, but I know the purists deprecate the mandatory flag.

        Here's an idea. If your producers use channel-transacted=true and the exchange does not exist they will get immediate feedback from the broker. Maybe if you have a single consumer the best way for it to signal that it is ready is to declare the exchange?

        Here's another. I'm not really sure why you need AMQP for this use case; couldn't the consumer just expose an HTTP endpoint - then you would know if it was there? Maybe there are actually a variable number of queues in the consumer (that would be an argument in favour of AMQP), but you said that both sides have to agree on the routing key (which also sounded odd to me), implying that the routes are fixed.

        Comment


        • #5
          The specific use case here is a notification system. Our various systems and applications communicate errors, alerts, warnings, etc. to a notification system (web app) which then decides how to handle it (record it for auditing, email, jabber, text message, turn on the siren, etc.). Our current production implementation does use an HTTPInvoker endpoint, but this really limits when we can take the notification system down and the cost of it going down on it's own. And we really don't need this level of "immediate processing"; the clients sending the notification need them to be eventually processed, but not necessarily immediately.

          So this is where messaging came in. If our notifications were handled by a message broker we would be free to take the notification system offline for maintenance, wouldn't loose notifications during outage, etc. We would now be reliant on the message broker being up, but our experience with ActiveMQ has shown us this is a pretty safe bet (we can do HA as well). We've been wanting to look into RabbitMQ and this seemed like a good opportunity. Having the ability to create additions queues (as you mention) and consumers is also a benefit -- say we want notifications from a particular system to be processed differently.

          The current RabbitMQ based implementation of our notification system is using channelTransacted=true, which does seem to catch use of an incorrect exchange. So that is some help, but still doesn't ensure the message is routable.

          Support for "mandatory" and "immediate" seem like valuable features to have in the tool box, and when looking to migrate from existing message solutions they may be essential. I'm certainly open to alternatives, but I just don't see it.

          I absolutely appreciate the listening so thanks for your time,

          --Harlan

          Comment


          • #6
            Mark & Dave,

            Just wanted to check back and see if there are any additional thoughts here. Is it still possible for a return of support for mandatory and immediate?

            Thanks,

            Harlan

            Comment


            • #7
              If the consumer creates the exchange and the binding and the message is still not routable, I think you have an easy to detect configuration error in a single place (e.g. in an integration test). Isn't that good enough?

              Maybe you could try it out, and also try an alternative with a hand-coded send operation using a ChannelCallback that uses mandatory/immediate=true (you would only have to write it once and give it to all the producers). I'd be interested to hear if the callback approach is usable because if it's not then maybe we need to open up some more of the template or add more callbacks.

              Comment


              • #8
                Use of the mandatory flag would lead to a "fast fail" of the send in a production environment (rather than silently dropping the message). Integration test wouldn't really help us out here as we are talking about production configuration and that configuration can change over time.

                Explored use of the ChannelCallback. Downside here is that my producers need to get the specific callback implementation and then use RabbitTemplate execute which takes the callback (rather than the send methods they would normally use).

                At this point would prefer to extend RabbitTemplate adding back in support for mandatory and immediate. That would be easy enough if doSend were not private. Any chance this could change to protected? (Maybe this is what you were referring to by "open up some more of the template").

                Thanks,

                Harlan

                Comment


                • #9
                  Is it really better for you to extend the template itself (and hope that no-one uses the vanilla one), rather than provide a ChannelCallback, or some other so-far-unimagined callback that can be used with the standard version? It was the the unimagined callback that I was hoping you might design for us, but I'm certainly open to making a method protected if that's the only way. Or we could just add the flags back and Javdoc them with "here be dragons".

                  Comment


                  • #10
                    I've attached a couple of rough implementations, SendChannelCallback and CustomRabbitTemplate. RabbitExampleProducer is an test main which demonstrates usage.

                    The summary is ChannelCallback is just too awkward to use (as far as I understand it, please let me know if I'm missing something). Providing a custom RabbitTemplate implementation seems most appealing, especially when considering sharing with other developers (those writing the producers). It would be helpful if RabbitTemplate could become more friendly for customization (loosen up accessibility).

                    Let me know what you think. Thank you for your time.

                    --Harlan

                    Comment


                    • #11
                      I have some niggling doubts about the implementation of your producers - in particular I think the ReturnListener has to be a property of the ConnectionFactory if it is supported at all in the framework. Did you actually try this with a realistic failure scenario? The listener callback should come on the Connection thread I think, so it's not going to be part of the ongoing transaction if there was one in the producer. I don't know if that would affect your real use case, but it makes this quite a slippery slope to introduce a new feature in Spring AMQP, and I'd be a bit worried about your own implementations if I was passing them to others for general use. We might need an abstraction that makes it clearer what the limitations of ReturnListener are.

                      Comment


                      • #12
                        Yes, the ReturnListener callback does occur on the AMQP Connection thread, but that is not an issue in our case. We are not trying to manage it all in a transaction, but do want to know the message was unroutable so the system can take specific action to deal with it.

                        The current implementation we are using resets the ReturnListener on the channel just prior to ConnectionFactoryUtils.releaseResources(resourceHo lder). This has worked in "realistic failure scenarios", but has by means gone through exhaustive testing. I imagine this approach may be problematic given Channel.returnListener is accessed on a different thread. I'm not familiar enough with what's happening in the RabbitMQ client code to know how much of an issue this is. Given those potential issues, I see your point that it may be best to associate the ReturnListener with the ConnectionFactory.

                        If Spring AMQP provided clean support for mandatory/immediate that would be ideal. If not then some direction on how to implement a workaround would be very helpful (especially with the use of CachingConnectionFactory).

                        --Harlan

                        Comment


                        • #13
                          Hi Harlan,

                          'basic.publish' (from client to broker) and 'basic.return' (from broker to client when publish had mandatory/immediate flags and either route was not found or there is no active consumer on routed queue) are both asynchronous calls.
                          That means 'basic.return' back to client can happen at some time in the future.

                          Current Spring implementation caches not only connections but also channels.
                          It is possible that two parallel threads use the same channel at the same time. One thread might expect message delivery failure, while another can cause the channel or connection to be closed (due to some kind of exception). In that case ReturnListener will not be notified even if its message was unroutable or not consumed immediately.

                          So possible solution for you could be that you use your 'SendChannelCallback' and make sure that the same channel is not used by another thread in parallel.

                          /Tomas

                          Comment


                          • #14
                            Sorry for the delay in responding.

                            Is it possible to "make sure that the same channel is not used by another thread in parallel" while using CachingConnectionFactory? Or would this require a move to SingleConnectionFactory?

                            --Harlan

                            Comment


                            • #15
                              Both ConnectionFactory implementations already segregate channels from the moment they are logically created to the moment they are logically closed. Tomas was hinting that you might be able to implement some blocking to force the producer to wait for the basic.return before continuing (maybe after sending a batch of messages to make it more efficient) - as long as you delay the call to close() you know that no-one else is using the channel.

                              Your problem is actually much deeper. There is literally no way to be sure that you will ever get a basic.return callback - it's asynchronous and the Connection can be closed without warning, e.g. on a hard error that your producer actually wasn't responsible for. If you are OK with that, then we can think about how you might add the callback in the ConnectionFactory.

                              Your code samples were pretty basic. What would your real-life ReturnListener actually do? How would it get a reference to the business data that was undelivered?

                              The RabbitMQ broker engineers, by the way, refer to immediate and mandatory flags as "misfeatures" in the spec, and say they reserve the right to remove those features from RabbitMQ at any time. If I were you I'd try harder to find an alternative solution.

                              Comment

                              Working...
                              X