Azure / go-amqp

AMQP 1.0 client library for Go.
https://github.com/Azure/go-amqp
MIT License
104 stars 56 forks source link

Artemis, anycast routing, queues w/o random id #319

Closed thorstenhirsch closed 5 months ago

thorstenhirsch commented 7 months ago

Trying to use go-amqp with Artemis the same way as amqp-10-jms-spring-boot uses Artemis. I have 2 problems with ad-hoc creation of queues I couldn't solve, yet:

  1. go-amqp always creates queues with routing-type=multicast, while the JMS client creates queues with routing-type=anycast (unicast). How can I switch to anycast... I don't this it's an applicationProperty, is it?
  2. go-amqp receivers create queue names with the pattern ContainerID.Name. The ContainerID is in the ConnOptions, the Name in the ReceiverOptions. Leaving either empty will result in a random unique string, so it doesn't seem possible to get a simple queue name w/o a dot in the middle like "queue1".

So this an address+queue I'd like to create (same name, anycast):

image

And this is what go-amqp creates (ContainerID.Name, multicast): image

Any chance to enhance go-amqp with the behaviour I'd like to have?

jhendrixMSFT commented 7 months ago

This is likely controlled via either connection properties, link properties, or a combination of both. Either way, it's specific to Artemis as these are not governed by the AMQP 1.0 spec. I did some searching through the Artemis codebase but didn't find anything obvious.

One thing you can try is to use a tool like Wireshark (it knows how to decode AMQP frames) to inspect the traffic when creating a queue with the JMS client to see what connection and/or link properties are sent from the client.

thorstenhirsch commented 6 months ago

Found it in the Artemis code:

   public static final byte QUEUE_TYPE = 0x00;
   public static final byte TOPIC_TYPE = 0x01;
   public static final byte TEMP_QUEUE_TYPE = 0x02;
   public static final byte TEMP_TOPIC_TYPE = 0x03;

...and...

         routingType = getMessageAnnotation(AMQPMessageSupport.JMS_DEST_TYPE_MSG_ANNOTATION);
         if (routingType != null) {
            if (AMQPMessageSupport.QUEUE_TYPE == ((Number) routingType).byteValue() || AMQPMessageSupport.TEMP_QUEUE_TYPE == ((Number) routingType).byteValue()) {
               return RoutingType.ANYCAST;
            } else if (AMQPMessageSupport.TOPIC_TYPE == ((Number) routingType).byteValue() || AMQPMessageSupport.TEMP_TOPIC_TYPE == ((Number) routingType).byteValue()) {
               return RoutingType.MULTICAST;
            }
         } 

...and...

   public static final Symbol JMS_DEST_TYPE_MSG_ANNOTATION = getSymbol("x-opt-jms-dest");

...and when looking at the message structure it turns out, that these annotations are go-amqp's DeliveryAnnotations. Guess now I have everything I need regarding ANYCAST/MULTICAST. 😀

I keep this issue open, because I still need to find a solution for the random id in ContainerID.Name.

jhendrixMSFT commented 6 months ago

Did you look through the Artemis codebase to see how they determine the queue name?

thorstenhirsch commented 6 months ago

Well, I've found this in the Artemis docs:

By default any receiving link that attaches to an address that has only multicast enabled will be treated as a subscription and a corresponding subscription queue will be created. If the Terminus Durability is either UNSETTLED_STATE or CONFIGURATION then the queue will be made durable (similar to a JMS durable subscription) and given a name made up from the container id and the link name, something like my-container-id:my-link-name.

I'm interested in these durable subscription queues. From my tests I would conclude the queue name (in go-amqp terms) is ContainerID.Name(ReciverOptions). Actually I wonder why the link name is mentioned in the Artemis docs at this point, because we're only talking about the subscriber, which is the target node. But in the AMQP 1.0 specification in section 2.6.1 a link name is defined by the target AND THE SOURCE node:

A link’s name uniquely identifies the link from the container of the source to the container of the target node

So I'm not 100% sure, yet, but to me it looks like a qpid/proton receiver only sets the ContainerID and leaves the Name(ReciverOptions) empty while go-amqp sets either of them to a unique ID if it's empty. Thus with go-amqp it's currently not possible to give durable subscription queues a name without a . and at least one character to the left and to the right of it.

Or did I miss something?

jhendrixMSFT commented 6 months ago

Per 2.7.3 the link's name is mandatory, so we cannot omit it.

What parameters are you passing to Session.NewReceiver()?

thorstenhirsch commented 6 months ago
    receiverOptions := &amqp.ReceiverOptions{
        Durability:       amqp.DurabilityUnsettledState,
        Name:             "amcName",
        SourceDurability: amqp.DurabilityUnsettledState,
        TargetAddress:    "queue1",
    }
    receiver, err := session.NewReceiver(ctx, "queue1", receiverOptions)
jhendrixMSFT commented 6 months ago

And if you create a receiver with the same configuration via JMS the name ends up being different on the Artemis side right? Have you tried using Wireshark to see how the frames differ?

thorstenhirsch commented 5 months ago

Got it. I had to add SourceCapabilities to the receiverOptions:

    receiverOptions := &amqp.ReceiverOptions{
        SourceCapabilities: []string{"queue"},
    }

This is how Artemis decides to create an anycast queue.