网上找的
生产者发消息发往交换机,交换机会自己投递消息到绑定的queue队列
这里有几个点需要注意
对routing_key进行严格匹配,当消息来到的时候,只有exchange与某queue绑定的routing_key完全匹配才将消息投递到该queue
对routing_key进行通配符模糊匹配,满足条件的queue都能收到消息,这里的routing_key以"."分隔,*匹配一个单词,#匹配多个单词,如果同一个queue满足多个条件不会被投递多次
一下图为例,如果消息的routingkey是quick.orange.rabbit,那么Q1 Q2都会收到这条消息。
如果消息的routingkey是quick.orange.fox,那么Q1会收到这条消息
根据消息体内的headers属性匹配,绑定的时候可以制定键值对。不依赖routing_key匹配。
没有图,大致逻辑与direct差不多,只不过不是用的routing_key来匹配
转发消息到所有绑定队列,不依赖routing_key匹配
在不需要路由的时候,一般是使用的这个类型的exchange。
发布订阅:两个queue绑定到同一个exchange上,那么同一个消息被发送到exchange后,exchange会把这个消息发给绑定的所有队列,两个消费者,一人消费一个队列,这就在这两个消费者之间达到了发布订阅的效果
竞争消费:两个消费者消费同一个队列,这就达到了这两个消费者之间的竞争消费效果。注意,下图没有画exchange,实际上在写代码的时候不显示指定exchange的数据是发送到一个默认的exchange上的。
如果不设置持久化,broker挂了,再重启,这个exchange就不存在了。
在客户端声明exchange的时候有个入参来控制是否持久化
Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable) throws IOException;
Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete,
Map arguments) throws IOException;
Exchange.DeclareOk exchangeDeclare(String exchange, String type) throws IOException;
Exchange.DeclareOk exchangeDeclare(String exchange,
String type,
boolean durable,
boolean autoDelete,
boolean internal,
Map arguments) throws IOException;
void exchangeDeclareNoWait(String exchange,
String type,
boolean durable,
boolean autoDelete,
boolean internal,
Map arguments) throws IOException;
Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException;
上图中,durable即是是否持久化
而autoDelete则是,当没有queue绑定的时候是否自动删除这个exchange
【二】rabbitmq生产者确保消息一定送达
mandatory
当mandatory标志位设置为true时,如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那么会调用basic.return方法将消息返回给生产者(Basic.Return + Content-Header + Content-Body);
当mandatory设置为false时,出现上述情形broker会直接将消息扔掉。
immediate
当immediate标志位设置为true时,如果exchange在将消息路由到queue(s)时发现对于的queue上没有消费者,那么这条消息不会放入队列中。当与消息routeKey关联的所有queue(一个或者多个)都没有消费者时,该消息会通过basic.return方法返还给生产者。
换句话说,无法找到一个消费者时,消息返还给生产者
消息队列,先进先出,有缓存消息的能力。
1.可以设置成是持久化队列,这样消息会落盘,没有消费的消息重启后不会丢。
2.如果是临时队列,则没有持久化,堆积的数据在rabbitmq重启后会丢失。
3.如果设置为自动队列,当没有消费者消费这个队列的时候,队列会自动删除。
在channel声明队列的时候可以设置
public com.rabbitmq.client.impl.AMQImpl.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments) throws IOException {
validateQueueNameLength(queue);
return (com.rabbitmq.client.impl.AMQImpl.Queue.DeclareOk)this.exnWrappingRpc((new com.rabbitmq.client.AMQP.Queue.Declare.Builder()).queue(queue).durable(durable).exclusive(exclusive).autoDelete(autoDelete).arguments(arguments).build()).getMethod();
}
1.durable是否持久化队列。
2.exclusive是否为独占(排他)队列。
exclusive队列的特点是:
- 只对首次声明它的连接(Connection)可见,注意,是首次声明它的connection不是channel。
- 会在其连接断开的时候自动删除。
3.autoDelete是否自动删除,如果为是,当没有消费者消费这个队列的时候,这个队列会被自动删除。
注意,这里只是说持久化队列,持久化队列在rabbitmq重启后依旧存在,如果需要未消费的消息在重启后依旧存在,还需要持久化消息
不支持。rabbitmq不像kafka那样,并没有偏移量。rabbitmq即使是持久化队列+持久化消息,在被消费后该数据会被标记为删除,等待回收。
另外一篇将
【三】rabbitmq消费者ACK机制message acknowledgment
如果只是持久化队列,没有持久化消息,那么重启后,队列存在,消息不存在了。
持久化消息的设置在channel.basicPublish方法的入参中
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)
throws IOException;
void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
throws IOException;
入参中的 BasicProperties props用于设置消息是否持久化
public BasicProperties(
String contentType,//消息类型如:text/plain
String contentEncoding,//编码
Map headers,
Integer deliveryMode,//消息是否持久化 1:nonpersistent 2:persistent
Integer priority,//优先级
String correlationId,
String replyTo,//反馈队列
String expiration,//expiration到期时间
String messageId,
Date timestamp,
String type,
String userId,
String appId,
String clusterId)
deliveryMode=1表示不需要持久化消息,deliveryMode=2表示需要持久化消息
Round-robin dispatching 循环分发(默认)
若存在多个consumer,每个consumer的负载可能不同,有些处理的快有些处理的慢,RabbitMQ并不管这些,只是简单的以round-robin的方式分配message,这可能造成某些consumer积压很多任务处理不完,而一些consumer长期处于饥饿状态
那么,Rabbit是如何处理这种问题呢?
Fair dispatch 公平分发
通过basic.qos方法设置prefetch_count=1,这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message,换句话说,在接收到该Consumer的ack前,它不会将新的Message分发给它
channel.basic_qos(prefetch_count=1)
注意,这种方法可能会导致queue满。当然,这种情况下你可能需要添加更多的Consumer,或者创建更多的virtualHost来细化你的设计。
是建立在真实的TCP连接内的虚拟连接(是我们与RabbitMQ打交道的最重要的一个接口)。
仅仅创建了客户端到Broker之间的连接后,客户端还是不能发送消息的,需要为每一个Connection创建Channel,AMQP协议规定只有通过Channel才能执行AMQP的命令。
AMQP的命令都是通过信道发送出去的(我们大部分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。)。
每条信道都会被指派一个唯一ID。在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务,理论上无限制,减少TCP创建和销毁的开销,实现共用TCP的效果。
之所以需要Channel,是因为TCP连接的建立和释放都是十分昂贵的,如果一个客户端每一个线程都需要与Broker交互,如果每一个线程都建立一个TCP连接,暂且不考虑TCP连接是否浪费,就算操作系统也无法承受每秒建立如此多的TCP连接。
注1:一个生产者或一个消费者与MQ服务器之间只有一条TCP连接
注2:RabbitMQ建议客户端线程之间不要共用Channel,至少要保证共用Channel的线程发送消息必须是串行的,但是建议尽量共用Connection。
是Publisher/Consumer和Broker之间的TCP连接。断开连接的操作只会在Publisher/Consumer端进行,Broker不会断开连接,除非出现网络故障或者Broker服务出现问题,Broker服务宕了。
每一个vhost本质上是一个mini-rabbitmq server,分别管理各自的exchange,和bindings。
一个Broker里可以开有多个VirtualHost,它的作用是用作不同用户的权限分离。
这个特性在做多租户的时候较方便
1.RabbitMQ默认的vhost是“/”开箱即用;
2.多个vhost是隔离的,多个vhost无法通讯,并且不用担心命名冲突(队列和交换器和绑定),实现了多层分离;
3.创建用户的时候必须指定vhost;
可以通过rabbitmqctl工具命令创建:
rabbitmqctl add_vhost[vhost_name]
删除vhost:
rabbitmqctl delete_vhost[vhost_name]
查看所有的vhost:
rabbitmqctl list_vhosts