docker pull rabbitmq
docker run -itd --name rabbitmq -p 5673:5672 -p 15673:15672 rabbitmq:latest
或者可设置默认用户名和密码
docker run -itd --name rabbitmq -e RABBITMQ_DEFAULT_USER mrtuzi -e RABBITMQ_DEFAULT_PASS 123456 -p 5673:5672 -p 15673:15672 rabbitmq:latest
docker exec -it 容器id /bin/bash
rabbitmq-plugins enable rabbitmq_management
http://ip:15673,用户名和密码默认是guest
默认情况下,它会将消息依次推送给订阅队列的每一个消费者,并不会考虑消费者是否处理完消息,可能会造成消息堆积。
spring:
rabbitmq:
host: 192.168.174.128
port: 5673
username: guest
password: guest
listener:
simple:
# 每次只能获取1条消息,处理完才能获取下一条
prefetch: 1
真正生产环境都会经过exchange来发送消息,而不是直接发送到消息队列。
交换机类型:
direct交换机会将接收到的消息根据规则发送到消息队列。
topic交换机和direct类似,但是direct只能是完整的词,而topic交换机的routingKey可以是多个词,每个词以英文点**.**隔开。
bindingKey通配符:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
spring:
rabbitmq:
host: 192.168.174.128
port: 5673
username: guest
password: guest
@Resource
RabbitTemplate rabbitTemplate;
@Test
void testDirect() {
String msg = "hello world order";
String exchange = "direct.ex";
rabbitTemplate.convertAndSend(exchange, "order", msg);
log.info("发送成功");
}
队列和交换机的绑定可以使用配置类配置,也可以使用注解配置。
配置类配置
@Configuration
public class DirectConfig {
@Bean
public DirectExchange directExchange() {
return ExchangeBuilder.directExchange("demo.direct").build();
}
@Bean(name = "directQueue")
public Queue directQueue() {
return QueueBuilder.durable("demo.direct.queue").build();
}
@Bean(name = "directBinding")
public Binding directBinding(@Qualifier("directQueue") Queue queue, DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("red");
}
}
@RabbitListener(queues = {"demo.direct.queue"})
public void listenerSimpleQueueUser(String msg) {
log.info("接收到消息2: {}", msg);
}
注解配置
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "direct.ex", type = ExchangeTypes.DIRECT),
key = {"blue", "red"}
)
})
public void listenerDirectQueue(String msg) {
log.info("接收到消息direct.queue2: {}", msg);
}
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
当前重连是阻塞式重连,会阻塞当前业务,可异步执行发送消息。
spring:
rabbitmq:
host: 192.168.174.128
port: 5673
username: guest
password: guest
# 超时时间
connection-timeout: 1s
template:
retry:
# 开启重连
enabled: true
# 失败后初始等待时间
initial-interval: 1000ms
# 失败后下次等待时间倍数, 下次等待时长=initial-interval * multiplier
multiplier: 1
# 最大重试次数
max-attempts: 3
rabbitmq提供了Publisher Confirm和Publisher Return确认机制。开启后,MQ收到消息后会返回确认消息给生产者,有几种情况:
生产者确认消息需要额外的网络开销,尽量不使用;如果要使用不需要开启return机制,一般路由失败是代码问题;对nack消息可以进行重试,可以记录失败异常消息。
spring:
rabbitmq:
host: 192.168.174.128
port: 5673
username: guest
password: guest
# 开始confirm机制, none: 关闭, simple: 同步阻塞等待MQ回执, correlated: 异步回调等待MQ回执
publisher-confirm-type: correlated
# 开始return机制,一般不需要开启
publisher-returns: true
@Test
void testSend() {
String queueName = "demo.queue2";
JsonResult jsonResult = new JsonResult();
jsonResult.setMsg("成功了啊");
jsonResult.setCode("200");
CorrelationData correlationData = new CorrelationData();
correlationData.getFuture().addCallback(new ListenableFutureCallback<>() {
@Override
public void onFailure(Throwable ex) {
// future发生异常时触发,基本不会触发
}
@Override
public void onSuccess(CorrelationData.Confirm result) {
// 回执处理
if (result.isAck()) {
// 发送消息成功
log.info("发送消息成功");
} else {
// 发送消息失败
log.error("发送消息失败");
}
}
});
rabbitTemplate.convertAndSend(queueName, jsonResult, correlationData);
log.info("发送成功");
}
如果同时开启持久化和消息确认机制,MQ只有在消息完成持久化才发送ack回执。
从3.6.0版本开始,增加了Lazy Queue,惰性队列。
3.12版本后,所有队列都是Lazy Queue模式,无法更改。
为了确认消费者是否成功处理消息,MQ提供了消费者确认机制,(Consumer Acknowledgement)。当消费者处理完消息后,应该向MQ发送一个回执,告诉MQ自己是否处理完成。
三种回执:
spring:
rabbitmq:
host: 192.168.174.128
port: 5673
username: guest
password: guest
# 开始confirm机制, none: 关闭, simple: 同步阻塞等待MQ回执, correlated: 异步回调等待MQ回执
publisher-confirm-type: none
# 开始return机制
publisher-returns: false
# 超时时间
connection-timeout: 1s
template:
retry:
# 开启重连
enabled: true
# 失败后初始等待时间
initial-interval: 1000ms
# 失败后下次等待时间倍数, 下次等待时长=initial-interval * multiplier
multiplier: 1
# 最大重试次数
max-attempts: 3
listener:
simple:
prefetch: 1
# 消费者确认机制
acknowledge-mode: none
当消费者异常后,消息会不断requeue重新入队到队列,再次发送给消费者,然后再次异常,再次重新入队,无限循环,导致MQ的消息处理飙升,带来不必要的压力。
可以使用spring的重试机制,防止无限重试。
spring:
rabbitmq:
host: 192.168.174.128
port: 5673
username: guest
password: guest
# 开始confirm机制, none: 关闭, simple: 同步阻塞等待MQ回执, correlated: 异步回调等待MQ回执
publisher-confirm-type: none
# 开始return机制
publisher-returns: false
# 超时时间
connection-timeout: 1s
template:
retry:
# 开启重连
enabled: true
# 失败后初始等待时间
initial-interval: 1000ms
# 失败后下次等待时间倍数, 下次等待时长=initial-interval * multiplier
multiplier: 1
# 最大重试次数
max-attempts: 3
listener:
simple:
prefetch: 1
acknowledge-mode: auto
retry:
# 开启重试机制
enabled: true
# 初始的失败等待时长,1秒
initial-interval: 1000ms
# 失败下次等待时间倍数
multiplier: 1
# 重试次数
max-attempts: 2
# true 无状态;false 有状态。如果业务包含事务,则改为false
stateless: true
重试失败处理策略
开启重试后,重试多次依旧失败,则可以通过MessageRecoverer接口来处理,包含三种实现:
/**
* 只有开启消费失败重试才生效,所以使用ConditionalOnProperty判断开启重试才启动当前配置类
*/
@Configuration
@ConditionalOnProperty(prefix = "spring.rabbitmq.listener.simple.retry", name = "enabled", havingValue = "true")
public class ErrorExchangeConfig {
@Bean
public DirectExchange errorExchange() {
return ExchangeBuilder.directExchange("error.direct").build();
}
@Bean(name = "errorQueue")
public Queue directQueue() {
return QueueBuilder.durable("error.queue").build();
}
@Bean(name = "errorBinding")
public Binding directBinding(@Qualifier("errorQueue") Queue queue, DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("error");
}
@Bean
public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate) {
return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
}
}
**总结:**保证消费者的可靠性,需要开启消费者消息确认机制为auto,让Spring确认消息处理成功后返回ack,异常时返回nack;开启失败重试机制,并设置MessageRecoverer,多次重试失败后将消息投递到用来专门处理异常的交换机,由人工处理。
@Bean
public MessageConverter messageConverter() {
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setCreateMessageIds(true);
return converter;
}
延迟消息