五、RabbitMQ在net6的基本用法
一、RabbitMQ概念
1.概念
RabbitMQ是一种消息中间件,它是在AMQP基础上实现的,erlang语言编写与可复用的企业消息系统。RabbitMQ主要作用分为三个:异步,削峰,解耦;可以简单的理解为,mq就是驿站,快递员把包裹放进驿站,而你负责去取包裹;
2.名称介绍
2.1.broker:接受和分发消息的应用;可交RabbitMQ service或者消息实体
2.2.virtual host:出于多租户和安全因素设计,把AMQP的基本组件划分一个虚拟的分组中,类似于网络中的namespace概念。
2.3.connection:是生产者/消费者与broker直接的tcp连接
2.4.channel:如果每次访问都简历RabbitMQ连接,在消息量大的时候简历tcp 连接开销巨大,效率也低。channel是在connection内部简历的逻辑连接。如果应用程序支持多线程,通常每股thread创建单独的channel进行通信,而且是隔离的;
2.5.exchange:消息到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发给消息到queue中。
2.6.queue:消息最终被送到这里等待消费者取走
2.7.binding:exchange和queue之间的虚拟连接,binging中科院包含routing key,bingding信息被保存到exchange中的查询表,用于消息的分发依据。
3.安装rabbit
自行去官网执行命令即可。
4.net6引入组件
RabbitMQ.Client组件
二、RabbitMq在net6中的实现
1.直连交换机
直连交换机是根据消息携带的路由键(routing key)将消息投递给对应队列的。直连交换机用来处理消息的单播路由;它是如何工作的:
创建队列,但不需要创建交换机,会自动绑定到默认的交换机上,同时赋予(routing key)

