1.amqp协议介绍:
RabbitMQ是基于amqp协议的一种实现,学习rabbitmq需要先仔细学习amqp。
AMQP官网:http://www.amqp.org/product/architecture
其他一篇介绍很详细的博客:https://blog.csdn.net/weixin_37641832/article/details/83270778
2.rabbitmq核心概念:
2.1 Exchange:
2.2.1 exchange属性介绍:
属性名 | 备注 |
---|---|
name | ★ 交换机名 |
type | ★ 交换机类型:direct、topic、fanout、headers |
durability | ★ 是否需要持久化,true为持久化 |
auto Delete | ★ 当最后一个绑定到该交换机的队列删除,是否本身也自动删除 |
internal | 该exchange是否为rabbitmq内部使用,默认为false。绝大多数情况用不着,类如要扩展rabbitmq插件时可能会使用到。 |
arguments | 扩展参数,用于扩展amqp协议定制化使用 |
2.2.2 交换机类型介绍:
交换机类型 | 说明 |
---|---|
direct | 直连交换机,所有生产者发送到direct exchange的消息,会被转发到与routingKey有完全匹配关系的queue。RabbitMQ有默认的直连交换机:Default Exchange,可以直接使用。 |
topic | topic交换机,所有生产者发送到topic exchange的消息,都会被转发到所有关系routingKey指定topic的queue。exchange会将routingKey和某topic进行模糊匹配,队列需要绑定到某个topic。可以使用通配符进行模糊匹配,"#“匹配一个或多个词,”*"只能匹配一个词。 |
fanout | 不处理路由键,只需简单的将队列绑定到交换机上。生产者发送到交换机的消息,都会被转发到与该交换机有绑定的所有queue上。fanout交换机转发消息是最快的。 |
2.2.3 实现示例:
/**
* 第一个参数:交换机名
* 第二个参数:交换机类型(DIRECT,TOPIC,FANOUT,HEADERS)
* 第三个参数:durable,是否持久化
* 第四个参数:autoDelete,是否自动删除
* 第五个参数:arguments,扩展参数
*/
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true, false, MessageProperties.PERSISTENT_TEXT_PLAIN);
2.3 queue:
2.3.1 queue属性介绍:
属性名 | 备注 |
---|---|
name | ★ 队列名 |
durability | ★ 是否需要持久化,trurable为持久化,transient为不持久化。 |
auto Delete | ★ 当最后一个监听被移除之后,是否本身自动删除 |
arguments | 扩展参数,用于扩展amqp协议定制化使用 |
2.3.2 实现示例:
/**
* 第一个参数:队列名
* 第二个参数:durable,是否持久化
* 第三个参数:是否独占式获取消息
* 第四个参数:autoDelete,是否自动删除
* 第五个参数:arguments,扩展参数
*/
channel.queueDeclare(queueName, true, false, false, null);
2.4 binding:
2.4.1 概述:
指exchange和exchange、queue之间的绑定关系。
2.3.2 实现示例:
/**
* 第一个参数:队列名
* 第二个参数:交换机名
* 第三个参数:路由键
*/
channel.queueBind(queueName, exchangeName, routingKey);
2.5 Message:
2.5.1 概述:
指服务器和应用程序之间传送的数据,由Properties和Payload(body)组成。
- Properties中的属性:
content_type:传输协议
content_encoding:编码方式
priority:优先级
correlation_id:请求的唯一标识。
reply_to:rpc属性,
expiration:消息过期时间
message_id:消息的id
timestamp:消息的时间戳
…
- Payload(body):
即发送的消息内容。
2.5.2 实现实例:
//自定义扩展headers属性
Map<String, Object> headers = new HashMap<>();
headers.put("attr1", "扩展的属性1");
headers.put("attr2", "扩展的属性2");
//拼装消息properties
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.deliveryMode(2) //持久化模式
.contentEncoding("utf-8") //编码
.expiration("5000") //过期时间
.headers(headers) //headers扩展属性
.build();
//消息体Payload(Body)
String body = "hello rabbit!!! " + i;
//5.生产消息
channel.basicPublish(exchangeName, routingKey, properties, body.getBytes());
3.rabbitmq高级特性:
3.1 confirm监听机制:
3.1.1 概述:
确认监听机制:生产者投递消息后,如果broker收到消息,会给生产者发送一个应答。生产者根据应答,来确定消息是否正常发送到了broker。这是rabbitmq消息投递可靠性的核心保障!
3.1.2 流程图:
3.1.3 实现:
1.在channel上开启确认模式:channel.confirmSelect()
2.在channel上添加监听:addConfirmListener,监听成功或失败的返回结果,进行相应处理,如消息重发、做日志记录后续补偿等。
//6.指定消息投递模式:确认模式
channel.confirmSelect();
//7.设置confirm监听
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long l, boolean b) throws IOException {
System.out.println("---------- 在此ack!!------------");
}
@Override
public void handleNack(long l, boolean b) throws IOException {
System.out.println("---------- 在此noack!!------------");
}
});
3.2 return监听机制:
3.2.1 概述:
return监听机制:用于处理一些生产者发送消息到broker之后,根据exchange、routingKey不可路由到queue的消息。
3.2.2 流程图:
3.2.3 实现:
1.在channel上添加监听:addReturnListener,监听消息不可达时的通知。
2.在消息发送时,设置mandatory为true。
//4.设置return监听
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange,
String routingKey, AMQP.BasicProperties basicProperties, byte[] body) throws IOException {
System.out.println("收到不可路由的消息:");
System.out.println("replyCode: "+replyCode);
System.out.println("replyText: "+replyText);
System.out.println("exchange: "+exchange);
System.out.println("routingKey: "+routingKey);
System.out.println("basicProperties: "+basicProperties);
System.out.println("body: "+new String(body));
}
});
//7.生产消息
String msg = "hello rabbit!!! ";
// 注意这里的第三个参数:boolean mandatory 是否开启return通知。
// 当设置为true:该消息时不可路由的时,rabbitMQ会回调上面添加的returnListener方法;
// 当设置为false:该消息时不可路由的时,rabbitMQ会在broker端直接把该消息清除。
channel.basicPublish(exchangeName, routingKeyError, true, null, msg.getBytes());
3.3 ack机制:
3.3.1 概述:
消费端通过ack操作,来通知mq该消息是否正常接收到了。
3.3.2 实现:
public class MyConsumer extends DefaultConsumer {
private Channel channel;
public MyConsumer(Channel channel) {
super(channel);
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(consumerTag);
System.out.println(envelope.toString());
System.out.println(properties.toString());
System.out.println("消息内容:" + new String(body));
//手动ack
// channel.basicAck(envelope.getDeliveryTag(), false);
/**
* 手动nack
* 注意第三个参数:是否重回队列。
* 如果设为true,该消息会被rabbitmq重新放到队列的尾端,等待下次推送;
* 如果设为false,该消息不会重回队列,这时主要靠日志记录后续补偿处理。
* 一般都会把重回队列关闭。
*/
channel.basicNack(envelope.getDeliveryTag(), false, false);
}
}
3.4 消费端限流:
3.4.1 为何需要消费端限流:
假设有个场景,MQ服务器发生消息积压,有成千上万条待处理的消息,这时打开一个消费者客户端,大量的消息推送过来,单个客户端处理不过来,导致服务崩溃。
3.4.2 概述:
RabbitMQ提供一种qos(服务质量保证)功能,即在【非自动确认消息的前提】下,如果一定数目的消息未被确认前,不进行消费新的消息。
3.4.3 实现:
/**
* 消费端限流:
* 第一个参数:prefetchSize,消费的单条消息大小限制,如果为0表示不限制。
* 第二个参数:prefetchCount,告诉RabbitMQ不要一次给消费者推送多余N个消息,直到这N个消息已全部被ack了。
* 第三个参数:global,true表示此设置应用于channel级别,false表示应用于consumer级别。
*/
channel.basicQos(0, 1, false);
//9.消费消息
// 一定注意此处,ack设置为手动ack,否则限流不生效
channel.basicConsume(queueName, false, consumer);
3.5 ttl:
3.5.1 概述:
Rabbitmq中可以给消息/队列设置存活时间,当该消息被转发到队列时开始计时,如果没有在该存活时间内被消费掉,消息会被自动清除。
3.5.2 实现:
//拼装消息properties
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.deliveryMode(2) //持久化模式
.contentEncoding("utf-8") //编码
.expiration("5000") //ttl过期时间
.headers(headers) //headers扩展属性
.build();
//5.生产消息
channel.basicPublish(exchangeName, routingKey, properties, msg.getBytes());
3.6 死信队列:
3.6.1 概述:
死信队列:DLX,Dead-Letter-Exchange
利用死信队列,当消息在一个队列中变成死信(dead message)时,能被重新publish到另一个exchange中。
- 消息变成死信有以下几种情况:
1.消息ttl(存活时间)过期
2.消息被拒绝(basic.nack/basic.reject),并没有没重回队列(requeue=false)
3.队列达到最大长度
- 死信队列说明:
1.DLX也是个正常的exchange,和一般的exchange没有区别,只不过它被绑定到某个queue上,作为死信转发使用。
2.当与它绑定的queue中有死信产生时,rabbitmq会自动将该死信转发给该exchange,进而被路由到另一队列。
3.可以监听后面这个队列作相应处理。
3.6.2 实现:
1.设置一个死信队列:dlx.exchange
2.给正常使用的一个队列,加上arguments.put(“x-dead-letter-exchange”, “dlx.exchange”)
这样当该队列有死信消息时,就会被publish到dlx.exchange上。
//4.声明一个死信队列,该步骤跟声明一个正常队列一样。
channel.exchangeDeclare("dlx.exchange", BuiltinExchangeType.TOPIC, true, false, null);
channel.queueDeclare("dlx.queue", true, false, false, null);
channel.queueBind("dlx.queue", "dlx.exchange", "#");
//5.声明要正常使用的exchange、queue、binding
String exchangeName = "test_dead_exchange";
String queueName = "test_dead_queue";
String routingKey = "test.dead";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, false, false, null);
/**
* 6.注意在此步骤:
* 要给此正常使用的queue添加一个arguments,设置"x-dead-letter-exchange"为刚才声明的死信exchange名
*/
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", "dlx.exchange");
channel.queueDeclare(queueName, false, false, false, arguments);
channel.queueBind(queueName, exchangeName, routingKey);