AMQP 协议
- AMQP is the Internet Protocol for Business Messaging
- RabbitMQ is the most widely deployed open source message broker
架构及相关概念
- Producer 消息生产者,封装消息内容和消息属性
- Connection
网络连接
,一般底层为TCP连接 - Channel 使用
通道
来复用网络连接
,避免不断的建立连接和销毁连接,这样做系统开销比较大 - Virtual Host
虚拟主机
,拥有自己的消息队列、交换器、绑定和权限机制 - Exchange
交换器
,接收来自生产者
发送的消息并按照路由表路由
到消息队列
中 - Binding
绑定
,用于消息队列
和交换机
之间的关联;保存路由规则,相当于路由表。 - Queue 消息队列,用于存储消息实体
- Consumer 消费者
Exchange类型
- 简单模式
- Work 消息模型
- 生产者客户端
try (Connection conn = ConnectionUtils.getConnection(); Channel channel = conn.createChannel()) {
// 声明并创建一个队列;如果队列已存在,则使用这个队列
// 第一个参数:队列名称ID
// 第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
// 第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
// 第四个参数:是否自动删除,false代表连接停掉后不自动删除掉这个队列
channel.queueDeclare("queue1", false, false, false, null);
channel.basicPublish("", "queue1", null, "helloworld".getBytes());
}
- 消费者客户端
Connection conn = ConnectionUtils.getConnection();
Channel channel = conn.createChannel();
//如果不写basicQos(1),则自动MQ会将所有请求平均发送给所有消费者
//basicQos, MQ不再对消费者一次发送多个请求,而是消费者处理完一个消息后(确认后),在从队列中获取一个新的
channel.basicQos(1);//处理完一个取一个
//第一个参数:队列名
//第二个参数:代表是否自动确认收到消息,false代表手动编程来确认消息,这是MQ的推荐做法
//第三个参数:DefaultConsumer的实现类
channel.basicConsume("queue1", false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(" 消息id:" + envelope.getDeliveryTag() + " 消息内容:" + new String(body));
//false只确认签收当前的消息,设置为true的时候则代表签收该消费者所有未签收的消息
getChannel().basicAck(envelope.getDeliveryTag(), false);
}
});
通过 BasicQos 方法设置prefetchCount = 1。
这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理1个Message。
换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。
相反,它会将其分派给不是仍然忙碌的下一个Consumer。
值得注意的是:prefetchCount在手动ack的情况下才生效,自动ack不生效。
- Publish/subscribe 模型
每个队列都需要绑定到交换机上
Exchange(交换机)只负责转发消息,不具备存储消息的能力,
因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
Exchange类型
- Fanout:广播,将消息交给所有绑定到交换机的队列
- Direct:定向,把消息交给符合指定routing key 的队列
- Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
- Header:header模式取消routingkey,使用header中的 key/value(键值对)匹配队列。
Fanout:广播
// 消费者1
channel.queueDeclare("queue1", false, false, false, null);
channel.exchangeDeclare("exchange1", BuiltinExchangeType.FANOUT);
//queueBind用于将队列与交换机绑定
//参数1:队列名 参数2:交互机名 参数三:路由key(暂时用不到)
channel.queueBind("queue1", "exchange1", "");
channel.basicQos(1);
channel.basicConsume("queue1", false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1信息:" + new String(body));
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
// 消费者2
channel.queueDeclare("queue2", false, false, false, null);
channel.queueBind("queue2", "exchange1", "");
channel.basicQos(1);
channel.basicConsume("queue2", false, new DefaultConsumer(channel) {
// 同上
});
// 生产者
channel.basicPublish("exchange1", "", null, "hello world".getBytes());
Direct:定向
// 消费者1
channel.queueDeclare("queue3", false, false, false, null);
channel.exchangeDeclare("exchange3", BuiltinExchangeType.DIRECT);
channel.queueBind("queue3", "exchange3", "debug");
channel.queueBind("queue3", "exchange3", "info");
channel.basicQos(1);
channel.basicConsume("queue3", false, new DefaultConsumer(channel) {}
// 消费者2
channel.queueDeclare("queue4", false, false, false, null);
channel.queueBind("queue4", "exchange3", "warn");
channel.queueBind("queue4", "exchange3", "error");
channel.basicQos(1);
channel.basicConsume("queue4", false, new DefaultConsumer(channel) {}
// 生产者
List<String> keys = Arrays.asList("debug", "info", "warn", "error", "hello");
try (Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();) {
for (int i = 0; i < 10; i++) {
channel.basicPublish("exchange3", keys.get(i % keys.size()), null, keys.get(i % keys.size()).getBytes());
}
}