1 public void Send() 2 { 3 var rounteKey = "test"; 4 int[] intlist = new int[] { 1, 2, 3,4,5,6,7,8,9 }; 5 var msg = ""; 6 using (var connection= RabbitMQHelper.GetConnection()) 7 { 8 using (var channel =connection.CreateModel())//创建信道,通信管道 9 { 10 //创建队列 11 channel.QueueDeclare(rounteKey, false, false, false, null); 12 //没绑定交换机,但由于rabbitmq有默认交换机 13 foreach(var row in intlist) 14 { 15 string message = row.ToString(); 16 var body = Encoding.UTF8.GetBytes(message); 17 msg += message+","; 18 channel.BasicPublish("", rounteKey, null, body); 19 } 20 21 } 22 } 23 }

public static void Consumption() { var rounteKey = "test"; using (var connection = RabbitMQHelper.GetConnection()) { using (var channel = connection.CreateModel())//创建信道,通信管道 { //创建队列 channel.QueueDeclare(rounteKey, false, false, false, null); while (true) { //没绑定交换机,但由于rabbitmq有默认交换机 var consumer = new EventingBasicConsumer(channel); channel.BasicQos(0, 1, false);//设置消费者权重 consumer.Received += (model, ea) => { var message = ""; message = Encoding.UTF8.GetString(ea.Body.ToArray()); Console.WriteLine(message); }; channel.BasicConsume(rounteKey, true, consumer); Thread.Sleep(1000); } } } }
2.工作模式(workQueue)
在simple模式下只有一个生产者和消费者,当生产者消息速度大于消费者时,我们可以增加一个或多个消费者来增加消费速度。消费者权重可以通过BasicQos函数来设置。
3.扇形交换机(fanout exch routing)
扇型交换机将消息路由给绑定到他身上的队列,而不理会绑定的路由键。扇型交换机处理消息的广播路由(broadcast routing)。
应用案例:
1.新闻网站用它来近乎实时将实时新闻分发给客户端
2.分发系统使用它来广播各种状态和配置更新

public void send() { using (var connection = RabbitMQHelper.GetConnection()) { using (var channel = connection.CreateModel())//创建信道,通信管道 { //声明交换机 channel.ExchangeDeclare("fanout_exchange", "fanout"); //创建队列 var queue = "fanout1"; channel.QueueDeclare(queue, false, false, false, null); var queue2 = "fanout2"; channel.QueueDeclare(queue2, false, false, false, null); var queue3 = "fanout3"; channel.QueueDeclare(queue3, false, false, false, null); //绑定交换机 channel.QueueBind(queue, "fanout_exchange",""); channel.QueueBind(queue2, "fanout_exchange", ""); channel.QueueBind(queue3, "fanout_exchange", ""); //没绑定交换机,但由于rabbitmq有默认交换机 for (var i=0;i<10;i++) { string message = "测试1"+i; var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish("fanout_exchange", "", null, body); } } } }

public static void Consumption() { using (var connection = RabbitMQHelper.GetConnection()) { using (var channel = connection.CreateModel())//创建信道,通信管道 { channel.ExchangeDeclare("fanout_exchange", "fanout"); //创建队列 //创建队列 var queue = "fanout1"; channel.QueueDeclare(queue, false, false, false, null); var queue2 = "fanout2"; channel.QueueDeclare(queue2, false, false, false, null); var queue3 = "fanout3"; channel.QueueDeclare(queue3, false, false, false, null); //绑定交换机 channel.QueueBind(queue, "fanout_exchange", ""); channel.QueueBind(queue2, "fanout_exchange", ""); channel.QueueBind(queue3, "fanout_exchange", ""); while (true) { //没绑定交换机,但由于rabbitmq有默认交换机 var consumer = new EventingBasicConsumer(channel); consumer.Received += (model, ea) => { var message = Encoding.UTF8.GetString(ea.Body.ToArray()); Console.WriteLine(message); }; channel.BasicConsume(queue, true, consumer); Thread.Sleep(1000); } } } }
4.完全匹配模式(direct)
跟扇型交换机差不多的原理只是会绑定routingKey作为完全匹配。
应用案例:
1.针对不同用户级别广播消息

public void send() { using (var connection = RabbitMQHelper.GetConnection()) { using (var channel = connection.CreateModel())//创建信道,通信管道 { //声明交换机 channel.ExchangeDeclare("direct_exchange", "direct"); //创建队列 var queue = "directtest1"; channel.QueueDeclare(queue, false, false, false, null);//durable等于true时就是消息持久化,用户重启不丢消息 var queue2 = "directtest2"; channel.QueueDeclare(queue2, false, false, false, null); var queue3 = "directtest3"; channel.QueueDeclare(queue3, false, false, false, null); //绑定交换机,指定routingKey string[] routingKeyList = new string[] { "red", "gre", "yell" }; channel.QueueBind(queue, "direct_exchange", routingKeyList[0]); channel.QueueBind(queue2, "direct_exchange", routingKeyList[1]); channel.QueueBind(queue3, "direct_exchange", routingKeyList[2]); //没绑定交换机,但由于rabbitmq有默认交换机 for (var i = 0; i < 10; i++) { foreach(var row in routingKeyList) { //指定发送routingKey string message = "测试1" + i; var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish("direct_exchange", row, null, body); } } } } }

public static void Consumption() { using (var connection = RabbitMQHelper.GetConnection()) { using (var channel = connection.CreateModel())//创建信道,通信管道 { channel.ExchangeDeclare("direct_exchange", "direct"); //创建队列 //创建队列 var queue = "directtest3"; channel.QueueDeclare(queue, false, false, false, null); //var queue2 = "test2"; //channel.QueueDeclare(queue2, false, false, false, null); //var queue3 = "test3"; //channel.QueueDeclare(queue3, false, false, false, null); //绑定交换机 string[] routingKeyList = new string[] { "yel1l" }; foreach(var row in routingKeyList) { channel.QueueBind(queue, "direct_exchange", row); } //channel.QueueBind(queue2, "direct_exchange", ""); //channel.QueueBind(queue3, "direct_exchange", ""); while (true) { //没绑定交换机,但由于rabbitmq有默认交换机 var consumer = new EventingBasicConsumer(channel); consumer.Received += (model, ea) => { var message = Encoding.UTF8.GetString(ea.Body.ToArray()); Console.WriteLine(message); //消息完成后需要手动签收消息,如果不写下面代码,容易导致重复消费 channel.BasicAck(ea.DeliveryTag,true);//可以降低每次签收的性能损耗 }; //消息签收模式 //手动签收:false 避免消息丢失 //自动签收:true 容易丢消息 channel.BasicConsume(queue, autoAck: false, consumer); Thread.Sleep(1000); } } } }
5.主题交换机(topic exchanges)
主题交换机通过对消息的路由键和队列到交换机的绑定模式之间的匹配。将消息路由给一个或多个队列。#表示匹配所有字符,例如:red#,就会匹配red.tes.few等
而*表示匹配单个单词,例如red.*会匹配red.test;但不会匹配red.test.one;
使用案例:
1.分发给关于指定地理位置的数据,例如销售点。
2.给不同的客户标签发送相关消息邮件或短信。

public void send() { using (var connection = RabbitMQHelper.GetConnection()) { using (var channel = connection.CreateModel())//创建信道,通信管道 { //声明交换机 channel.ExchangeDeclare("topic_exchange", "topic"); //创建队列 var queue = "topic"; channel.QueueDeclare(queue, false, false, false, null);//durable等于true时就是消息持久化,用户重启不丢消息 var queue2 = "topic2"; channel.QueueDeclare(queue2, false, false, false, null); var queue3 = "topic3"; channel.QueueDeclare(queue3, false, false, false, null); //绑定交换机,指定routingKey string[] routingKeyList = new string[] { "red.*", "gre.f", "yell.w" };//routingKey在匹配符模式下,可以根据匹配符来发送消息;*代表一个单词,a.*可以是a.b;但不可以为a.b.c;#代表所有,例如a.#,可以是a.b或者a.b.c channel.QueueBind(queue, "topic_exchange", routingKeyList[0]); channel.QueueBind(queue2, "topic_exchange", routingKeyList[1]); channel.QueueBind(queue3, "topic_exchange", routingKeyList[2]); //没绑定交换机,但由于rabbitmq有默认交换机 for (var i = 0; i < 10; i++) { //指定发送routingKey string message = "测试1" + i; var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish("topic_exchange", "gre.f", null, body);//这里的routingKey必须为全匹配 } } } }

public static void Consumption() { using (var connection = RabbitMQHelper.GetConnection()) { using (var channel = connection.CreateModel())//创建信道,通信管道 { channel.ExchangeDeclare("topic_exchange", "topic"); //创建队列 //创建队列 var queue = "topic"; channel.QueueDeclare(queue, false, false, false, null); //var queue2 = "test2"; //channel.QueueDeclare(queue2, false, false, false, null); //var queue3 = "test3"; //channel.QueueDeclare(queue3, false, false, false, null); //绑定交换机 string[] routingKeyList = new string[] { "yel1l.*" };//消费者只会根据queue来消费,所以这里设置routing不起作用,原因是个bug foreach (var row in routingKeyList) { channel.QueueBind(queue, "topic_exchange", row); } //channel.QueueBind(queue2, "direct_exchange", ""); //channel.QueueBind(queue3, "direct_exchange", ""); while (true) { //没绑定交换机,但由于rabbitmq有默认交换机 var consumer = new EventingBasicConsumer(channel); consumer.Received += (model, ea) => { var message = Encoding.UTF8.GetString(ea.Body.ToArray()); Console.WriteLine(message); //消息完成后需要手动签收消息,如果不写下面代码,容易导致重复消费 channel.BasicAck(ea.DeliveryTag, true);//可以降低每次签收的性能损耗 }; //消息签收模式 //手动签收:false 避免消息丢失 //自动签收:true 容易丢消息 channel.BasicConsume(queue, autoAck: false, consumer); Thread.Sleep(1000); } } } }
三、RabbiteMQ高级用法
1.死信队列
简单的理解为:如果消息最终进入了死信队列,则可以通过RocketMQ提供的相关接口从死信队列获取到相应的消息,保证了消息消费的可靠性。
原理:Q1队列绑定了x-dead-letter-exchange(死信交换机)为X2,x-dead-letter-routing-key(死信路由key)指向Q2(队列2)
P(生产者)发送消息经X1(交换机1)路由到Q1(队列1),Q1的消息触发特定情况,自动把消息经X2(交换机2)路由到Q2(队列2),C(消费者)直接消息Q2的消息。
特定情况有哪些呢:
1.消息被拒(basic.reject or basic.nack)并且没有重新入队(requeue=false);
2.当前队列中的消息数量已经超过最大长度(创建队列时指定" x-max-length参数设置队列最大消息数量)。
3.消息在队列中过期,即当前消息在队列中的存活时间已经超过了预先设置的TTL(Time To Live)时间;

public void send() { //死信交换机 string dlxexChange = "dlx.exchange"; //死信队列 string dlxQueueName = "dlx.queue"; //消息交换机 string exchange = "direct-exchange"; //消息队列 string queueName = "queue_a"; using (var connection = RabbitMQHelper.GetConnection()) { using (var channel = connection.CreateModel()) { //创建死信交换机 channel.ExchangeDeclare(dlxexChange, type: ExchangeType.Direct, durable: true, autoDelete: false); //创建死信队列 channel.QueueDeclare(dlxQueueName, durable: true, exclusive: false, autoDelete: false); //死信队列绑定死信交换机 channel.QueueBind(dlxQueueName, dlxexChange, routingKey: dlxQueueName); // 创建消息交换机 channel.ExchangeDeclare(exchange, type: ExchangeType.Direct, durable: true, autoDelete: false); //创建消息队列,并指定死信队列 channel.QueueDeclare(queueName, durable: true, exclusive: false, autoDelete: false, arguments: new Dictionary<string, object> { { "x-dead-letter-exchange",dlxexChange}, //设置当前队列的DLX(死信交换机) { "x-dead-letter-routing-key",dlxQueueName}, //设置DLX的路由key,DLX会根据该值去找到死信消息存放的队列 }); //消息队列绑定消息交换机 channel.QueueBind(queueName, exchange, routingKey: queueName); string message = "hello rabbitmq message"; var properties = channel.CreateBasicProperties(); properties.Persistent = true; //发布消息 channel.BasicPublish(exchange: exchange, routingKey: queueName, basicProperties: properties, body: Encoding.UTF8.GetBytes(message)); Console.WriteLine($"向队列:{queueName}发送消息:{message}"); } } }

public static void Consumption() { //死信交换机 string dlxexChange = "dlx.exchange"; //死信队列 string dlxQueueName = "dlx.queue"; //消息交换机 string exchange = "direct-exchange"; //消息队列 string queueName = "queue_a"; var connection = RabbitMQHelper.GetConnection(); { //创建信道 var channel = connection.CreateModel(); { //创建死信交换机 channel.ExchangeDeclare(dlxexChange, type: ExchangeType.Direct, durable: true, autoDelete: false); //创建死信队列 channel.QueueDeclare(dlxQueueName, durable: true, exclusive: false, autoDelete: false); //死信队列绑定死信交换机 channel.QueueBind(dlxQueueName, dlxexChange, routingKey: dlxQueueName); // 创建消息交换机 channel.ExchangeDeclare(exchange, type: ExchangeType.Direct, durable: true, autoDelete: false); //创建消息队列,并指定死信队列 channel.QueueDeclare(queueName, durable: true, exclusive: false, autoDelete: false, arguments: new Dictionary<string, object> { { "x-dead-letter-exchange",dlxexChange}, //设置当前队列的DLX { "x-dead-letter-routing-key",dlxQueueName}, //设置DLX的路由key,DLX会根据该值去找到死信消息存放的队列 }); //消息队列绑定消息交换机 channel.QueueBind(queueName, exchange, routingKey: queueName); var consumer = new EventingBasicConsumer(channel); channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: true); consumer.Received += (model, ea) => { //处理业务 var message = Encoding.UTF8.GetString(ea.Body.ToArray()); Console.WriteLine($"队列{queueName}消费消息:{message},不做ack确认"); //channel.BasicAck(ea.DeliveryTag, false); //不ack(BasicNack),且不把消息放回队列(requeue:false) channel.BasicNack(ea.DeliveryTag, false, requeue: false); }; channel.BasicConsume(queueName, autoAck: false, consumer); } } }
2.延时队列
延时队列其实也是配合死信队列一起用,其实就是上面死信队列的第二中情况。给队列添加消息过时时间(TTL),变成延时队列。可以理解为,给消息加上过期时间。消息到期后就进入私信队列中。
解决问题场景:像商城下单,未支付时取消订单场景。下单时写一条记录入Q1,延时30分钟后转到Q2,消费Q2,检查订单,支付则不做操作,没支付则取消订单,恢复库存。

1 public void send() 2 { 3 //死信交换机 4 string dlxexChange = "dlx.exchange"; 5 //死信队列 6 string dlxQueueName = "dlx.queue"; 7 8 //消息交换机 9 string exchange = "direct-exchange"; 10 //消息队列 11 string queueName = "delay_queue"; 12 13 using (var connection = RabbitMQHelper.GetConnection()) 14 { 15 using (var channel = connection.CreateModel()) 16 { 17 //创建死信交换机 18 channel.ExchangeDeclare(dlxexChange, type: ExchangeType.Direct, durable: true, autoDelete: false); 19 //创建死信队列 20 channel.QueueDeclare(dlxQueueName, durable: true, exclusive: false, autoDelete: false); 21 //死信队列绑定死信交换机 22 channel.QueueBind(dlxQueueName, dlxexChange, routingKey: dlxQueueName); 23 24 // 创建消息交换机 25 channel.ExchangeDeclare(exchange, type: ExchangeType.Direct, durable: true, autoDelete: false); 26 //创建消息队列,并指定死信队列,和设置这个队列的消息过期时间为10s 27 channel.QueueDeclare(queueName, durable: true, exclusive: false, autoDelete: false, arguments: 28 new Dictionary<string, object> { 29 { "x-dead-letter-exchange",dlxexChange}, //设置当前队列的DLX(死信交换机) 30 { "x-dead-letter-routing-key",dlxQueueName}, //设置DLX的路由key,DLX会根据该值去找到死信消息存放的队列 31 { "x-message-ttl",10000} //设置队列的消息过期时间 32 }); 33 //消息队列绑定消息交换机 34 channel.QueueBind(queueName, exchange, routingKey: queueName); 35 36 string message = "hello rabbitmq message"; 37 var properties = channel.CreateBasicProperties(); 38 properties.Persistent = true; 39 //发布消息 40 channel.BasicPublish(exchange: exchange, 41 routingKey: queueName, 42 basicProperties: properties, 43 body: Encoding.UTF8.GetBytes(message)); 44 Console.WriteLine($"{DateTime.Now},向队列:{queueName}发送消息:{message}"); 45 } 46 } 47 }

1 public static void Consumption() 2 { 3 //死信交换机 4 string dlxexChange = "dlx.exchange"; 5 //死信队列 6 string dlxQueueName = "dlx.queue"; 7 var connection = RabbitMQHelper.GetConnection(); 8 { 9 //创建信道 10 var channel = connection.CreateModel(); 11 { 12 //创建死信交换机 13 channel.ExchangeDeclare(dlxexChange, type: ExchangeType.Direct, durable: true, autoDelete: false); 14 //创建死信队列 15 channel.QueueDeclare(dlxQueueName, durable: true, exclusive: false, autoDelete: false); 16 //死信队列绑定死信交换机 17 channel.QueueBind(dlxQueueName, dlxexChange, routingKey: dlxQueueName); 18 19 var consumer = new EventingBasicConsumer(channel); 20 channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: true); 21 consumer.Received += (model, ea) => 22 { 23 //处理业务 24 var message = Encoding.UTF8.GetString(ea.Body.ToArray()); 25 Console.WriteLine($"{DateTime.Now},队列{dlxQueueName}消费消息:{message}"); 26 channel.BasicAck(ea.DeliveryTag, false); 27 }; 28 channel.BasicConsume(dlxQueueName, autoAck: false, consumer); 29 } 30 } 31 32 }
总结:
mq目前应用广泛,而且是针对写操作的,所以下一节我们来实操分布式事务
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)