Azure Service Bus transport supports configuring multiple Azure Service Bus namespaces, in order to:
- Enable various namespace partitioning strategies to cover scenarios such as High Availability and multiple Data Center support, or to overcome Azure services limits.
- Enable cross namespace routing to endpoints outside of the partition set.
The namespace partitioning strategy can be configured using the NamespacePartitioning()
configuration API section, where the cross namespace routing can be configured using the NamespaceRouting()
API section. NServiceBus provides a few namespace partitioning strategies implementations. It's also possible to implement a custom partitioning strategy if needed.
Single namespace partitioning
By default the Azure Service Bus transport uses the SingleNamespacePartitioning
strategy, when it is configured using the ConnectionString
extension method:
var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
transport.ConnectionString(
connectionString: "Endpoint=sb://[NAMESPACE].servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
This is the functional equivalent of providing a namespace using the AddNamespace
partitioning API with the default namespace alias and namespace's connection string.
var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
var partitioning = transport.NamespacePartitioning();
partitioning.UseStrategy<SingleNamespacePartitioning>();
partitioning.AddNamespace(
name: "default",
connectionString: "Endpoint=sb://[NAMESPACE].servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
With this strategy, the transport uses only a single namespace to send and receive messages, hence only one namespace can be configured for the purpose of partitioning. When more than one namespace, or none, is specified for partitioning, then a ConfigurationErrorsException will be thrown at startup.
Round robin namespace partitioning
The RoundRobinNamespacePartitioning
can be used to avoid throttling by the service. With this strategy, the transport uses multiple namespaces for the communication between endpoints. Messages are sent to a single namespace and received from all namespaces. For sending operations, namespaces are chosen in a round-robin fashion.
var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
var partitioning = transport.NamespacePartitioning();
partitioning.UseStrategy<RoundRobinNamespacePartitioning>();
partitioning.AddNamespace(
name: "namespace1",
connectionString: "Endpoint=sb://namespace1.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
partitioning.AddNamespace(
name: "namespace2",
connectionString: "Endpoint=sb://namespace2.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
partitioning.AddNamespace(
name: "namespace3",
connectionString: "Endpoint=sb://namespace3.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
Multiple namespaces have to be configured when using RoundRobinNamespacePartitioning
strategy. When less than two namespaces are specified, then a ConfigurationErrorsException will be thrown at startup.
Fail over namespace partitioning
The FailOverNamespacePartitioning
can be used to provide High Availability. It uses two different namespaces: a primary and a secondary. The transport uses the primary namespace by default, and switches to secondary one when the primary is not available.
var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
var partitioning = transport.NamespacePartitioning();
partitioning.UseStrategy<FailOverNamespacePartitioning>();
partitioning.AddNamespace(
name: "primary",
connectionString: "Endpoint=sb://primary.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
partitioning.AddNamespace(
name: "secondary",
connectionString: "Endpoint=sb://secondary.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
Exactly two namespaces have to be configured when using FailOverNamespacePartitioning
strategy. When only one or more than two namespaces are specified, then a ConfigurationErrorsException will be thrown at startup.
Combining High Availability and failover options
To achieve high availability and failover, a custom strategy can be used. For example, a combination of round robin and failover strategies would ensure that messages are not throttled by the broker and sent when one of the namespaces is experiencing an outage.
Caching of send/publish namespaces
Calculation of send/publish destination namespaces is an operation that is required for each dispatch message. For certain strategies performance can be improved by caching send/publish destination namespaces to be re-used for subsequent dispatches. For other strategies such as RoundRobinNamespacePartitioning
namespaces should not be cached. Namespace partitioning strategies can control caching by setting the value of the INamespacePartitioningStrategy.
property.
public class MyNamespacePartitioningStrategy :
INamespacePartitioningStrategy
{
ReadOnlySettings settings;
public MyNamespacePartitioningStrategy(ReadOnlySettings settings)
{
this.settings = settings;
}
public IEnumerable<RuntimeNamespaceInfo> GetNamespaces(PartitioningIntent partitioningIntent)
{
throw new NotImplementedException();
}
public bool SendingNamespacesCanBeCached { get; }
}
Cross namespace routing
NServiceBus allows to specify destination addresses using an "endpoint@physicallocation"
in various places such as the Send and Routing API or the MessageEndpointMappings
. In this notation the physicallocation
section represents the location where the endpoint's infrastructure is hosted, such as a machine name or a Service Bus namespace.
Using this notation it is possible to route messages to any endpoint hosted in namespaces that do not belong to the current endpoint's partition set.
endpointInstance.Send(
destination: "sales@Endpoint=sb://destination1.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]",
message: new MyMessage());
Versions 7 and above of the Azure Service Bus transport it is also possible to provide an alias for namespaces, and use alias instead of connection string value; in all of these places.
endpointInstance.Send(
destination: "sales@destination1",
message: new MyMessage());
This requires namespace alias and connection string to be registered using the NamespaceRouting()
API.
var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
var routing = transport.NamespaceRouting();
routing.AddNamespace(
name: "destination1",
connectionString: "Endpoint=sb://destination1.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
routing.AddNamespace(
name: "destination2",
connectionString: "Endpoint=sb://destination2.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
Versions 8 and above of the Azure Service Bus transport provide a more convenient way of sending messages over multiple namespaces that does not require to explicitly specify the destination.
To leverage that capability the namespace alias and connection string need to be registered using the NamespaceRouting()
API. Furthermore, the endpoint name needs to be registered on the namespace that it belongs to.
var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
// Step 1 - associate MyMessage with "sales" endpoint (logical routing)
var routing = transport.Routing();
routing.RouteToEndpoint(typeof(MyMessage), "sales");
// Step 2 - register namespace that will be used for "sales" endpoint
var namespaceRouting = transport.NamespaceRouting();
var destination = namespaceRouting.AddNamespace(
name: "destination1",
connectionString: "Endpoint=sb://destination1.servicebus.windows.net;SharedAccessKeyName=[KEYNAME];SharedAccessKey=[KEY]");
// Step 3 - Associate endpoint "sales" with the namespace to route
// MyMessage messages to (physical routing)
destination.RegisteredEndpoints.Add("sales");
With that in place a normal send operation can be issued without needing to specify the destination with an alias or a connection string.
endpointInstance.Send(message: new MyMessage());
Cross namespace subscriptions
Similar to cross namespace sending it is also possible to subscribe to registered endpoints outside the topology by registering the publisher explicitly.
var endpointOrientedTopology = transport.UseEndpointOrientedTopology();
endpointOrientedTopology.RegisterPublisher(
type: typeof(MyEvent),
publisherName: "sales");
EndpointOrientedTopology
as the ForwardingTopology
relies on the Azure Service Bus forwarding feature that does not allow forwarding between entities in different namespaces.Default namespace alias
When using the ConnectionString
method to configure a namespace, it will get an alias as well. This alias is represented by the DefaultNamespaceAlias
configuration setting, which has a value of default
.
When doing cross namespace request reply communication between endpoints configured this way, in combination with the UseNamespaceAliasesInsteadOfConnectionStrings()
configuration method to secure connection strings, then the reply address header will include a value of "sourceendpoint@default"
. However, the connection string that is mapped to this alias is different for each endpoint in the communication and it will break the request-reply pattern.
In order to overcome this problem, it is possible to change the value of the DefaultNamespaceAlias
configuration setting using the API:
var transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
transport.DefaultNamespaceAlias("myalias");
or use NamespacePartitioning().
with a different alias instead of the ConnectionString()
method in the source endpoint.
Also, ensure that the same alias and connection string are registered with the replying endpoint using the NamespaceRouting().
method.