Skip to content

Instantly share code, notes, and snippets.

@digitalBush
Created March 14, 2013 02:15
Show Gist options
  • Select an option

  • Save digitalBush/5158269 to your computer and use it in GitHub Desktop.

Select an option

Save digitalBush/5158269 to your computer and use it in GitHub Desktop.
SignalR scale out via RabbitMQ and ServiceStack.Text
using System;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Messaging;
using RabbitMQ.Client;
namespace SignalR.RabbitMQ {
public static class DependencyResolverExtensions {
public static IDependencyResolver UseRabbitMq(this IDependencyResolver resolver, ConnectionFactory connectionFactory) {
if (connectionFactory == null) {
throw new ArgumentNullException("connectionFactory");
}
resolver.Register(typeof(IMessageBus), () => new RabbitMQBackplane(connectionFactory,resolver));
return resolver;
}
}
}
using System;
using System.IO;
using RabbitMQ.Client;
using ServiceStack.Text;
namespace SignalR.RabbitMQ {
public class MessageConsumer<T>:DefaultBasicConsumer {
private readonly Action<T> _action;
public MessageConsumer(Action<T> action) {
_action = action;
}
public override void HandleBasicDeliver(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey,IBasicProperties properties, byte[] body) {
using (var stream = new MemoryStream(body)) {
var obj = JsonSerializer.DeserializeFromStream<T>(stream);
_action(obj);
}
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Messaging;
using RabbitMQ.Client;
using ServiceStack.Text;
using IConnection = RabbitMQ.Client.IConnection;
namespace SignalR.RabbitMQ {
public class RabbitMQBackplane:ScaleoutMessageBus {
private const string Exchange = "signalR";
private readonly IModel _sendChannel;
private readonly IConnection _connection;
private ulong _id = 0; //TODO: Not sure what to do here...
public RabbitMQBackplane(ConnectionFactory connectionFactory, IDependencyResolver resolver) : base(resolver) {
_connection = connectionFactory.CreateConnection();
_sendChannel = _connection.CreateModel();
_sendChannel.ExchangeDeclare(Exchange, ExchangeType.Fanout, true, false, null);
var receiveChannel = _connection.CreateModel();
var queue = receiveChannel.QueueDeclare("signalR-"+Guid.NewGuid(), false, true, true, null);
receiveChannel.QueueBind(queue.QueueName, Exchange, "");
var consumer = new MessageConsumer<IList<Message>>(messages => OnReceived("0", _id++, messages));
receiveChannel.BasicConsume(queue.QueueName, true, consumer);
}
protected override Task Send(IList<Message> messages) {
return Task.Factory.StartNew(msgs => {
using (var stream = new MemoryStream()){
JsonSerializer.SerializeToStream(msgs, stream);
_sendChannel.BasicPublish(Exchange, "", null, stream.ToArray());
}
}, messages);
}
protected override void Dispose(bool disposing) {
if (disposing){
_connection.Abort();
_connection.Dispose();
}
base.Dispose(disposing);
}
}
}
@derekgreer

Copy link
Copy Markdown

I really wrestled with the .Net RabbitMQ.Client API on monitoring shutdowns. They've got some poor design/buggy behavior with respect to their ConnectionShutdown event. I ended up reestablishing connections on one of their internal SessionShutdown events and I restablish the bus's previous subscriptions and push queued publications upon reconnect.

On the issue of grabbing a new connection with each publish, that's not the recommended approach and is really going to be a bottleneck for nodes issuing many messages. That's what channels are for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment