本章目标

  • 理解主题交换机(Topic Exchange)的强大路由能力。

  • 掌握通配符*#的使用规则。

  • 学习基于模式匹配的复杂消息路由。

  • 实现一个支持多维度过滤的智能消息系统。


一、理论部分

1. 主题交换机(Topic Exchange)简介

主题交换机是RabbitMQ中最灵活也是最强大的交换机类型。它结合了扇形交换机的广播能力和直连交换机的精确匹配能力,同时引入了模式匹配的概念。

主题交换机的工作方式:

  • 消息仍然带有路由键(Routing Key),但路由键必须是由点号分隔的单词列表(如:usa.newseurope.weather.alert)。

  • 队列通过绑定键(Binding Key) 绑定到交换机,绑定键也使用相同的点号分隔格式。

  • 绑定键支持两种通配符进行模式匹配。

2. 通配符规则

主题交换机的强大之处在于绑定键支持通配符:

  • *(星号):匹配恰好一个单词

    • 示例:*.orange.* 可以匹配 quick.orange.rabbit,但不能匹配 quick.orange.fox.jumps

  • #(井号):匹配零个或多个单词

    • 示例:lazy.# 可以匹配 lazylazy.foxlazy.brown.foxlazy.pink.fox.jumps.over

3. 路由键格式最佳实践

路由键通常采用层次结构,便于模式匹配:

  • <facility>.<severity>auth.infokernel.error

  • <region>.<service>.<event>usa.payment.successeurope.order.cancelled

  • <category>.<subcategory>.<action>news.sports.updateweather.alert.severe

4. 使用场景

主题交换机适用于需要复杂、灵活的消息路由场景:

  • 新闻订阅系统:用户可以根据兴趣订阅特定主题(如sports.**.finance

  • 物联网设备监控:按设备类型、地理位置、告警级别路由消息

  • 微服务事件总线:基于事件类型和来源进行精细路由


二、实操部分:构建智能新闻分发系统

我们将构建一个新闻分发系统,其中:

  • 生产者发送带有分类路由键的新闻消息

  • 消费者可以根据兴趣订阅特定模式的新闻

第1步:创建项目

  1. 创建一个新的解决方案。

  2. 添加一个控制台应用程序项目作为生产者:EmitLogTopic

  3. 添加多个消费者项目:

    • ReceiveNewsAll - 接收所有新闻

    • ReceiveSportsNews - 接收所有体育新闻

    • ReceiveUSNews - 接收所有美国新闻

    • ReceiveCriticalAlerts - 接收所有紧急警报

    • ReceiveWeatherUpdates - 接收所有天气更新

  4. 为所有项目添加RabbitMQ.Client NuGet包。

第2步:编写新闻生产者(EmitLogTopic.cs)

using System.Text;
using RabbitMQ.Client; var factory = new ConnectionFactory() { HostName = "localhost", UserName = "myuser", Password = "mypassword" }; using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
// 声明主题交换机
channel.ExchangeDeclare(exchange: "topic_logs", type: ExchangeType.Topic); // 路由键格式:<category>.<region>.<severity>
// 示例:news.usa.info, sports.europe.alert, weather.asia.critical
var routingKey = (args.Length > 0) ? args[0] : "anonymous.info";
var message = (args.Length > 1) ? string.Join(" ", args.Skip(1).ToArray()) : "Hello World!";
var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish(exchange: "topic_logs",
routingKey: routingKey,
basicProperties: null,
body: body); Console.WriteLine($" [x] Sent '{routingKey}':'{message}'");
} Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();

第3步:编写接收所有新闻的消费者(ReceiveNewsAll.cs)

using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory() { HostName = "localhost", UserName = "myuser", Password = "mypassword" }; using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "topic_logs", type: ExchangeType.Topic);
var queueName = channel.QueueDeclare().QueueName; // 使用 # 匹配所有消息
channel.QueueBind(queue: queueName, exchange: "topic_logs", routingKey: "#"); Console.WriteLine($" [*] Waiting for ALL news. Queue: {queueName}"); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($" [x] [ALL] Received '{ea.RoutingKey}':'{message}'");
}; channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}

