NET 5 使用RabbitMQ以及Kafka区别
区别
1.应用场景方面
RabbitMQ:用于实时的,对可靠性要求较高的消息传递上。
kafka:用于处于活跃的流式数据,大数据量的数据处理上。
2.架构模型方面
producer,broker,consumer
RabbitMQ:以broker为中心,有消息的确认机制
kafka:以consumer为中心,无消息的确认机制
3.吞吐量方面
RabbitMQ:支持消息的可靠的传递,支持事务,不支持批量操作,基于存储的可靠性的要求存储可以采用内存或硬盘,吞吐量小。
kafka:内部采用消息的批量处理,数据的存储和获取是本地磁盘顺序批量操作,消息处理的效率高,吞吐量高。
4.集群负载均衡方面
RabbitMQ:本身不支持负载均衡,需要loadbalancer的支持
kafka:采用zookeeper对集群中的broker,consumer进行管理,可以注册topic到zookeeper上,通过zookeeper的协调机制,producer保存对应的topic的broker信息,可以随机或者轮询发送到broker上,producer可以基于语义指定分片,消息发送到broker的某个分片上。
RabbitMQ认识
接收数据的方式是什么呢?自然是端口监听啦。
那消息队列是什么就很好解释了?
它就是端口监听,接到数据后,将数据排列起来。
Publish:生产者(发布消息)
Subscribe:消费者(接收消息)
Channel:信道
Queue:队列名称
Exchange:交换机名称
排他队列
如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,
并在连接断开时自动删除。这里需要注意三点:其一,排他队列是基于连接可见的,同一连接的不同信道是可
以同时访问同一个连接创建的排他队列的。其二,“首次”,如果一个连接已经声明了一个排他队列,其他连
接是不允许建立同名的排他队列的,这个与普通队列不同。其三,即使该队列是持久化的,一旦连接关闭或者
客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景。
信息确认(如何确保消息正确地发送至RabbitMQ)
信息一旦发送到Subscribe中,则该信息就会从队列中移除。一旦中间信息处理异常/失败,Subscribe端程序退出等,都将会导致信息未处理完成,而此时队列中已将信息移除了,那么就会导致一系列的问题。我们可以在Subscribe端设置手动确认信息,从而解决上述问题的发生。
只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。
在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进入队列;
// 发送信息确认信号(手动信息确认) channel.BasicAck(ea.DeliveryTag, false);
信息持久化
消息持久化的前提是:将交换器/队列的durable属性设置为true,表示交换器/队列是持久交换器/队列,在服务器崩溃或重启之后不需要重新创建交换器/队列(交换器/队列会自动创建)。
当RabbitMQ退出或死机时会清空队列和信息。通过将队列和信息标记为持久的,来告知RabbitMQ将信息持久化
durable: true, //标记队列持久
消息基于什么传输
生产者产生了消息,然后发布到 RabbitMQ 服务器,发布之前肯定要先连接上服务器,也就是要在应用程序和rabbitmq 服务器之间建立一条 TCP 连接,一旦连接建立,应用程序就可以创建一条 AMQP 信道。
信道是建立在“真实的”TCP 连接内的虚拟连接,AMQP 命令都是通过信道发送出去的,每条信道都会被指派一个唯一的ID(AMQP库会帮你记住ID的),不论是发布消息、订阅队列或者接收消息,这些动作都是通过信道来完成的。
TCP的创建和销毁开销特别大,创建需要3次握手,销毁需要4次挥手。如果不用信道,那么应用程序就会以TCP连接到RabbitMQ,高峰时每秒成千上万条连接会造成资源的巨大浪费,而且操作系统每秒处理TCP连接数也是有限制的,必定造成性能瓶颈。
信道的原理是一条线程一条信道,多条线程多条信道同用一条TCP连接。一条TCP连接可以容纳无线信道即使每秒成千上万的请求也不会成为性能的瓶颈。
通信过程
假设P1和C1注册了相同的Broker,Exchange和Queue。P1发送的消息最终会被C1消费。基本的通信流程大概如下所示:
P1生产消息,发送给服务器端的Exchange
Exchange收到消息,根据ROUTINKEY,将消息转发给匹配的Queue1
Queue1收到消息,将消息发送给订阅者C1
C1收到消息,发送ACK给队列确认收到消息
Queue1收到ACK,删除队列中缓存的此条消息
注意要点:
Consumer收到消息时需要显式的向rabbit broker发送basic.ack消息或者consumer订阅消息时设置auto_ack参数为true。在通信过程中,队列对ACK的处理有以下几种情况:
如果consumer接收了消息,发送ack,rabbitmq会删除队列中这个消息,发送另一条消息给consumer。
如果cosumer接受了消息, 但在发送ack之前断开连接,rabbitmq会认为这条消息没有被deliver,在consumer在次连接的时候,这条消息会被redeliver。
如果consumer接受了消息,但是程序中有bug,忘记了ack,rabbitmq不会重复发送消息。
rabbitmq2.0.0和之后的版本支持consumer reject某条(类)消息,可以通过设置requeue参数中的reject为true达到目地,那么rabbitmq将会把消息发送给下一个注册的consumer。
1 为什么要使用消息队列?
回答:这个问题,咱只答三个最主要的应用场景(不可否认还有其他的,但是只答三个主要的),即以下六个字:
(1)解耦
传统模式:
传统模式的缺点: 系统间耦合性太强,如上图所示,系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦!
中间件模式:
中间件模式的的优点:
-
将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。
(2)异步
传统模式:
传统模式的缺点:
-
一些非必要的业务逻辑以同步的方式运行,太耗费时间。
中间件模式:
中间件模式的的优点:
-
将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度
(3)削峰
传统模式
传统模式的缺点:
-
并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常
中间件模式:
中间件模式的的优点:
-
系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。
2 使用了消息队列会有什么缺点?
分析:一个使用了MQ的项目,如果连这个问题都没有考虑过,就把MQ引进去了,那就给自己的项目带来了风险。我们引入一个技术,要对这个技术的弊端有充分的认识,才能做好预防。要记住,不要给公司挖坑!
回答:回答也很容易,从以下两个个角度来答
系统可用性降低:你想啊,本来其他系统只要运行好好的,那你的系统就是正常的。现在你非要加个消息队列进去,那消息队列挂了,你的系统不是呵呵了。因此,系统可用性降低
系统复杂性增加:要多考虑很多方面的问题,比如一致性问题、如何保证消息不被重复消费,如何保证保证消息可靠传输。因此,需要考虑的东西更多,系统复杂性增大。
RabbitMQ交换机类型 (4种)
fanout(扇形交换机):
扇形交换机是最基本的交换机类型,它能做的事非常简单——广播消息,扇形交换机会把能接收到的消息全部发送给绑定在自己身上的队列。因为广播不需要"思考",所以扇形交换机处理消息的速度也是所有的交换机类型里面最快的。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。
direct(直连交换机):
直连交换机是一种带路由功能的交换机,一个队列会和一个交换机绑定,除此之外再绑定一个routing_key,当消息被发送的时候,需要指定一个binding_key,这个消息被送达交换机的时候,就会被这个交换机送到指定的队列里面去。同样的一个binding_key也是支持应用到多个队列中的。
这样当一个交换机绑定多个队列时,就会被送到对应的队列去处理。
调用发送者的send方法后,发现结果是队列1和2收到了消息,符合预期
适用场景:有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以派更多的资源去处理高优先级的队列。
RabbitMQ 队列模型
1. 简单队列(1对1)
P:消息的生产者
C:消息的消费者
红色:队列
生产者将消息发送到队列,消费者从队列中获取消息。
2.Work模式(1对2)
说明:
一个生产者、2个消费者。
一个消息只能被一个消费者获取。
3.订阅模式(1对多,且一个消息可多个消费)
1、1个生产者,多个消费者
2、每一个消费者都有自己的一个队列
3、生产者没有将消息直接发送到队列,而是发送到了交换机
4、每个队列都要绑定到交换机
5、生产者发送的消息,经过交换机,到达队列,实现,一个消息被多个消费者获取的目的
注意:一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费
4.路由模式
交换机可以绑定一个或多个队列
当交换机没有绑定队列时,消息会直接丢弃。
声明的队列没有持久化,程序停止后,绑定队列会自动删除。
5.主题模式(通配符模式)
同一个消息被多个消费者获取。一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费到消息。
1. 消息分发机制
轮询分发 :使用任务队列的优点之一就是可以轻易的并行工作。如果我们积压了好多工作,我们可以通过增加工作者(消费者)来解决这一问题,使得系统的伸缩性更加容易。在默认情况下,RabbitMQ将逐个发送消息到在序列中的下一个消费者(而不考虑每个任务的时长等等,且是提前一次性分配,并非一个一个分配)。平均每个消费者获得相同数量的消息。这种方式分发消息机制称为Round-Robin(轮询)。
公平分发 :虽然上面的分配法方式也还行,但是有个问题就是:比如:现在有2个消费者,所有的奇数的消息都是繁忙的,而偶数则是轻松的。按照轮询的方式,奇数的任务交给了第一个消费者,所以一直在忙个不停。偶数的任务交给另一个消费者,则立即完成任务,然后闲得不行。而RabbitMQ则是不了解这些的。这是因为当消息进入队列,RabbitMQ就会分派消息。它不看消费者为应答的数目,只是盲目的将消息发给轮询指定的消费者。
2. 消息的确认模式
消费者从队列中获取消息,服务端如何知道消息已经被消费呢?
模式1:自动确认
只要消息从队列中获取,无论消费者获取到消息后是否成功消息,都认为是消息已经成功消费。
模式2:手动确认
消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态。
3. Ack,Nack,Reject的关系
1. 消息处理成功,执行Ack,RabbitMQ会把消息从队列中删除。
2. 消息处理失败,执行Nack或者Reject:
a) 当requeue=true时,消息会重新回到队列,然后当前消费者会马上再取回这条消息;
b) 当requeue=false时,如果Exchange有设置Dead Letter Exchange,则消息会去到Dead Letter Exchange;
c) 当requeue=false时,如果Exchange没设置Dead Letter Exchange,则消息从队列中删除,效果与Ack相同。
3. Nack与Reject的区别在于:Nack可以批量操作,Reject只能单条操作。
RabbitMQ简单封装
1.生产者
在Startup的ConfigureServices方法中添加相关rabbitmq的服务:
public void ConfigureServices(IServiceCollection services) { string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" }; int port = 5672; string userName = "admin"; string password = "123456"; string virtualHost = "/"; var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } }; #region 日志记录 services.AddRabbitLogger(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.Category = "Home"; options.MinLevel = LogLevel.Warning; //队列模式 options.Queue = "queue.logger"; //交换机模式 //options.Exchange = "exchange.logger"; //options.RouteQueues = new RouteQueue[] { new RouteQueue() { Queue = "queue.logger", Route = "#" } }; //options.Type = RabbitExchangeType.Topic; }); #endregion #region 普通模式 services.AddRabbitProducer("SimplePattern", options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.InitializeCount = 3; options.Queues = new string[] { "queue.simple" }; }); #endregion #region 工作模式 services.AddRabbitProducer("WorkerPattern", options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.InitializeCount = 3; options.Queues = new string[] { "queue.worker" }; }); #endregion #region 发布订阅模式 services.AddRabbitProducer("FanoutPattern", options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.InitializeCount = 3; options.RouteQueues = new RouteQueue[] { new RouteQueue() { Queue = "queue.fanout1" }, new RouteQueue() { Queue = "queue.fanout2" } }; options.Type = RabbitExchangeType.Fanout; options.Exchange = "exchange.fanout"; }); #endregion #region 路由模式 services.AddRabbitProducer("DirectPattern", options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.InitializeCount = 5; options.Exchange = "exchange.direct"; options.Type = RabbitExchangeType.Direct; options.RouteQueues = new RouteQueue[] { new RouteQueue() { Queue = "queue.direct1", Route = "direct1" }, new RouteQueue() { Queue = "queue.direct2", Route = "direct2" } }; }); #endregion #region 主题模式 services.AddRabbitProducer("TopicPattern", options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.InitializeCount = 5; options.RouteQueues = new RouteQueue[] { new RouteQueue() { Queue = "queue.topic1", Route = "topic1.#" }, new RouteQueue() { Queue = "queue.topic2", Route = "topic2.#" } }; options.Type = RabbitExchangeType.Topic; options.Exchange = "exchange.topic"; }); #endregion services.AddControllers(); }
里面介绍了6中集成方式:
使用AddRabbitLogger方法添加日志相关的服务,需要注意的是,数据是以json格式发送到rabbitmq中去的,具体可以参考RabbitLoggerMessage<T>类,最好是自己发布测试就可以了,当然读者可以安装自己的需求修改RabbitLogger类中的发布逻辑。
使用AddRabbitProducer方法添加一个发布者,可以指定名称,这个名称是获取发布者对象时使用。这个方法添加的发布者可以满足rabbitmq的五种使用方式(普通模式,工作模式,发布订阅模式,路由模式,主题模式),具体由RabbitProducerOptions配置指定。
服务配置好,具体使用可以参考HomeController,日志记录可以注入ILogger<T>对象,或者注入ILoggerFactory对象,然后获取ILogger<T>对象,直接使用ILogger<T>对象的方法就是发布消息了:
/// <summary> /// 日志 /// </summary> /// <returns></returns> [HttpGet] public string Get() { logger.LogDebug("Debug"); logger.LogInformation("Information"); logger.LogWarning("Warning"); logger.LogError("Error"); return "success"; }
至于另外五种模式,我们需要注入IRabbitProducerFactory对象,然后使用Create方法创建指定名称的发布者,然后调用Publish或者PublishAsync方法发布消息,而且他们都有几个重载。
需要注意的是,不同类型的生产者应该使用不同的Publish或者PublishAsync方法,比如普通模式和工作模式,因为他们没有路由参数,因此需要使用无路由参数的Publish方法,如:
/// <summary> /// Simple /// </summary> /// <returns></returns> [HttpGet("Simple")] public string Simple(string message = "Simple") { var producer = rabbitProducerFactory.Create("SimplePattern"); producer.Publish(message); return "success"; } /// <summary> /// Worker /// </summary> /// <param name="message"></param> /// <returns></returns> [HttpGet("Worker")] public string Worker(string message = "Worker") { var producer = rabbitProducerFactory.Create("WorkerPattern"); int count = 10; while (count-- > 0) { producer.Publish(message); } return "success"; }
而发布订阅模式、路由模式、主题模式都是有路由的(发布订阅模式的路由可以认为是空值),因此需要使用带路由参数的Publish方法:
/// <summary> /// Direct /// </summary> /// <param name="route"></param> /// <param name="message"></param> /// <returns></returns> [HttpGet("Direct")] public string Direct(string route = "direct1", string message = "Direct") { var producer = rabbitProducerFactory.Create("DirectPattern"); producer.Publish(route, message); return "success"; } /// <summary> /// Fanout /// </summary> /// <param name="message"></param> /// <returns></returns> [HttpGet("Fanout")] public string Fanout(string message = "Fanout") { var producer = rabbitProducerFactory.Create("FanoutPattern"); producer.Publish("", message);//fanout模式路由为空值 return "success"; } /// <summary> /// Topic /// </summary> /// <param name="route"></param> /// <param name="message"></param> /// <returns></returns> [HttpGet("Topic")] public string Topic(string route = "topic1.a", string message = "Topic") { var producer = rabbitProducerFactory.Create("TopicPattern"); producer.Publish(route, message); return "success"; }
2.消费者
生产者和消费者不在同一个项目中,同样的,需要先在Startup的ConfigureServices方法中添加服务:
public void ConfigureServices(IServiceCollection services) { string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" }; int port = 5672; string userName = "admin"; string password = "123456"; string virtualHost = "/"; var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } }; #region 日志记录 services.AddRabbitConsumer(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; //options.AutoAck = true; //options.FetchCount = 10; //options.RouteQueues = new RouteQueue[] { new RouteQueue() { Queue = "queue.logger", Route = "#" } };//交换机模式 //options.Type = RabbitExchangeType.Topic;//交换机模式 }) //.AddListener("queue.logger", result => //{ // Console.WriteLine("Message From queue.logger:" + result.Body); //}); .AddListener<RabbitConsumerListener>("queue.logger"); //.AddListener("exchange.logger", "queue.logger", result => //{ // Console.WriteLine("Message From queue.logger:" + result.Body); //});//交换机模式 #endregion #region 普通模式 services.AddRabbitConsumer(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; //options.FetchCount = 1; options.AutoAck = false; }).AddListener("queue.simple", result => { Console.WriteLine("Message From queue.simple:" + result.Body); result.Commit(); }); #endregion #region 工作模式 services.AddRabbitConsumer(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; //options.FetchCount = 2; options.AutoAck = false; }).AddListener("queue.worker", result => { Console.WriteLine("Message From queue.worker1:" + result.Body); result.Commit(); }).AddListener("queue.worker", result => { Console.WriteLine("Message From queue.worker2:" + result.Body); result.Commit(); }); #endregion #region 发布订阅模式 services.AddRabbitConsumer(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.FetchCount = 2; options.AutoAck = false; options.RouteQueues = new RouteQueue[] { new RouteQueue() { Queue = "queue.fanout1" }, new RouteQueue() { Queue = "queue.fanout2" } }; options.Type = RabbitExchangeType.Fanout; }).AddListener("exchange.fanout", "queue.fanout1", result => { Console.WriteLine("Message From queue.fanout1:" + result.Body); result.Commit(); }).AddListener("exchange.fanout", "queue.fanout2", result => { Console.WriteLine("Message From queue.fanout2:" + result.Body); result.Commit(); }); #endregion #region 路由模式 services.AddRabbitConsumer(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.AutoAck = false; options.FetchCount = 2; options.Type = RabbitExchangeType.Direct; options.RouteQueues = new RouteQueue[] { new RouteQueue() { Queue = "queue.direct1", Route = "direct1" }, new RouteQueue() { Queue = "queue.direct2", Route = "direct2" } }; }).AddListener("exchange.direct", "queue.direct1", result => { Console.WriteLine("Message From queue.direct1:" + result.Body); result.Commit(); }).AddListener("exchange.direct", "queue.direct2", result => { Console.WriteLine("Message From queue.direct2:" + result.Body); result.Commit(); }); #endregion #region 主题模式 services.AddRabbitConsumer(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.FetchCount = 2; options.AutoAck = false; options.RouteQueues = new RouteQueue[] { new RouteQueue() { Queue = "queue.topic1", Route = "topic1.#" }, new RouteQueue() { Queue = "queue.topic2", Route = "topic2.#" } }; options.Type = RabbitExchangeType.Topic; }).AddListener("exchange.topic", "queue.topic1", result => { Console.WriteLine("Message From queue.topic1:" + result.Body); result.Commit(); }).AddListener("exchange.topic", "queue.topic2", result => { Console.WriteLine("Message From queue.topic2:" + result.Body); result.Commit(); }); #endregion services.AddControllers(); }
无论是日志的消费,还是其他五种模式的消费,都是先使用AddRabbitConsumer方法获取到一个IRabbitConsumerBuilder消费者构造对象,然后它的通过AddListener方法添加消息处理程序。
同样的,需要注意的是,普通模式和工作模式是不基于交换机的策略模式,因此需要使用不包含交换机参数的AddListener方法:
#region 普通模式 services.AddRabbitConsumer(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; //options.FetchCount = 1; options.AutoAck = false; }).AddListener("queue.simple", result => { Console.WriteLine("Message From queue.simple:" + result.Body); result.Commit(); }); #endregion #region 工作模式 services.AddRabbitConsumer(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; //options.FetchCount = 2; options.AutoAck = false; }).AddListener("queue.worker", result => { Console.WriteLine("Message From queue.worker1:" + result.Body); result.Commit(); }).AddListener("queue.worker", result => { Console.WriteLine("Message From queue.worker2:" + result.Body); result.Commit(); }); #endregion
发布订阅模式、路由模式和主题模式都是基于交换机的策略模式,因此使用需要交换机参数的AddListener方法:
#region 发布订阅模式 services.AddRabbitConsumer(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.FetchCount = 2; options.AutoAck = false; options.RouteQueues = new RouteQueue[] { new RouteQueue() { Queue = "queue.fanout1" }, new RouteQueue() { Queue = "queue.fanout2" } }; options.Type = RabbitExchangeType.Fanout; }).AddListener("exchange.fanout", "queue.fanout1", result => { Console.WriteLine("Message From queue.fanout1:" + result.Body); result.Commit(); }).AddListener("exchange.fanout", "queue.fanout2", result => { Console.WriteLine("Message From queue.fanout2:" + result.Body); result.Commit(); }); #endregion #region 路由模式 services.AddRabbitConsumer(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.AutoAck = false; options.FetchCount = 2; options.Type = RabbitExchangeType.Direct; options.RouteQueues = new RouteQueue[] { new RouteQueue() { Queue = "queue.direct1", Route = "direct1" }, new RouteQueue() { Queue = "queue.direct2", Route = "direct2" } }; }).AddListener("exchange.direct", "queue.direct1", result => { Console.WriteLine("Message From queue.direct1:" + result.Body); result.Commit(); }).AddListener("exchange.direct", "queue.direct2", result => { Console.WriteLine("Message From queue.direct2:" + result.Body); result.Commit(); }); #endregion #region 主题模式 services.AddRabbitConsumer(options => { options.Hosts = hosts; options.Password = password; options.Port = port; options.UserName = userName; options.VirtualHost = virtualHost; options.Arguments = arguments; options.Durable = true; options.AutoDelete = true; options.FetchCount = 2; options.AutoAck = false; options.RouteQueues = new RouteQueue[] { new RouteQueue() { Queue = "queue.topic1", Route = "topic1.#" }, new RouteQueue() { Queue = "queue.topic2", Route = "topic2.#" } }; options.Type = RabbitExchangeType.Topic; }).AddListener("exchange.topic", "queue.topic1", result => { Console.WriteLine("Message From queue.topic1:" + result.Body); result.Commit(); }).AddListener("exchange.topic", "queue.topic2", result => { Console.WriteLine("Message From queue.topic2:" + result.Body); result.Commit(); }); #endregion
另外,AddListener中的消息处理委托可以使用一个实现了IRabbitConsumerListener接口的类代替,如Demo中的RabbitConsumerListener:
public class RabbitConsumerListener : IRabbitConsumerListener { public Task ConsumeAsync(RecieveResult recieveResult) { Console.WriteLine("RabbitConsumerListener:" + recieveResult.Body); recieveResult.Commit(); return Task.CompletedTask; } }
EasyNetQ来使用RabbitMQ
https://www.cnblogs.com/shanfeng1000/p/12359190.html
github https://github.com/EasyNetQ/EasyNetQ
控制台简单使用
Install-Package RabbitMQ.Client
生产者
using RabbitMQ.Client; using System; using System.Text; namespace RabbitMQProducer { class Program { static void Main(string[] args) { var factory = new ConnectionFactory() { HostName = "localhost", Port = 5672, UserName = "guest", Password = "guest" }; using var connection = factory.CreateConnection(); using var channel = connection.CreateModel(); var exchange = "msg_test"; channel.ExchangeDeclare(exchange, type: ExchangeType.Fanout); for (int i = 0; i < 10000; i++) { var message = $"hello,我是生产者【{i + 1}】号"; var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish(exchange, routingKey: "", basicProperties: null, body: body); Console.WriteLine($"- 发送消息:{message}"); } } } }
模拟发出10000次消息,接下来在RabbitMQConsumer
消费者项目中接收消息
using RabbitMQ.Client; using RabbitMQ.Client.Events; using System; using System.Text; namespace RabbitMQConsumer { class Program { static void Main(string[] args) { var factory = new ConnectionFactory() { HostName = "localhost", Port = 5672, UserName = "guest", Password = "guest" }; using var connection = factory.CreateConnection(); using var channel = connection.CreateModel(); var exchange = "msg_test"; channel.ExchangeDeclare(exchange, type: ExchangeType.Fanout); var queueName = channel.QueueDeclare().QueueName; channel.QueueBind(queue: queueName, exchange, routingKey: ""); Console.WriteLine("开始监听消息..."); while (true) { var consumer = new EventingBasicConsumer(channel); consumer.Received += (model, ea) => { byte[] body = ea.Body.ToArray(); var message = Encoding.UTF8.GetString(body); Console.WriteLine($"- 接收到消息:{message}"); }; channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); } } } }