- 本文出现的代码只是简述,详细代码之后文章有涉及。
简单队列
RabbitMQ中的消息都只能存储在Queue中,生产者(下图中的P)生产消息并最终投递到Queue中,消费者(下图中的C)可以从Queue中获取消息并消费。
工作队列
即单生产者,多个消费者消费队列的情况。
工作队列–轮询分发
即使一个处理快,一个处理慢,消息队列也还是平均的分发
工作队列–公平分发(fair dipatch)
-
消费者关闭自动应答,ack改为手动;
每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息,限制发送给同一个消费者不得超过一个消息
生产者(消息队列)
//保证一次只分发一个 int prefetchCount=1; channel.baseicQos(prefetchCount)
消费者
//保证一次只分发一个 int prefetchCount=1; channel.baseicQos(prefetchCount); channel.basicAck(envelope.getDeliveryTag(),false); boolean autoAck=false;
autoAck为true时,为自动确认模式,即一旦rabbitMQ将消息分发给消费者,就会从内存中删除;这种情况下,如果正在执行的消费者线程挂了,就会丢失正在处理的消息;
autoAck为false时,如果有一个消费者挂掉,就会交付给其他消费者,rabbitmq支持消息应答,消费者发送一个消息应答,告诉rabbitmq这个消息我已经处理完成,你可以删了,然后rabbitmq再删除内存中的消息。
消息应答默认是打开的。
消息的持久化
如果rabbitMq挂了,怎么办?
这时候,引入持久化。
//声明队列
boolean durable=false;
channel.queueDeclare(QUEUE_NAME,durable,false,false,null);
我们将程序中的boolean durable=false改为true是不可以的,尽管代码是正确的,他也不会成功运行;因为我们已经定义了一个名为QUEUE_NAME的队列,这个队列是未持久化的,rabbitmq不准重新定义(不同参数)一个已存在的队列。
订阅模式(Publish /Subscribe)
解读:
- 一个生产者,多个消费者
- 每个消费者都有自己的队列
- 生产者没有直接把消息队列发送到队列,而是发送到了交换机、转发器、exchange
- 每个队列都要绑定到交换机上
- 生产者发送的消息,经过交换机,到达队列,就能实现一个消息被多个消费者消息
疑问:
-
如果只声明交换机,未声明队列,此时发送消息,会发现消息丢失了,消息哪去了?
交换机没有存储的能力,在rabbitMq中只有消息队列有存储的能力,这时候没有队列绑定到交换机上,所以数据丢失了。
Exchange(交换机、转发器)
两方面的操作:接收生产者的消息,同时向队列推送消息
fanout
匿名模式(非路由模式)
//其中第二个参数就为:routingKey
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes())
它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。
Routing
路由模式
Direct
exchangeType为direct,它会把消息路由到那些binding key与routing key完全匹配的Queue中。
Topic exchange
将路由键和某模式匹配。
前面讲到direct类型的Exchange路由规则是完全匹配binding key与routing key,但这种严格的匹配方式在很多情况下不能满足实际业务需求。topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:
- routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
- binding key与routing key一样也是句点号“. ”分隔的字符串
- binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“ * ”用于匹配一个单词,“ # ”用于匹配多个单词(可以是零个)
(商品:发布、删除、修改…)
RabbitMQ的消息确认机制(事务+confirm)
在rabbitmq中可以通过持久化数据,解决rabbitmq服务器异常的数据丢失问题。
问题:生产者将消息发送出去之后,消息到底有没有到达rabbitmq服务器,默认的情况是不知道的。(之前持久化是解决了:开启自动确认模式时,消费者突然断电的清空)。
两种方式:
- AMQP实现了事务机制
- Confirm模式
事务机制
txSelect、txCommit、txRollback
txSelect:用户将当前channel设置成transation模式
txCommit:用于提交事务
txRollback:回滚事务
channel.TxSelect();//将信道设置为事务模式
try
{
//do something
var message = Encoding.UTF8.GetBytes("TestMsg");
channel.BasicPublish("normalExchange", "NormalRoutingKey", true, null, message);
//do something
channel.TxCommit();//提交事务
}
catch (Exception ex)
{
//log(ex);
channel.TxRollback();
}
事务确实能够解决消息发送方和RabbitMQ之间消息确认的问题,只有消息成功被RabbitMQ接收,事务才能提交成功,否则便可在捕获异常之后进行事务回滚,与此同时可以进行消息重发。但是使用事务同样会带来一些问题。
- 会阻塞,发布者必须等待broker处理每个消息。
- 事务是重量级的,每次提交都需要fsync(),需要耗费大量的时间
- 事务非常耗性能,会降低RabbitMQ的消息吞吐量。
Confirm模式
这里就引入了一种轻量级的方式一发送方确认(publisher confirm)机制。生产者将信道设置成confirm确认模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID( 从1开始),一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认(BasicAck) 给生产者(包含消息的唯一ID),这就使得生产者知晓消息已经正确到达了目的地了。如果消息和队列是可持久化的,那么确认消息会在消息写入磁盘之后发出。
Comfirm模式的最大好处就是它是异步的。
开启Confirm模式:
channel.ConfirmSelect();//开启确认模式
编程模式:
- 普通,发一条,waitForConfirms()
- 批量的,发一批
- 异步confirm模式:提供一个回调方法