新的场景#
在我们学习了RabbitMQ的发布与订阅之后,我们很容易就可以完成一个简单的消息群发器。
使用这个消息群发器,所有的消费者程序实例都会接收到相同的消息信息,从而实现广播的效果。
但是这种广播是一种无意识的广播,即使消息是有分类的,消费者程序也不能自己决定关注的消息类型,只能被动的接收所有的消息,这就导致消费者程序收到许多无用的消息。
如果想要消费者程序自主决定关注的消息类型,我们可以使用RabbitMQ的Routing机制。
Binding#
之前学习RabbitMQ的发布与订阅,我们使用如下代码将消息队列和Exchange进行绑定。
1
|
channel.QueueBind(queue: queueName, exchange:
"broadcast"
, routingKey:
""
);
|
其中有一个参数是routingKey, 我们将它设置为空。
对于fanout类型的Exchange, 在绑定消息队列和Exchange的时候,会自动忽略routingKey。
但是Direct类型的Exchange使用这个参数可以帮助我们对消息进行的分类,这里我们可以简单的认为routingKey就是一种消息类型
Direct Exchange#
Direct的路由算法很简单,它会将消息发送到与Direct Exchange绑定并拥有指定routingKey的消息队列中。
一个消息队列与一个Direct Exchange绑定的时候,可以通过多次绑定,拥有多个routingKey。
同理,一个routingKey也可以被多个消息队列拥有。
例:
1
2
3
|
channel.QueueBind(queue: queueName, exchange:
"broadcast"
, routingKey:
"key1"
);
channel.QueueBind(queue: queueName, exchange:
"broadcast"
, routingKey:
"key2"
);
|
在发布与订阅中,我们使用生产者程序向Fanout Exchange发送消息的时候,编写了如下代码
1
2
3
4
5
6
7
8
9
|
channel.BasicPublish(exchange:
"broadcast"
,
routingKey:
""
,
basicProperties:
null
,
body: body
);
|
其中routingKey为空, 因为Fanout Exchange并不关心routingKey。
现在我们使用Direct Exchange, 我们需要指定消息类型routingKey。当Direct Exchnage接收到这条消息之后,它查找和他绑定的所有消息队列,如果消息队列拥有这个routingKey,Direct Exchange就会将这条消息传输给它, 这样监听该消息队列的消费者程序就可以接收到这条消息。
修改代码#
Send#
- Send可以发送3种类型的消息, game, sport, music
- 发送消息时必须指定消息的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
static
string
[] availableTypes = {
"game"
,
"music"
,
"sport"
};
static
void
Main(
string
[] args)
{
if
(args ==
null
|| args.Length == 0)
{
Console.WriteLine(
"This command line should be like 'dotnet run [message type] [message].'"
);
Environment.Exit(1);
}
var
messageType = args[0];
if
(!availableTypes.Contains(messageType))
{
Console.WriteLine(
"Available message type can be 'game','music','sport'."
);
Environment.Exit(1);
}
var
factory =
new
ConnectionFactory()
{
HostName =
"localhost"
};
using
(
var
connection = factory.CreateConnection())
{
using
(
var
channel = connection.CreateModel())
{
channel.ExchangeDeclare(
"broadcast"
,
"direct"
);
string
message = $
"{args[0]} news: {args[1]}"
;
var
body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange:
"broadcast"
,
routingKey: messageType,
basicProperties:
null
,
body: body
);
Console.WriteLine(
"[x] Sent {0}"
, message);
}
}
}
|
Receive#
- Receive可以接收3种类型的消息, game, sport, music
- 程序启动时指定接收的消息类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
static
string
[] availableTypes = {
"game"
,
"music"
,
"sport"
};
static
void
Main(
string
[] args)
{
if
(args ==
null
|| args.Length == 0)
{
Console.WriteLine(
"This command line should be like 'dotnet run [message type 1] [message type 2] [message type 3].'"
);
Environment.Exit(1);
}
foreach
(
var
type
in
args)
{
if
(!availableTypes.Contains(type))
{
Console.WriteLine(
"Available message type can be 'game','music','sport'."
);
Environment.Exit(1);
}
}
var
factory =
new
ConnectionFactory() { HostName =
"localhost"
};
using
(
var
connection = factory.CreateConnection())
{
using
(
var
channel = connection.CreateModel())
{
channel.ExchangeDeclare(
"broadcast"
,
"direct"
);
var
queueName = channel.QueueDeclare().QueueName;
foreach
(
var
type
in
args)
{
channel.QueueBind(queue: queueName, exchange:
"broadcast"
, routingKey: type);
}
var
consumer =
new
EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var
body = ea.Body;
var
message = Encoding.UTF8.GetString(body);
Console.WriteLine(
"[x] Received {0}"
, message);
};
channel.BasicConsume(queue: queueName, autoAck:
true
, consumer: consumer);
Console.Read();
}
}
}
|
最终效果#
启动1个Send, 2个Receive。
一个Receive关注game, music类型的消息。
一个Receive关注sport, game类型的消息。