第4步:编写接收体育新闻的消费者(ReceiveSportsNews.cs)

using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory() { HostName = "localhost", UserName = "myuser", Password = "mypassword" }; using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "topic_logs", type: ExchangeType.Topic);
var queueName = channel.QueueDeclare().QueueName; // 匹配所有体育相关的新闻:sports.*.*
channel.QueueBind(queue: queueName, exchange: "topic_logs", routingKey: "sports.#"); Console.WriteLine($" [*] Waiting for SPORTS news. Queue: {queueName}"); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($" [x] [SPORTS] Received '{ea.RoutingKey}':'{message}'");
}; channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}

第5步:编写接收美国新闻的消费者(ReceiveUSNews.cs)

using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory() { HostName = "localhost", UserName = "myuser", Password = "mypassword" }; using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "topic_logs", type: ExchangeType.Topic);
var queueName = channel.QueueDeclare().QueueName; // 匹配所有美国相关的新闻:*.usa.*
channel.QueueBind(queue: queueName, exchange: "topic_logs", routingKey: "*.usa.*"); Console.WriteLine($" [*] Waiting for USA news. Queue: {queueName}"); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($" [x] [USA] Received '{ea.RoutingKey}':'{message}'");
}; channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}

第6步:编写接收紧急警报的消费者(ReceiveCriticalAlerts.cs)

using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory() { HostName = "localhost", UserName = "myuser", Password = "mypassword" }; using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "topic_logs", type: ExchangeType.Topic);
var queueName = channel.QueueDeclare().QueueName; // 匹配所有紧急级别的消息:*.*.critical
channel.QueueBind(queue: queueName, exchange: "topic_logs", routingKey: "*.*.critical"); Console.WriteLine($" [*] Waiting for CRITICAL alerts. Queue: {queueName}"); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($" [x] [CRITICAL] Received '{ea.RoutingKey}':'{message}'");
Console.WriteLine(" -> Sending emergency notification!");
}; channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}

第7步:编写接收天气更新的消费者(ReceiveWeatherUpdates.cs)

using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory() { HostName = "localhost", UserName = "myuser", Password = "mypassword" }; using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "topic_logs", type: ExchangeType.Topic);
var queueName = channel.QueueDeclare().QueueName; // 匹配所有天气相关的更新:weather.*
// 一个队列可以绑定多个模式
channel.QueueBind(queue: queueName, exchange: "topic_logs", routingKey: "weather.#");
channel.QueueBind(queue: queueName, exchange: "topic_logs", routingKey: "*.alert"); // 也接收所有警报 Console.WriteLine($" [*] Waiting for WEATHER updates and ALERTS. Queue: {queueName}"); var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine($" [x] [WEATHER/ALERT] Received '{ea.RoutingKey}':'{message}'");
}; channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}

第8步:运行与演示

  1. 启动所有消费者
    打开六个终端窗口,分别运行所有消费者程序。

  2. 发送各种类型的新闻消息

    cd EmitLogTopic
    
    # 发送体育新闻
    dotnet run "sports.usa.score" "Team USA wins gold medal"
    dotnet run "sports.europe.update" "Champions League finals scheduled" # 发送美国相关新闻
    dotnet run "news.usa.politics" "Election results announced"
    dotnet run "tech.usa.innovation" "Silicon Valley startup raises $10M" # 发送紧急警报
    dotnet run "weather.usa.critical" "Tornado warning for Midwest"
    dotnet run "safety.europe.critical" "Security alert: System maintenance" # 发送天气更新
    dotnet run "weather.asia.update" "Monsoon season begins"
    dotnet run "news.europe.alert" "Breaking: Major announcement" # 发送其他消息
    dotnet run "entertainment.hollywood.gossip" "Celebrity wedding announced"
  3. 观察路由结果并分析模式匹配

    消息路由键 ALL SPORTS USA CRITICAL WEATHER/ALERT
    sports.usa.score
    sports.europe.update
    news.usa.politics
    tech.usa.innovation
    weather.usa.critical
    safety.europe.critical (*.alert)
    weather.asia.update
    news.europe.alert (*.alert)
    entertainment.hollywood.gossip
  4. 测试复杂场景

    • 发送 weather.alert.severe.critical - 观察哪些消费者能收到

    • 发送 sports.alert - 测试多个模式的匹配

    • 在管理后台查看绑定关系,理解通配符的实际效果

