一、配置文件
1、消息生产者配置文件:
spring:
rabbitmq:
host: 127.0.0.1 #rabbit所在的主机地址
port: 5672 #端口号
username: admin #用户名,在15672上配置
password: admin #密码,在15672上配置
virtual-host: /user #占用的虚拟主机地址,在15672上配置
template:
retry:
enabled: true #开启重试
initial-interval: 10000ms #重试初始间隔时间
max-interval: 80000ms #重试最大间隔时间,超过则放弃重试
multiplier: 2 #重试间隔时间增长指数
publisher-confirms: true #消息生产者验证消息能否正常传递到exchange也就是最外的交换机
publisher-returns: true #消息生产者验证消息能否在exchange内正常传递到消息队列
listener:
simple:
acknowledge-mode: manual #消息消费者(监听器)对消息的确认模式auto:自动ack模式/manual:手动ack模式)(这里常设置为手动ack模式,原因下面说明)
额外说明:exchange和queue共同组成broker
2、消息消费者配置文件:
rabbitmq:
host: 127.0.0.1
port: 5672
username: admin
password: admin
virtual-host: /user
3、在消息生产者模块下,添加对于消息生产者验证消息能否传递到交换机和消息队列的回调方法:
public class RabbitTemplateConfig implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
//消息传到exchange最外也就是交换机后,执行的回调方法
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("消息唯一标识:"+correlationData);
System.out.println("确认结果:"+ ack);//true:能传到交换机;false:不能传到交换机
System.out.println("失败原因:"+ cause);
}
//消息在不能传到消息队列的情况,如消息队列没有和交换机匹配的路由后,执行的回调方法
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("消息主体 message : "+message);
System.out.println("消息主体 message : "+replyCode);
System.out.println("描述:"+replyText);
System.out.println("消息使用的交换器 exchange : "+exchange);
System.out.println("消息使用的路由键 routing : "+routingKey);
}
}
二、消息生产者:
1、点对点类型(包含simple queue模式、work模式):
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());//设置消息转换器为高效率的Jackson2JsonMessageConverter
User user = User.getUser();//发送的信息
String queueName = "qqq";//消息队列名称
rabbitTemplate.convertAndSend(queueName,user);
2、发布订阅类型(包含fanout模式、direct模式、topic模式):
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());//设置消息转换器为高效率的Jackson2JsonMessageConverter
User user = User.getUser();//发送的信息
String exchange = "spring.test.exchange";//交换机名称
String routingKey = "test.test";//路由数据(fanout模式不发送,topic模式可带*/#)
rabbitTemplate.convertAndSend(exchange,routingKey,user);
3、对于消息生产者注意以下问题:
(1)RabbitTemplate与AmqpTemplate两者的功能是一样的,都可以进行消息队列的消息发布,但AmqpTemplate没有RabbitTemplate的setMessageConverter方法,无法修改默认消息转换器,需要在消息生产者的模块下加入配置类,代码如下:
@Configuration
public class RabbitConfig {
@Bean
public Jackson2JsonMessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
三、消息消费者:
1、点对点类型(包含simple queue模式、work模式):
@Component
public class RMQListener {
@RabbitListener(queuesToDeclare = @Queue(name = "qqq",durable = "true"))
@RabbitHandler
public void listen(User user, Channel channel, @Payload String body, @Header(AmqpHeaders.DELIVERY_TAG) long tag){
try {
/**
* 在手动ack的模式下,进行消息的确认,在配置文件开启消费者的手动ack确认模式,然后再在此处进行手动ack操作,最大目的只为了防止直接自动ack时,如果程序出现异常,则rabbitmq的消息队列内的消息会直接被清除,而采用手动ack模式可以将消息保留。只有程序在没有异常的情况下运行到当前监听器下,再对消息队列内传递的消息进行ack处理。这样就不会因为异常而丢失队列内的数据信息。
*/
channel.basicAck(tag,false);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("result:" + user);
System.out.println("body:" + body);
System.out.println("headers:"+headers);
}
}
2、发布订阅类型(包含fanout模式、direct模式、topic模式):
@Component
public class RMQListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "user.queue",durable = "true"),
exchange = @Exchange(name = "spring.test.exchange",type = ExchangeTypes.TOPIC),
key = {"test.text"}
))
@RabbitHandler
public void listen(User user, Channel channel, @Payload String body, @Header(AmqpHeaders.DELIVERY_TAG) long tag){
try {
/**
* 在手动ack的模式下,进行消息的确认,在配置文件开启消费者的手动ack确认模式,然后再在此处进行手动ack操作,最大目的只为了防止直接自动ack时,如果程序出现异常,则rabbitmq的消息队列内的消息会直接被清除,而采用手动ack模式可以将消息保留。只有程序在没有异常的情况下运行到当前监听器下,再对消息队列内传递的消息进行ack处理。这样就不会因为异常而丢失队列内的数据信息。
*/
channel.basicAck(tag,false);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("result:" + user);
System.out.println("body:" + body);
System.out.println("headers:"+headers);
}
}
并且需要在消息消费者的模块下也直接添加消息转换器配置类,代码如下:
@Configuration
public class RabbitConfig {
@Bean
public Jackson2JsonMessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
3、对于消息消费者注意以下问题:
(1)durable设置为true的作用,是为了维持消息持久化。类似于redis的持久化一样,为了防止RabbitMQ的宕机,可以将消息持久化到本地磁盘。
(2)发布订阅类型下,可以对交换机设置fanout/direct/topic三种类型,路由信息也要随之更改。
四、统一注意问题:
1、消息生产者和消息消费者如果传递的信息是一个复杂实体类,那么不可以在各个模块分别建一个完全一样的实体类,因为这样序列号是不能保证一致的。所以一定要保证引用同一个实体类,也就是抽取一个公共的实体类模块,以供消息生产者模块和消息消费者模块共同调用。
2、注意消息生产者和消息消费者两个模块都要设置消息转换器,否则数据会转换异常。
3、@RabbitHandler的作用,即是@RabbitListener注解监听到消息后,会直接调用的方法。
4、@Payload 和 @Headers 注解可以获取消息中的 body 与 headers 信息,@Header()可直接获取headers内对应名字的信息。
5、amqp_deliveryTag(AmqpHeaders.DELIVERY_TAG)(唯一标识 ID):rabbitmq在与生产者和消费者建立connection也就是TCP连接后,为了防止频繁操作消息对TCP连接的影响,会接着创建AMQP信道(Channel),这样就可以保持TCP连接的复用,对Channel进行频繁操作。其中,每个channel会被指定一个唯一的ID。因此,当一个消费者向RabbitMQ注册后,会建立一个Channel,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel。
6、手动ack的必要性:对于比较重要的信息,手动ack可以防止数据丢失。在在配置文件开启消费者的手动ack确认模式,然后在手动ack的模式下,在消息消费者(监听器)中单独进行消息的确认、进行手动ack操作,最大目的只为了防止直接自动ack时,如果程序出现异常,则rabbitmq的消息队列内的消息会直接被清除,而采用手动ack模式可以将消息保留。只有程序在没有异常的情况下运行到消息消费者监听器下,再对消息队列内传递的消息进行ack处理。这样就不会因为异常而丢失队列内的数据信息。(且如果在配置文件中设置了手动ack模式,却不在消息消费者监听器中进行手动ack的处理,那么由于这已经是收到消息却还不ack处理,消息队列内的消息就会越来越多。)