9、Subscribe订阅

  一个EasyNetQ订阅者订阅一种消息类型(消息类的.NET 类型)。一旦通过调用Subscribe方法对一个类型建立了订阅,一个持久化的队列就会在RabbitMQ broker代理服务器上被创建,这个类型的任何消息都会被发送到这个队列上。订阅者无论什么时候连接上,RabbitMQ都会把消息从队列中发送给订阅者。

订阅时需要指定收到消息该怎么处理,我们一般会传递一个Action<T>泛型委托:

bus.Subscribe<MyMessage>("my_subscription_id", msg => Console.WriteLine(msg.Text));

 

现在每次MyMessage实例被发布后(经RabbitMQ再发送给订阅者),EasyNetQ会调用我们的委托方法,打印MyMessage的Text属性到控制台。

你传给Subscribe方法的订阅Id是非常重要的

EasyNetQ将会在RabbitMQ Broker代理服务器上为特定的消息类型和订阅id的组合创建唯一的队列。

(消息队列名称为:    消息命名空间名.消息类名:消息类库文件名_订阅时指定的id)如     Model.MyMessage:Models_my_subscription_id

每一次调用Subscribe方法会创建一个新的队列消费者。如果你用相同的消息类型和订阅id调用Subscribe两次(即上面黄底组合内容相同),你将会创建两个消费者去消费同一个队列。然后RabbitMQ将依次向每个消费者轮转方式发送后续消息。这对于扩展和工作分配非常有用。比如说,你创建了一个处理特殊消息的服务(做消费者),但是他已经超负荷工作了(假如消息/工作处理很耗时)。简单的创建几个新的服务实例(即增加消费者),无论在同一个机器上,还是不同的机器上,不用配置任何东西,你自动就得到了伸缩性。同理,如果消费者大部分时间很空闲,也可以关掉几个消费者。。这也就是分布式服务。

假如相同的消息类型,用不同的订阅id调用了两次Subscribe,你将创建两个队列,每一个队列有自己的消费者。每一个消息的副本将会路由到每个队列,因此不同的消费者都将得到所有消息(这个类型的)。假如你有几个不同的服务都关心相同类型的消息,这样很棒。

写订阅回调委托时的注意事项

消费者通过EasyNetQ订阅后,每当它从RabbitMQ消息队列接收到消息,消息就被放置在消费者的内存队列中。

EasyNetQ会创建单个线程循环从内存队列读取消息,调用你之前注册的委托方法(处理消息)。因为是在单个线程上,委托一次只能处理一个消息,所以你要避免长时间地同步IO操作。你应该尽快从委托返回控制。

使用异步订阅 SubscribeAsync

SubscribeAsync方法允许你的订阅者委托到一个异步方法,它能立即返回Task,然后异步地执行长时间IO操作。一旦长时间运行的订阅完成后,就简单的完成这个任务。

在下面的例子中,我们使用一个异步IO操作(即DownloadStringTask)请求一个web service。当这个task完成时,写一行信息到控制台。

1 bus.SubscribeAsync<MyMessage>("subscribe_async_test", message => 
2        new WebClient().DownloadStringTask(new Uri("http://localhost:1338/?timeout=500"))
3            .ContinueWith(task => 
4                 Console.WriteLine("Received:'{0}',Downloaded:'{1}'",
5                     message.Text,
6                     task.Result)))

下一个列子是如果有错误发生,返回结果会有异常抛出,那么消息将会被放到一个默认的错误队列中。

 1 _bus.SubscribeAsync<MessageType>("Queue_Identifier", 
 2          message => Task.Factory.StartNew(() => 
 3          {
 4              //这里执行一些操作
 5              //如果这里有一个异常,那么在这个Task执行完毕后,这个异常会作为结果返回,
 6              // 然后任务将继续执行下去。
 7          }).ContinueWith(task => 
 8          {
 9              if ( task.IsCompleted && ! task.IsFaulted)
10              {
11                  // 一切工作正常时
12              }
13              else
14              {
15                  // 这里不要Catch 异常,否则异常会进一步被嵌套,results结果会被发送到RabbitMQ上默认的错误队列
16                  throw new EasyNetQException("Message processing exception - look in t  the default error quenue(broker)");
17              }
18          }));

 


撤销订阅

所有的Subscribe订阅方法都会返回一个ISubscriptionResult接口实例。它包含返回IExchange和IQueue的属性(实际在底层是通过IConsumer实现的),如果需要还可以使用高级API IAdvancededBus进一步操作这些属性。

你可以在任何时间撤销一个订阅者,通过调用ISubscriptionResult实例上的Dispose方法,或者在它之上的 ConsumerCancellation属性。

var subscriptionResult = bus.Subscribe<MyMessage>("sub_id", MyHandler);

...

subscriptionResult.Dispose();
// 这个等价与 subscriptionResult.ConsumerCancellation.Dispose();

这将停止EasyNetQ对队列的消费,并且关闭这个消费者的channel。

注意:上面代码不同于IBus和IAndvancedBus的dispose方法,后两者会撤销所有消费者,并关闭与RabbitMQ的连接。

不要在消息处理(委托方法)中调用 subscriptionResult.Dispose()。这将在EasyNetQ ack确认消息和subscriptionResult.Dispose()调用关闭Channel之间产生一个竞争。(即到底会先ack确认?还是先关闭信道?)因为在EasyNetQ的内部架构中,这两件事是在不同的线程上被执行,所以在时间上存在不确定性。

英文地址:https://github.com/EasyNetQ/EasyNetQ/wiki/Subscribe

posted on 2017-12-04 17:50  困兽斗  阅读(2407)  评论(0编辑  收藏  举报

导航