基础概念
在springboot项目中加入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
依赖即可。 看起来是一个简单的操作,实际上maven又自动加入了两个重要的依赖:
<!--spring对于message所作的抽象-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>5.1.5.RELEASE</version>
<scope>compile</scope>
</dependency>
<!--spring整合rabbit所需要的jar-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.4.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>http-client</artifactId>
<groupId>com.rabbitmq</groupId>
</exclusion>
</exclusions>
</dependency>
- 一般spring开发的整合框架命名为spring-xxx;我们写的模块想要和spring整合也可以做一个xxx-spring的单独的jar;类似的命名有shiro-spring、mybatis-spring等。
- 基础项目和衍生项目。spring的基础模块groupId为org.springframework,artifactId为spring-xxx;package为org.springframework.xxx。spring的衍生项目采用groupId为org.springframework.xxx,artifactId为spring-xxx;package为org.springframework.xxx的形式。Jar的管理是一个复杂的事情,什么时候在老的项目中增加新的jar;什么时候启动新的项目;这些事情不能由一个人去决定,应该有组织有预谋的开始。纵观spring projects,顶级项目一般决定着公司的发展方向;子项目是对这个方向的支持。
Queue
队列有三个参数:
- name:队列的名字
- durable:是否持久化
- exclusive:一旦声明这个queue的connection断了,这个队列就删除,包括消息。
- autoDelete:这个队列不再使用的话就broker就删除;Broker重启同样会删除.
原生Rabbitmq Demo
原生的Rabbitmq的生产者如下: Productor.java
public void start() throws IOException, TimeoutException, InterruptedException {
String exchangeName = "exchange-name";
String routingKey = "routingKey";
String queueName = "queue";
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接属性
//1.获取链接
Connection connection = connectionFactory.newConnection();
//2.开启通道
Channel channel = connection.createChannel();
//3.创建交换机和队列
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true);
//4.创建队列
String queue = channel.queueDeclare(queueName, false, false, false, null).getQueue();
//5. 绑定队列
channel.queueBind(queue, exchangeName, routingKey);
//6. 发送消息
for (int i = 0; i < 200; i++) {
String s = "Hello-world" + i;
channel.basicPublish(exchangeName, routingKey, null, s.getBytes());
System.out.println("发送成功 " + s);
Thread.sleep(1000L);
}
//7:关闭连接
channel.close();
connection.close();
}
Consumer.java
public void start() throws IOException, TimeoutException, InterruptedException {
String queueName = "queue";
ConnectionFactory connectionFactory = new ConnectionFactory();
//1.获取链接
Connection connection = connectionFactory.newConnection();
//2.开启通道
Channel channel = connection.createChannel();
//3.声明队列
String queue = channel.queueDeclare(queueName, false, false, false, null).getQueue();
for (int i = 0; i < 200; i++) {
channel.basicConsume(queue, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
long deliveryTag = envelope.getDeliveryTag();
//4. 确认
channel.basicAck(deliveryTag, false);
System.out.println(routingKey);
System.out.println(routingKey + "-" + contentType + "-" + deliveryTag + "-" + new String(body));
}
});
Thread.sleep(1000L);
}
//5:关闭连接
channel.close();
connection.close();
}
原生的代码有如下弊端:
- 模板代码。在生产者端步骤1、2、7与消费者端步骤1、2、5重复。
- 消息为二进制,只能手动解码。
- 事务支持不友好
- channel不停的打开和关闭,需要一定的管理
在spring整合中,需要解决掉以上三个问题。
spring.rabbitmq.addresses=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
- spring自己抽象了ConnectionFactory接口,可以增加ConnectionListener、发布确认机制
- Spring的Connection和原生Connection的区别是:增加事务支持、BlockedListener。
- AmqpTemplate,发送消息、将一个Bean转成一个Message发送;接收消息、接收消息并转化、接受并回复。RabbitOperations,增加了Channel级别的回调、获取ConnectionFactory。AmqpAdmin管理Exchange、Queue、Binding的。
生产者生产消息的确认和返回机制
#生产者发送确认
spring.rabbitmq.publisher-confirms=true #生产者可以判断消息是否发送到了broker
spring.rabbitmq.publisher-returns=true #生产者可以判断消息是否发送到了queue
#消费者自动确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
- RabbitTemplate支持确认和返回机制。
- 开启返回机制,RabbitTemplate需要设置mandatory=true,CachingConnectionFactory的publisherReturns=true,RabbitTemplate#setReturnCallback注册回调。
- 开启确认机制,CachingConnectionFactory 的publisherConfirms=true;RabbitTemplate#setConfirmCallback注册确认器。一个RabbitTemplate仅仅支持一个ConfirmCallback和ReturnCallback。mandatory标志告诉broker代理服务器至少将消息route到一个队列中,否则就将消息return给发送者(调用ReturnCallback)。 只要交换机接收到消息,ConfirmCallback回调就会被执行,不管消息是否被存储到队列中。
消费者消费消息的确认机制
- 消息接收确认,通过ACK完成,分手动、自动两种。自动确认在Rabbitmq发送给消费者后立即确认,存在丢失可能(消费端抛异常,即使Spring回滚也会丢失)。
- 手动确认,消费者调用ack(确认)\nack(否定,会重新进入队列)\reject(拒绝,丢弃不会重回队列)方法进行确认,手动更自由一些。
- 未被ACK的消息会被发送给下一个消费者。
- 某个消息忘记ACK了,Rabbitmq不会再发送数据给它,因为RBmq认为该消费者消费能力有限。
- 消息确认模式有:AcknowledegMode.NONE(自动确认)、AcknowledegMode.AUTO(根据情况确认)、AcknowledgeMode.MANUAL(手动确认)。
- AcknowledegMode.AUTO的处理逻辑:如果消息成功被消费,则自动确认。当抛出AmqpRejectAndDontRequeueException 异常的时候,则消息会被拒绝,且 requeue = false(不重新入队列)。当抛出ImmediateAcknowledgeAmqpException 异常,则消费者会被确认。其他的异常,则消息会被拒绝,且 requeue = true(如果此时只有一个消费者监听该队列,则有发生死循环的风险,多消费端也会造成资源的极大浪费,这个在开发过程中一定要避免的)。可以通过 setDefaultRequeueRejected(默认是true)去设置。
- 当消息没有被确认,就会不停的返回消息队列,直消息被正常消费。当队列中堆积很多没有被消费的消息时,会造成内存溢出。处理方法有两种:1. 消费者自己try-catch 2. 在spring配置文件中增加尝试次数配置,尝试几次后将消息删除或者其他的处理。
spring.rabbitmq.listener.retry.enable=true
spring.rabbitmq.listener.retry.max-attempts=5
消息可靠总结
- 持久化:exchange、queue、message要持久化
- 消息确认:生产确认、消费确认。
- 启动消费返回(@ReturnList注解,生产者就可以知道哪些消息没有发出去)。!!!这条待验证。
死信队列(DLX,dead-letter-exchange)
消息死亡的原因:
- 消息被拒绝 (basic.reject / basic.nack) 并且 reQueue=false
- 消息 TTL 过期
- 队列达到最大长度