第9步:通配符规则详解示例

为了更好理解通配符,让我们看一些匹配示例:

绑定键 *.orange.* 的匹配情况:

  • quick.orange.rabbit (匹配)

  • lazy.orange.elephant (匹配)

  • quick.orange.fox.lazy (不匹配 - 四个单词)

  • orange (不匹配 - 只有一个单词)

  • quick.brown.fox (不匹配 - 中间不是orange)

绑定键 lazy.# 的匹配情况:

  • lazy (匹配)

  • lazy.fox (匹配)

  • lazy.brown.fox (匹配)

  • lazy.pink.fox.jumps.over (匹配)

  • quick.lazy.fox (不匹配 - 第一个单词不是lazy)


本章总结

在这一章中,我们深入学习了RabbitMQ中最强大的主题交换机,掌握了基于模式匹配的复杂消息路由:

  1. 主题交换机(Topic Exchange):理解了基于通配符的模式匹配路由机制。

  2. 通配符规则:掌握了*(匹配一个单词)和#(匹配零个或多个单词)的使用方法。

  3. 路由键设计:学习了使用点号分隔的层次化路由键设计最佳实践。

  4. 复杂路由场景:实现了支持多维度过滤的智能新闻分发系统。

  5. 多重模式绑定:掌握了单个队列绑定多个模式的高级用法。

主题交换机提供了无与伦比的灵活性,是构建复杂事件驱动系统的理想选择。在下一章,我们将转向另一个重要主题:消息可靠性保障,学习如何确保消息在复杂的分布式环境中绝不丢失。

