1、什么是RabbitMQ?为什么要使用MQ?优缺点?
RabbitMQ是一款开源的,Erlang编写的,基于AMQP协议的,消息中间件;
可以用来:流量消峰,应用解耦,异步处理
缺点:
降低了系统的稳定性:加入了消息队列,那消息队列挂了,系统可用性会降低;
增加了系统的复杂性:加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。因此,需要考虑的东西更多,复杂性增大。
系统可用性降低,对外部有依赖了
需要考虑 MQ 消息丢失,重复消费的问题
需要花费精力保证消息的顺序性,一致性
2、RabbitMQ 工作原理
● 拉模式【pull】: 【消费者主动】从消息中间件【拉】取消息;只想从队列中获取单条消息而不是持续订阅;
● 推模式【push】:【消息中间件主动】将消息【推】送给【消费者】;
优点:实时性非常好,消费者能及时得到最新的消息;
缺点:消费者必须设置一个缓冲区缓存这些消息,缺点就是缓冲区可能会溢出;
3、RabbitMQ 的构造【RabbitMQ各组件的功能】
(1)Broker:rabbitmq服务节点,表示消息队列服务器实体。消息队列服务进程。一般情况下一个Broker可以看做一个RabbitMQ服务器。
(2)Exchange:消息队列交换器,接受生产者发送的消息,根据路由键将消息路由到绑定的队列上。按一定的规则将消息路由转发到某个队列,对消息进行过虑。
(3)Queue:消息队列,用来存放消息的队列。一个消息可投入一个或多个队列,多个消费者可以订阅同一队列,这时队列中的消息会被平摊(轮询)给多个消费者进行处理。
(4)Producer:生产者,生产消息,就是投递消息的一方。消息一般包含两个部分:消息体(payload)和标签(Label)
(5)Consumer:消费者,消费消息,也就是接收消息的一方。消费者连接到RabbitMQ服务器,并订阅到队列上。消费消息时只消费消息体,丢弃标签。
(6)Routing Key: 路由关键字,用于指定这个消息的路由规则,需要与交换器类型和绑定键(Binding Key)联合使用才能最终生效。
(7)Binding:绑定,通过绑定将交换器和队列关联起来,一般会指定一个BindingKey,通过BindingKey,交换器就知道将消息路由给哪个队列了。
(8)Connection :网络连接,是物理TCP连接,比如一个TCP连接,用于连接到具体broker
(9)Channel: 信道,AMQP 命令都是在信道中进行的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。
因为建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接,一个TCP连接可以用多个信道。客户端可以建立多个channel,每个channel表示一个会话任务。
(10)Message:消息,由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
(11)Virtual host:虚拟主机,用于逻辑隔离,表示一批独立的交换器、消息队列和相关对象。一个Virtual host可以有若干个Exchange和Queue,同一个Virtual host不能有同名的Exchange或Queue。最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段
(12)Server:接收客户端的连接,实现AMQP实体服务。
4、Exchange 交换器的类型
Exchange分发消息时根据类型的不同分发策略有区别,目前共4种类型:direct、fanout、topic、headers
(1)Direct:路由队列,定向,把消息交给符合指定routing key 的队列,根据消息中的路由键(RoutingKey)如果和 Bingding 中的 bindingKey 完全匹配,交换器就将消息发到对应的队列中。是基于完全匹配、单播的模式。
(2)Fanout:发布/订阅【Publish/Subscribe】,也称为广播,将消息交给所有绑定到交换机的队列。把所有发送到 Fanout 交换器的消息路由到所有绑定该交换器的队列中,Fanout 类型转发消息是最快的。
(3)Topic:主题,通配符,把消息交给符合routing pattern(路由模式) 的队列,通过模式匹配的方式对消息进行路由,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。
匹配规则:
① RoutingKey 和 BindingKey 为一个 点号 '.' 分隔的字符串。 比如: java.xiaoka.show
② BindingKey可使用 * 和 # 用于做模糊匹配:*匹配一个单词,#匹配多个或者0个单词
实例:
channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: "*.orange.*");
channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: "*.*.rabbit");
channel.QueueBind(queue: queueName, exchange: exchangeName, routingKey: "lazy.#");
(4)Headers:头交换机,不依赖于【路由键】进行匹配,是根据发送消息内容中的【headers属性】进行匹配,键值对,除此之外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了;
5、RabbitMQ 的6种工作模式
(1)Simple 模式(即最简单的收发模式)
(2)Work Queues(工作队列)
(3)Publish/Subscribe发布订阅(共享资源)
(4)Routing路由模式
(5)Topics 主题模式(路由模式的一种)
(6)RPC 远程过程调用
6、生产者消息的过程:
(1)Producer 先连接到 Broker,建立连接 Connection,开启一个信道 channel
(2)Producer 声明一个交换器并设置好相关属性
(3)Producer 声明一个队列并设置好相关属性
(4)Producer 通过绑定键将交换器和队列绑定起来
(5)Producer 发送消息到 Broker,其中包含路由键、交换器等信息
(6)交换器根据接收到的路由键查找匹配的队列
(7)如果找到,将消息存入对应的队列,如果没有找到,会根据生产者的配置丢弃或者退回给生产者。
(8)关闭信道
7、消费者接收消息过程:
(1)Producer 先连接到 Broker,建立连接 Connection,开启一个信道 channel
(2)向 Broker 请求消费相应队列中消息,可能会设置响应的回调函数。
(3)等待 Broker 回应并投递相应队列中的消息,接收消息。
(4)消费者确认收到的消息,ack。
(5)RabbitMQ从队列中删除已经确定的消息。
(6)关闭信道
8、RabbitMQ Transaction(事务)
保障消息可靠传输,防止消息丢失,劫持等,确保【生产者】不丢消息,通过对【信道】设置实现,事务会降低 RabbitMQ 性能。
channel.txSelect(); 启动事务,用于将当前channel设置成transaction模式,通知服务器开启事务模式,服务端会返回tx.Select-Ok
channel.txCommit(); 提交事务
channel.txRollback(); 回滚事务
txSelect用于将当前channel(信道)设置成transaction(事务模式),
在通过txSelect开启事务之后,发布消息给broker代理服务器,
如果txCommit提交成功,则消息一定到达了broker,
如果txCommit执行之前broker异常崩溃或者由于其他原因抛出异常,这个时候便捕获异常通过txRollback回滚事务。
消费者使用事务:
• (1)autoAck=false,手动提交ack,以事务提交或回滚为准;
• (2)autoAck=true,不支持事务的,即使在收到消息之后在回滚事务也是于事无补的,队列已经把消息移除了。
9、消息丢失
生产-->MQ Broker --> 消费。这三个环节都有丢失消息的可能。
异常处理
消息重试机制
错误日志记录
死信队列
监控与告警
9.1、生产者 消息丢失【1】
RabbitMQ提供事务机制(transaction)【性能差】和确认机制(confirm)【推荐】两种模式来确保生产者不丢消息。
transaction事务机制就是说,发送消息前开启事物(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事物(channel.txCommit())。然而缺点就是吞吐量下降了。
因此,生产上用confirm模式的居多。一旦channel进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),
一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个Ack给生产者(包含消息的唯一ID),
这就使得生产者知道消息已经正确到达目的队列了.如果rabiitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。
重试注意事项:
(1)设置最大重试次数,避免无限循环重试造成系统负载过高;
(2)设置重试间隔时间,避免瞬时故障引发连续的重试请求;;
(3)达到最大重试次数后,发送到死信队列,防止消息无限重试;
记录错误日志
监控与告警系统
9.2、消息队列本身 RabbitMQ 本身 消息丢失【2】
处理消息队列丢数据的情况,一般是开启持久化磁盘。持久化配置可以和生产者的 confirm 机制配合使用,在消息持久化磁盘后,再给生产者发送一个Ack信号。
这样的话,如果消息持久化磁盘之前,即使 RabbitMQ 挂掉了,生产者也会因为收不到Ack信号而再次重发消息。
持久化设置如下(必须同时设置以下 2 个配置):
(1)创建queue的时候,将queue的持久化标志durable在设置为true,代表是一个持久的队列,这样就可以保证 rabbitmq 持久化 queue 的元数据,但是不会持久化queue里的数据;
(2)发送消息的时候将 deliveryMode 设置为 2,将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。
这样设置以后,RabbitMQ 就算挂了,重启后也能恢复数据。在消息还没有持久化到硬盘时,可能服务已经死掉,这种情况可以通过引入镜像队列,但也不能保证消息百分百不丢失(整个集群都挂掉)
9.3*、消费者 消息丢失【3】
消费者丢数据一般是因为采用了自动确认消息模式。该模式下,虽然消息还在处理中,但是消费中者会自动发送一个确认,通知 RabbitMQ 已经收到消息了,这时 RabbitMQ 就会立即将消息删除。这种情况下,如果消费者出现异常而未能处理消息,那就会丢失该消息。
解决方案就是采用手动确认消息,设置 autoAck = False,等到消息被真正消费之后,再手动发送一个确认信号,即使中途消息没处理完,但是服务器宕机了,那 RabbitMQ 就收不到发的ack,然后 RabbitMQ 就会将这条消息重新分配给其他的消费者去处理。
但是 RabbitMQ 并没有使用超时机制,RabbitMQ 仅通过与消费者的连接来确认是否需要重新发送消息,也就是说,只要连接不中断,RabbitMQ 会给消费者足够长的时间来处理消息。另外,采用手动确认消息的方式,我们也需要考虑一下几种特殊情况:
如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ 会认为消息没有被消费,然后重新分发给下一个订阅的消费者,所以存在消息重复消费的隐患
如果消费者接收到消息却没有确认消息,连接也未断开,则RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息
需要注意的点
1、消息可靠性增强了,性能就下降了,因为写磁盘比写 RAM 慢的多,两者的吞吐量可能有 10 倍的差距。所以,是否要对消息进行持久化,需要综合考虑业务场景、性能需要,以及可能遇到的问题。
若想达到单RabbitMQ服务器 10W 条/秒以上的消息吞吐量,则要么使用其他的方式来确保消息的可靠传输,要么使用非常快速的存储系统以支持全持久化,例如使用 SSD。或者仅对关键消息作持久化处理,且应该保证关键消息的量不会导致性能瓶颈。
2、当设置 autoAck = False 时,如果忘记手动 ack,那么将会导致大量任务都处于 Unacked 状态,造成队列堆积,直至消费者断开才会重新回到队列。解决方法是及时 ack,确保异常时 ack 或者拒绝消息。
3、启用消息拒绝或者发送 nack 后导致死循环的问题:如果在消息处理异常时,直接拒绝消息,消息会重新进入队列。这时候如果消息再次被处理时又被拒绝 。这样就会形成死循环。
10、死信队列 & 死信交换器
DLX 全称(Dead-Letter-Exchange),也称之为死信交换器,当消息无法被消费者正常消费时,将这些无法消费的消息发送到专门的死信队列中,以便进行进一步的处理,
消息变成一个死信之后,如果这个消息所在的队列存在x-dead-letter-exchange参数,那么它会被发送到x-dead-letter-exchange对应值的交换器上,这个交换器就称之为死信交换器,与这个死信交换器绑定的队列就是死信队列。
死信队列和普通队列没啥区别,都需要自己创建Queue、Exchange,然后通过RoutingKey绑定到Exchange上。只不过死信队列的RoutingKey和Exchange要作为参数,绑定到正常的队列上,用来存放死信而已。
死信消息
• (1)消息被拒绝(Basic.Reject或Basic.Nack)并且设置 requeue 参数的值为 false
• (2)消息过期了,消息在队列的存活时间超过设置的TTL时间。
• (3)队列达到最大的长度,消息队列的消息数量已经超过最大队列长度。
如果配置了死信队列信息,那么消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。
11、死信的处理方式
① 丢弃,如果不是很重要,可以选择丢弃
② 记录死信入库,然后做后续的业务分析或处理
③ 通过死信队列,由负责监听死信的应用程序进行处理
综合来看,更常用的做法是第三种,即通过死信队列,将产生的死信通过程序的配置路由到指定的死信队列,然后应用监听死信队列,对接收到的死信做后续的处理。
Dictionary<string, object> dicts = new Dictionary<string, object>();
// 死信交换机
dicts.Add("x-dead-letter-exchange", "dead_exchange");
// 死信路由键
dicts.Add("x-dead-letter-routing-key", "dead");
// 队列优先级最高为10,不加x-max-priority的话,计算发布时设置了消息的优先级也不会生效
dicts.Add("x-max-priority", 10);
// 声明队列
channel.QueueDeclare(
queue: queueName,//队列名称
durable: false,//持久化
exclusive: false,//排他队列
autoDelete: false,//队列自动删除
arguments: dicts);
12、RabbitMQ Transaction(事务)、Confirm 和 ACK
1、Transaction事务 作用在 生产端,将当前channel设置成transaction模式;
2、Confirm 作用在 生产端,开启了这个模式就可以知道消息是否发送到exchange上。不管有没有发送到都会触发回调方法。Confirm机制是保证消息成功投递到了服务端,通过回调通知生产者是否收到了消息,重点在生产者这里,要设置消息确认,然后监听回调;
3、Ack 作用在 消费端,为了保证消息正确被消费者消费,分为手动确认和自动确认,自动确认autoAck = true,手动确认autoAck = false;
13、如何保证RabbitMQ消息的顺序性
● 拆分多个 queue(消息队列),每个 queue(消息队列) 一个 consumer(消费者),就是多一些 queue (消息队列)而已,确实是麻烦点;
● 就一个 queue (消息队列)但是对应一个 consumer(消费者),然后这个 consumer(消费者)内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
14、如何保证消息的可靠性传输?
1、使用Transaction(事务)消息;
2、使用消息确认机制
【发送方确认】
● channel设置为confirm模式,则每条消息会被分配一个唯一id
● 消息投递成功,信道会发送ack给生产者,包含了id,回调ConfirmCallback接口;
● 如果发送错误到时消息丢失,发送nack给生产者,回调ReturnCallback接口;
● ack 和 nack只有一个触发,且只有一次,异步触发,可以继续发送消息;
【接收方确认】
● 声明队列时,指定noack=false,broker会等待消费者手动返回ack、才会删除消息,否则离开删除;
● broker的ack没有超时机制,只会判断链接是否断开,如果断开,消息会被重新发送;
15、如何保证消息不被重复消费
幂等:
保证消息的唯一性,就算是多次传输,不要让消息的多次消费带来影响;保证消息等幂性
在写入消息队列的数据做唯一标示,消费消息时,根据唯一标识判断是否消费过;
16、如何确保消息发送?消息接收?
【发送方确认机制】
信道设置为Confirm模式,则所有在信道上发布的消息都会分配一个唯一ID。
一旦消息被投递到queu(可持久化的消息需要写入磁盘),信道会发送一个确认给生产者(包含消息唯一ID)。
如果 RabbitMQ 发送内部错误从而导致消息丢失,会发送一条nack(未确认)消息给生成者。
所有被发送的消息都将被confirm(即ack)或者被nack一次。但是没有对消息被confirm的快慢做任何保证,并且同一条消息不会既被confirm又被nack
发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者,生产者的回调方法会被触发。
confirmCallback接口:只确认是否正确到达Exchange中,成功到达时回调;
returnCallback接口:消息失败返回时回调
【接收方确认机制】
● 消费者在声明队列时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消费者是显式发回ack信号后才从内存(或磁盘,持久化消息)中移除消息。否则,消息被消费后会被立即删除。
● 消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地吧消息从队列中删除
● RabbitMQ不会为未ack的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么设计的原因是RabbitMQ运行消费者消费一条小时的时间可以很长。保证数据的最终一致性。
● 如果消费者返回ack之前断开了链接,RabbitMQ会重新分发改下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)
17、如何确保消息接收方消费了消息
发送方确认模式(生产者),接收方确认模式(消费者)
1、让 生产者 确认,Confirm模式:生产与消费确认模式;
2、让 生产者 确认,TX事务模式:开启事务、提交事务、事务回滚;
3、让 消费者 确认,自动确认,autoAck 设置为 true;
4、让 消费者 确认,手动确认,autoAck 设置为 false;
18、如何处理消息堆积情况?
场景题:几千万条数据在MQ里积压了七八个小时。
*
*
*
*
19、RabbitMQ 持久化机制
● 交换机持久化
// durable: true 持久化,false 不持久化
channel.ExchangeDeclare(exchange: "交换机名称", type: ExchangeType.Direct, durable: true);
● 队列持久化
// durable: true 持久化,false 不持久化
channel.QueueDeclare(queue: "队列名称", durable: true, exclusive: false, autoDelete: false,
arguments: null);
● 消息持久化
var props = channel.CreateBasicProperties();
props.Persistent = true;
channel.BasicPublish(exchange: "", routingKey: props.ReplyTo, basicProperties: props, body: responseBytes);
20、RabbitMQ 性能调优
*
21、RabbitMQ 镜像队列原理
*
*
22、RabbitMQ 常用方法【公共】
//1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory
{
HostName = "127.0.0.1",//rabbitmq ip
Port = 5672,//端口号
UserName = "guest",//用户名
Password = "guest",//密码
//VirtualHost="/tj"//虚拟主机
};
//2. 创建连接对象
using (IConnection connection = factory.CreateConnection())
//3. 创建连接会话对象,信道
using (IModel channel = connection.CreateModel())
23、RabbitMQ 常用方法【生产者】
channel.BasicQos(0, 1, false);
/// 已确认,肯定确认,是using写法要写到创建交换器和队列并且进行绑定之前,否则不会触发
channel.BasicAcks += (sender, args) =>
{
//多条
if (args.Multiple)
Console.WriteLine("最后成功的一条是 : " + args.DeliveryTag);
//单条
else
Console.WriteLine("消息已经确认收到" + args.DeliveryTag + " 成功发送 ");
};
/// 未确认,否定确认,是using写法要写到创建交换器和队列并且进行绑定之前,否则不会触发
channel.BasicNacks += (sender, args) =>
{
//多条
if (args.Multiple)
Console.WriteLine("最后失败的一条是 : " + args.DeliveryTag);
//单条
else
Console.WriteLine(args.DeliveryTag + " 发送失败 ");
};
// broker 发现当前消息无法被路由到指定的 queues 中(如果设置了 mandatory 属性,则 broker 会先发送 basic.return)
channel.BasicReturn += (sender, args) =>
{
Console.WriteLine(args.ReplyCode); //消息失败的code
Console.WriteLine(args.ReplyText); //描述返回原因的文本
string str = Encoding.UTF8.GetString(args.Body.ToArray()); //失败消息的内容
Console.WriteLine("return message : " + str);
};
//这个事件就是用来监听我们一些不可达的消息的内容的:比如某些情况下,如果我们在发送消息时,当前的exchange不存在或者指定的routingkey路由不到,这个时候如果要监听这种不可达的消息,就要使用 return
EventHandler<BasicReturnEventArgs> evreturn = new EventHandler<BasicReturnEventArgs>((o, basic) => {
Console.WriteLine(basic.ReplyCode); //消息失败的code
Console.WriteLine(basic.ReplyText); //描述返回原因的文本
Console.WriteLine(Encoding.UTF8.GetString(basic.Body.ToArray())); //失败消息的内容
//在这里我们可能要对这条不可达消息做处理,比如是否重发这条不可达的消息呀,或者这条消息发送到其他的路由中呀,等等
System.IO.File.AppendAllText("d:/return.txt", "调用了Return;ReplyCode:" + basic.ReplyCode + ";ReplyText:" + basic.ReplyText + ";Body:" + Encoding.UTF8.GetString(basic.Body.ToArray()));
});
channel.BasicReturn += evreturn;
//4. 声明交换机
channel.ExchangeDeclare(exchange: "交换机名称", type: ExchangeType.Direct); Direct定向,路由队列
channel.ExchangeDeclare(exchange: "交换机名称", type: ExchangeType.Fanout); Fanout广播,发布/订阅
channel.ExchangeDeclare(exchange: "交换机名称", type: ExchangeType.Topic); Topic主题
channel.ExchangeDeclare(exchange: "交换机名称", type: ExchangeType.Headers); Headers头交换机,
channel.ExchangeDeclare(exchange: "交换机名称", type: ExchangeType.Direct, durable: true, autoDelete: false, arguments: null);
// 参数
Dictionary<string, object> dicts = new Dictionary<string, object>();
dicts.Add("x-dead-letter-exchange", "dead_exchange");
dicts.Add("x-dead-letter-routing-key", "dead");
//5. 声明一个队列
channel.QueueDeclare(
queue: queueName,//队列名称
durable: false,//持久化
exclusive: false,//排他队列
autoDelete: false,//队列自动删除
arguments: null
);
//6. 队列和交换机绑定 --消费者
channel.QueueBind(queue: "队列名称", exchange: "交换机名称", routingKey: "路由键");
//7. 发送消息 --生产者
// 开启确认模式【生产者】
channel.ConfirmSelect();
channel.BasicPublish(exchange: "交换机名称", routingKey: "路由键", basicProperties: null, body: Encoding.UTF8.GetBytes("消息内容"));
// 如果一条消息或多条消息确认发送
if (channel.WaitForConfirms())
{
Console.WriteLine($"{message} 发送到broke成功");
}
else
{
//记录日志,重试一下
}
// 如果所有消息发送成功,就正常执行;如果有消息发送失败,就抛出异常;
channel.WaitForConfirmsOrDie();
*
*
*
24、RabbitMQ 常用方法【消费者】
// 事件基本消费者
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
// 接收到消息事件【消费者】
consumer.Received += (model, ea) =>
{
/// 打印消息
Console.WriteLine(" [x] Received {0}", Encoding.UTF8.GetString(ea.Body.ToArray()));
/// 手动确认,消息正常消费,告诉 broker 你可以删除当前这条消息,multiple:true: 确认多条,把当前消息和之前的消息一起应答,false: 只应答当前消息
channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
/// 拒绝:requeue: true 重新放入队列,false 抛弃此条消息
channel.BasicNack(deliveryTag: ea.DeliveryTag, multiple: false, requeue: true);
/// 拒绝:告诉broker,消息没有正常被消费,requeue: true表示重新写到队列中,false 从队列中删除
channel.BasicReject(deliveryTag: ea.DeliveryTag, requeue: true);
// broker 发现当前消息无法被路由到指定的 queues 中(如果设置了 mandatory 属性,则 broker 会先发送 basic.return)
channel.BasicRecoverOk += (sender, args) =>
{
};
#region 主动拉取队列中的一条消息
/// 第二个参数传递true时,消息在被消费之后,会自动告诉RabbitMQ该消息已收到,RabbitMQ这时候会把该条消息从队列中移除
BasicGetResult result = channel.BasicGet("队列名称", true);
//采用手工确认
channel.BasicAck(result.DeliveryTag, false);
#endregion
/// 否恢复消息到队列,true:重新放入队列,并且尽可能的将之前recover的消息投递给其他消费者消费,而不是自己再次消费。false则消息会重新被投递给自己。
channel.BasicRecover(true);
/// 取消消费者对队列的订阅关系
channel.BasicCancel("");
};
// 消费消息 autoAck
channel.BasicConsume("队列名称", true, consumer);
channel.BasicConsume(queue: "队列名称", autoAck: false, consumer: consumer); /// 消费消息 - 手动确认
channel.BasicConsume(queue: "队列名称", autoAck: true, consumer: consumer); /// 消费消息 - 自动确认
*
*
*