【EasyNetQ笔记】Publish/Subscribe模式(发布订阅)
介绍
EasyNetQ支持的最简单的消息模式是发布/订阅.这个模式是一个极好的方法用来解耦消息提供者和消费者。消息发布者只要简单的对世界说“这里有些事发生” 。它不关心有没有人监听,或者接收者是谁,或者接收者在那里。我们能够添加和移除特定类型的消息的订阅者,不需发布者做任何的重新配置。
假如你开始去发布消息,而没有任何订阅者曾经定义此消息,那么这个消息就简单的消失了。因为发布者不会创建队列
一个EasyNetQ订阅者订阅一种消息类型。通过调用Subcribe
方法一旦对一个类型设置了订阅,一个持久化队列就会在RabbitMQ broker上被创建,这个类型的任何消息都会被发送到这个队列上。订阅者无论什么时候连接上,RabbitMQ都将会将消息从队列中发送给订阅者。
消息发布(Publish
、PublishAsync
)
EasyNetQ支持最简单的消息模式是发布和订阅。发布消息后,任意消费者可以订阅该消息,也可以多个消费者订阅。
var _bus = RabbitHutch.CreateBus("host=xxxxxx;port=5672;virtualHost=my_vhost;username=admin;password=admin;timeout=30;publisherConfirms=true");//连接字符串末尾不要加";"
var message = new MyMessage { Text = "Hello Rabbit" };
_bus.Publish<MyMessage>(message);
消息订阅(Subscribe
、SubscribeAsync
)
EasyNetQ提供了消息订阅,当调用Subscribe
方法时候,EasyNetQ会创建一个用于接收消息的队列,不过与消息发布不同的是,消息订阅增加了一个参数,subscribe_id
_bus.Subscribe<MyMessage>("subscribe_id", myMessage => {
lbxMessage1.Invoke(new Action(() => { lbxMessage1.Items.Add(myMessage.Text); }));
});
第一个参数是订阅id,另外一个是delegate参数,用于处理接收到的消息。
subscribe_id
参数很重要,假如开发者用同一个subscribe_id
订阅了同一种消息类型两次或者多次,RabbitMQ会以轮训的方式给每个订阅的队列发送消息。
//不同的subscribe_id将会创建两个队列,消息同时发给两个队列
private void btn_subscribe11_Click(object sender, EventArgs e)
{
_bus.Subscribe<MyMessage>("", myMessage => {
lbxMessage1.Invoke(new Action(() => { lbxMessage1.Items.Add(myMessage.Text); }));
});
}
private void btnSubscribe22_Click(object sender, EventArgs e)
{
_bus.Subscribe<MyMessage>("s1", myMessage => {
lbxMessage2.Invoke(new Action(() => { lbxMessage2.Items.Add(myMessage.Text); }));
});
}
如果用不同的subscribeid
订阅同一种消息类型,它们都会收到该消息。RabbitMQ根据消息类型和subscribe_id创建队列,不同的subscribe_id
会创建不同的队列。最终生成的队列名是“{queueName}_{subsicribe_id}”,queueName
是消息类型上的Attribute[Queue(queueName:"TestQueue")]
指定
以上案例中生成的队列名是'TestQueue'、TestQueue_s1
;
交换器是根据消息类型创建的,交换器名称是:“{消息类型全名},{所在程序集名称}”,类型是“topic”,绑定是“#”
异步订阅
在收到消息处理消息时候,不要占用太多的时间,会影响消息的处理效率,所以,遇到占用长时间的处理方法,最好用异步处理。代码如下:
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)));
取消订阅
所有subscribe方法都返回一个ISubscriptionResult
。它包含描述底层IConsumer
、IExchange
和IQueue
的属性,如果需要,可以使用高级API进一步操作这些属性。
var subscriptionResult = _bus.Subscribe<MyMessage>("s1", myMessage =>
{
lbxMessage1.Invoke(new Action(() => { lbxMessage1.Items.Add(myMessage.Text); }));
});
subscriptionResult.Dispose();
这将阻止EasyNetQ从队列中消耗并关闭消费者的频道。
非泛型发布订阅
在运行期间,你怎么去发现消息类型?例如:你可能有一些系统加载外部插件,希望能订阅他们自己的消息类型。EasyNetQ为了这个目标提供了非泛型的发布和订阅方法。
加using
using EasyNetQ.NonGeneric;
提供如下非泛型的发布订阅方法:
//订阅
public static IDisposable Subscribe(
this IBus bus,
Type messageType,
string subscriptionId,
Action<object> onMessage,
Action<ISubscriptionConfiguration> configure)
public static IDisposable SubscribeAsync(
this IBus bus,
Type messageType,
string subscriptionId,
Func<object, Task> onMessage,
Action<ISubscriptionConfiguration> configure)
//发布
public static void Publish(
this IBus bus,
Type messageType,
object message,
string topic)
public static Task PublishAsync(
this IBus bus,
Type messageType,
object message,
string topic)
基于Topic路由
基于Topic的路由,允许订阅者基于多个标准过滤消息。订阅时可以通过WithTopic
方法设置binding_key
,这样只有匹配的Routing_key
才会路由到该队列
//===================发布====================
_bus.PublishAsync<MyMessage>(message,"X.A")
//===================订阅====================
private void btnSubscribe_Click(object sender, EventArgs e)
{
_bus.Subscribe<OrderMessage>("s1", myMessage =>
{
lbxMessage1.Invoke(new Action(() => { lbxMessage1.Items.Add(myMessage.Text); }));
}, config => config.WithTopic("*.A"));
}
多态发布和订阅
您可以订阅一个接口,然后发布该接口的实现。
我们来看一个例子。 我有一个接口IAnimal和两个实现猫和狗:
public interface IAnimal
{
string Name { get; set; }
}
public class Cat : IAnimal
{
public string Name { get; set; }
public string Meow { get; set; }
}
public class Dog : IAnimal
{
public string Name { get; set; }
public string Bark { get; set; }
}
我可以订阅IAnimal并获得猫和狗类:
bus.Subscribe<IAnimal>("polymorphic_test", @interface =>
{
var cat = @interface as Cat;
var dog = @interface as Dog;
if (cat != null)
{
Console.Out.WriteLine("Name = {0}", cat.Name);
Console.Out.WriteLine("Meow = {0}", cat.Meow);
}
else if (dog != null)
{
Console.Out.WriteLine("Name = {0}", dog.Name);
Console.Out.WriteLine("Bark = {0}", dog.Bark);
}
else
{
Console.Out.WriteLine("message was not a dog or a cat");
}
});
让我们发布一只猫和一只狗:
var cat = new Cat
{
Name = "Gobbolino",
Meow = "Purr"
};
var dog = new Dog
{
Name = "Rover",
Bark = "Woof"
};
bus.Publish<IAnimal>(cat);
bus.Publish<IAnimal>(dog);
请注意,我必须明确指定我发布IAnimal。 EasyNetQ使用“发布”和“订阅”方法中指定的泛型类型将发布路由到订阅。