【RabbitMQ】主题(Topics)与主题交换机(Topic Exchange)的更多相关文章

  1. RabbitMQ (五)主题(Topic) -摘自网络

    虽然使用direct类型改良了我们的系统,但是仍然存在一些局限性:它不能够基于多重条件进行路由选择. 在我们的日志系统中,我们有可能希望不仅根据日志的级别而且想根据日志的来源进行订阅.这个概念类似un ...

  2. RabbitMQ (五)主题(Topic)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37706355 上一篇博客中,我们进步改良了我们的日志系统.我们使用direct类 ...

  3. RabbitMQ 官方NET教程(五)【Topic】

    在上一个教程中,我们改进了我们的日志记录系统.我们使用direct类型转发器,使得接收者有能力进行选择性的接收日志,,而非fanout那样,只能够无脑的转发 虽然使用direct类型改进了我们的系统, ...

  4. RabbitMQ指南之五:主题交换器(Topic Exchange)

    在上一章中,我们完善了我们的日志系统,用direct交换器替换了fanout交换器,使得我们可以有选择性地接收消息.尽管如此,仍然还有限制:不能基于多个标准进行路由.在我们的日志系统中,我们可能不仅希 ...

  5. RabbitMQ入门:主题路由器(Topic Exchange)

    上一篇博文中,我们使用direct exchange 代替了fanout exchange,这次我们来看下topic exchange. 一.Topic Exchange介绍 topic exchan ...

  6. rabbitmq系列五 之主题交换机

    1.主题 在前面的例子中,我们对日志系统进行了改进.使用了direct交换机代替了fanout交换机,从只能盲目的广播消息改进为有可能选择性的接收日志. 尽管直接交换机能够改善我们的日志系统,但是它也 ...

  7. 译:5.RabbitMQ Java Client 之 Topics (主题)

    在 上篇博文 译:4.RabbitMQ 之Routing(路由) 中,我们改进了日志系统. 我们使用的是direct(直接交换),而不是使用只能进行虚拟广播的 fanout(扇出交换) ,并且有可能选 ...

  8. RabbitMQ六种队列模式-主题模式

    前言 RabbitMQ六种队列模式-简单队列RabbitMQ六种队列模式-工作队列RabbitMQ六种队列模式-发布订阅RabbitMQ六种队列模式-路由模式RabbitMQ六种队列模式-主题模式 [ ...

  9. [译]RabbitMQ教程C#版 - 主题

    先决条件 本教程假定 RabbitMQ 已经安装,并运行在localhost标准端口(5672).如果你使用不同的主机.端口或证书,则需要调整连接设置. 从哪里获得帮助 如果您在阅读本教程时遇到困难, ...

  10. PHP 下基于 php-amqp 扩展的 RabbitMQ 简单用例 (二) -- Topic Exchange 和 Fanout Exchange

    Topic Exchange 此模式下交换机,在推送消息时, 会根据消息的主题词和队列的主题词决定将消息推送到哪个队列. 交换机只会为 Queue 分发符合其指定的主题的消息. 向交换机发送消息时,消 ...

随机推荐

  1. centos8安装puppeteer

    背景 我计划使用puppeteer爬点html数据,结果windows11上没问题 但在我的服务器centos8上确报错. [root@104 auto-task]# npm run start &g ...

  2. leetcode 236 而哈数的最近公共祖先

    简介 dfs 找出路劲,然后长链在短链里面找. code /** * Definition for a binary tree node. * struct TreeNode { * int val; ...

  3. leetcode1404

    简介 简单的模拟二进制的加减法 code class Solution1404 { public: int numSteps(string s) { int n = s.size(); int cou ...

  4. 研究生 华为杯数学建模F题

    简介 以作记录不做说明. code https://github.com/lishaohsuai/mathmatic_hw

  5. ETLCloud怎么样?深度解析其在数据管理中的表现

    在BI或数据大屏等数据分析工具中,经常需要从多个业务系统中提取原始数据,然后对数据进行清洗.处理,以获取高质量.有效且干净的数据以供后续的BI进行数据统计和分析使用,从高质量的实现企业数据的价值变现. ...

  6. Restcloud ETL开箱即用-永久免费

    2022年4月18日,国内领先的数据集成企业RestCloud发布了全新的ETL社区版本. RestCloud ETL社区版是一款完全国产化自主研发创新的全WEB化.开箱即用.永久免费的数据集成工具, ...

  7. AppLink让你的电商运营财务管理自动化

    电商运营财务管理的现状 在我们做电商运营管理的时候,财务管理是一个很繁琐并且对精度要求比较高的工作.通常需要花费财务管理人员很长的时间和经理进行核对.企业电商金额大,商品多.个体电商人员少,精力更多在 ...

  8. POLIR-Society-Organization-Psychology-Attitude态度-The Components(成份) of Attitude: Formation(形成) of an Attitude and How It Can Be Changed 如何能改变

    Theories > Social Psychology > The Components of Attitude The Components of Attitude Formation ...

  9. SciTech-Mathmatics-Probability+Statistics-数学专业社区(math.stackexchange.com/questions) + examples of "a set of distributions indexed by a parameter"

    Sampling Distribution Could some give an examples of "a set of distributions indexed by a param ...

  10. SciTech-EE-Virtual Electronics Lab: How to Create an Oscilloscope Using Python and ADALM2000

    https://wiki.analog.com/university/tools/m2k Virtual Electronics Lab: How to Create an Oscilloscope ...