目录
1、RabbitMQ
1.1、RabbitMQ定义了几种交换机
1.1.1、Direct exchange(直连交换机)
1.1.2、Fanout exchange(扇型交换机)
1.1.3、Topic exchange(主题交换机)
1.1.4、Headers exchange(头交换机)
1.2、RabbitMQ支持事务消息
1.2.1、事务消息
1.2.2、Confirm发送方确认模式
1.2.3、RabbitMQ的手动ACK机制
1.3、有虚拟主机VHOST的概念能很好的进行权限管理
1.4、RabbitMQ架构
根据消息携带的路由键(routing key)将消息投递给对应队列的。也就是说队列与交换机绑定时设置的路由键(routing key)必须与发送消息时指定的路由键(routing key)一样。
不需要指定路由键(routing key),你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。简单理解就是可绑定多个队列,同样一个队列也可以绑定多个交换机。如果发送消息的时候交换机没有绑定有队列,那么将丢弃该消息。
也可以理解为通配符交换机。主题交换机的routing_key需要有一定的规则,交换机和队列的binding_key需要采用*.#.*.....的格式,每个部分用.分开,其中:*表示一个单词;#表示任意数量(零个或多个)单词。因此“abc.#”能够匹配到“abc.def.ghi”,但是“abc.” 只会匹配到“abc.def”。
不处理路由键。而是根据发送的消息内容中的headers属性进行匹配。在绑定Queue与Exchange时指定一组键值对;当消息发送到RabbitMQ时会取到该消息的headers与Exchange绑定时指定的键值对进行匹配;如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers属性是一个键值对,可以是Hashtable,键值对的值可以是任何类型。而fanout,direct,topic 的路由键都需要要字符串形式的。匹配规则x-match有下列两种类型:x-match = all :表示所有的键值对都匹配才能接受到消息;x-match = any :表示只要有键值对匹配就能接受到消息。
事务的实现主要是对信道(Channel)的设置,主要的方法有三个:
channel.txSelect()声明启动事务模式;
channel.txComment()提交事务;
channel.txRollback()回滚事务;
消费需要注意:
Confirm发送方确认模式使用和事务类似,也是通过设置Channel进行发送方确认的。提供三个Confirm的三种实现方式:
channel.waitForConfirms()普通发送方确认模式
channel.confirmSelect();
// 方式消息Api
channel.basicPublish();
if (channel.waitForConfirms()) {
System.out.println("消息发送成功" );
}
channel.waitForConfirmsOrDie()批量确认模式
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
// 循环调用发送消息api
channel.basicPublish();
}
// 直到所有信息都发布,只要有一个未确认就会IOException
channel.waitForConfirmsOrDie();
System.out.println("全部执行完成");
channel.addConfirmListener()异步监听发送方确认模式
// 开启发送方确认模式
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
// 发送消息api
channel.basicPublish();
}
//异步监听确认和未确认的消息
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("未确认消息,标识:" + deliveryTag);
}
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println(String.format("已确认消息,标识:%d,多个消息:%b", deliveryTag, multiple));
}
});
发送和消费消息成功失败的时候显示调用Ack与Nack方法。特别常用,一般使用手动ACK机制的时候会开启消息持久化,要特别注意的是在声明队列和消息的时候需要设置持久化参数为true。
RabbitMQ是一个分布式系统,这里面有几个抽象概念。
1)、broker:每个节点运行的服务程序,功能为维护该节点的队列的增删以及转发队列操作请求。
2)、master queue:每个队列都分为一个主队列和若干个镜像队列。
3)、mirror queue:镜像队列,作为master queue的备份。在master queue所在节点挂掉之后,系统把mirror queue提升为master queue,负责处理客户端队列操作请求。注意,mirror queue只做镜像,设计目的不是为了承担客户端读写压力。
如上图所示,集群中有两个节点,每个节点上有一个broker,每个broker负责本机上队列的维护,并且borker之间可以互相通信。
集群中有两个队列A和B,每个队列都分为master queue和mirror queue(备份)。那么队列上的生产消费怎么实现的呢?
队列消费:
如上图有两个consumer消费队列A,这两个consumer连在了集群的不同机器上。RabbitMQ集群中的任何一个节点都拥有集群上所有队列的元信息,所以连接到集群中的任何一个节点都可以,主要区别在于有的consumer连在master queue所在节点,有的连在非master queue节点上。
因为mirror queue要和master queue保持一致,故需要同步机制,正因为一致性的限制,导致所有的读写操作都必须都操作在master queue上(想想,为啥读也要从master queue中读?和数据库读写分离是不一样的。),
然后由master节点同步操作到mirror queue所在的节点。
即使consumer连接到了非master queue节点,该consumer的操作也会被路由到master queue所在的节点上,这样才能进行消费。
队列生产:
原理和消费一样,如果连接到非 master queue 节点,则路由过去。所以,到这里小伙伴们就可以看到 RabbitMQ的不足:由于master queue单节点,导致性能瓶颈,吞吐量受限。虽然为了提高性能,内部使用了Erlang这个语言实现,但是终究摆脱不了架构设计上的致命缺陷。