3,EasyNetQ-发布/订阅
一、发布
在发布/订阅模式中的角色是彼此陌生的。 一个发布者只是向世界说这个已经发生了,一位订阅者告诉世界“我在乎这个”。 在这个模型中,没有人关心特定的事件是很好的。 消息可能有一个订阅者,可能有200个,或者可能没有。 发布者不应该关心。 EasyNetQ实现这种模式。 如果您开始发布,并且没有订阅者已经开始,那么您的消息就会消失。 这是设计(先订阅,后发布,如果现发布,则消息会丢失)。
代码:
var message = new MyMessage { Text = "Hello Rabbit" }; bus.Publish(message);
二、订阅
EasyNetQ用户订阅消息类型(消息类的.NET类型)。 一旦通过调用Subscribe方法设置了类型的订阅,就会在RabbitMQ代理上创建一个持久性队列,并且任何类型的消息将被放置在队列中。 RabbitMQ将在连接时将队列中的任何消息发送给用户。
要订阅一个消息,我们需要给EasyNetQ一个消息到达时执行的操作。 我们通过订阅一个委托来做到这一点:
bus.Subscribe<MyMessage>("my_subscription_id", msg => Console.WriteLine(msg.Text));
现在每次发布MyMessage的一个实例,EasyNetQ将会调用我们的代理,并将消息的Text属性打印到控制台。
您传递给订阅的订阅ID很重要。 EasyNetQ将针对消息类型和订阅ID的每个独特组合在RabbitMQ代理上创建一个唯一的队列(消息类型+订阅ID=唯一的队列)。
每个对Subscribe的调用都会创建一个新的队列消费者。 如果您使用相同的消息类型和订阅ID来呼叫两次订阅,则将创建两个消费者从同一个队列中消费的消费者。 然后,RabbitMQ将轮流向每个消费者循环连续的消息。 这对扩展和工作分享非常有用。 假设您已经创建了处理特定消息的服务,但是工作正在重载。 只需启动该服务的新实例(在同一台机器上或另一台机器上),而无需配置任何内容,就可以自动缩放。
如果您使用不同的订阅ids但是相同的消息类型来呼叫两次订阅,那么您将创建两个队列,每个队列都有自己的消费者。 给定类型的每个消息的副本将被路由到每个队列,因此每个消费者将获得所有消息(该类型的消息)。 如果您有几个不同的服务,那些都关心相同的信息类型,这是非常好的。
写订阅回调委托时的注意事项
当从通过EasyNetQ订阅的队列接收到消息时,它们被放置在内存队列中。 一个线程坐在循环中,从队列中获取消息并调用其Action代理。 由于代理在一个线程上一次处理一次,所以您应该避免长时间运行的同步IO操作。 尽快从委托返回控制。
使用异步订阅
异步订阅允许您的用户委托立即返回任务,然后异步执行长时间运行的IO操作。 一旦长时间运行的订阅完成,只需完成任务。 在下面的示例中,我们使用异步IO操作(DownloadStringTask)向Web服务发出请求。 当任务完成时,我们在控制台上写一行。
bus.SubscribeAsync<MyMessage>("subscribe_async_test", message => new WebClient().DownloadStringTask(new Uri("http://localhost:1338/?timeout=500")) .ContinueWith(task => Console.WriteLine("Received: '{0}', Downloaded: '{1}'", message.Text, task.Result)));
另一个例子会导致异常被抛出,如果有故障,那么会导致消息被放置在默认的错误队列上:
_bus.SubscribeAsync<MessageType>("Queue_Identifier", message => Task.Factory.StartNew(() => { //在这里执行一些操作 //如果有异常,则会导致任务完成,但任务发生错误 //在下面的继续处理 }).ContinueWith(task => { if (task.IsCompleted && !task.IsFaulted) { // 一切都奏效了 } else { // 不要抓住这一点,它被进一步上升到层次结构,并导致发送到默认错误队列 throw new EasyNetQException("Message processing exception - look in the default error queue (broker)"); } }));
取消订阅
所有的订阅方法返回一个ISubscriptionResult。 它包含描述底层IConsumer使用的IExchange和IQueue的属性,如果需要,可以使用高级API IAdvancedBus进一步操作这些属性。
您可以随时通过在ISubscriptionResult实例或其ConsumerCancellation属性上调用Dispose来取消订户:
var subscriptionResult = bus.Subscribe<MyMessage>("sub_id", MyHandler); ... subscriptionResult.Dispose(); // 这相当于 subscriptionResult.ConsumerCancellation.Dispose();
这将阻止EasyNetQ从队列中消费,并关闭消费者的频道。
请注意,处理IBus或IAdvancedBus实例也将取消所有消费者并关闭与RabbitMQ的连接。
不要在消息处理程序中调用subscriptionResult.Dispose()。 这将在EasyNetQ ACK消费者频道上的消息与subscriptionResult.Dispose()调用关闭该通道之间创建竞争条件。 由于EasyNetQ的内部架构,这些调用将在不同的线程上被调用,并且时序不是确定性的。