消息确认机制
消息确认分为两部分: 生产确认 和 消费确认。
生产确认: 生产者生产消息后,将消息发送到交换机,触发确认回调;交换机将消息转发到绑定队列,若失败则触发返回回调。
消费确认: 默认情况下消息被消费者从队列中获取后即发送确认,不管消费者处理消息时是否失败,不需要额外代码,但是不能保证消息被正确消费。我们增加手动确认,则需要代码中明确进行消息确认。
在配置文件中添加:
spring:
profiles:
active: dev
#线程池配置
task:
pool:
corePoolSize : 1000
maxPoolSize : 2000
keepAliveSeconds : 60
queueCapacity : 1000
rabbitmq:
template:
# 使用return-callback时必须设置mandatory为true
mandatory: true
# 消息发送到交换机确认机制,是否确认回调
publisher-confirms: true
# 消息发送到交换机确认机制,是否返回回调
publisher-returns: true
listener:
simple:
# 并发消费者初始化值
concurrency: 5
# 最大值
max-concurrency: 10
# 每个消费者每次监听时可拉取处理的消息数量
prefetch: 20
# 确认模式设置为手动签收
acknowledge-mode: manual
1. 引入pom文件
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.5.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.mq-amqp</groupId>
<artifactId>mq-amqp-client</artifactId>
<version>1.0.4</version>
</dependency>
2. bootstrap.yml
spring:
profiles:
active: dev
#线程池配置
task:
pool:
corePoolSize : 1000
maxPoolSize : 2000
keepAliveSeconds : 60
queueCapacity : 1000
rabbitmq:
template:
# 使用return-callback时必须设置mandatory为true
mandatory: true
# 消息发送到交换机确认机制,是否确认回调
publisher-confirms: true
# 消息发送到交换机确认机制,是否返回回调
publisher-returns: true
listener:
simple:
# 并发消费者初始化值
concurrency: 5
# 最大值
max-concurrency: 10
# 每个消费者每次监听时可拉取处理的消息数量
prefetch: 20
# 确认模式设置为手动签收
acknowledge-mode: manual
3. RabbitConfig
package com.cecurs.usercenter.common.rabbit;
import com.cecurs.usercenter.common.config.Config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@Slf4j
public class RabbitConfig {
//资源owner账户 ID 信息
private static final long RESOURCE_OWNER_ID =1594045111309684L;
@Autowired
private RabbitProperties rabbitProperties;
@Autowired
private Config config;
@Bean
public ConnectionFactory getConnectionFactory() {
log.info("mq host: " + config.getAliyunHost() + " port: " + config.getAliyunPort() +" vhost: "+config.getAliyunVirtualHost());
log.info("mq username: "+config.getAliyunUsername() + " mq pwd: " +config.getAliyunPassword());
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory =
new com.rabbitmq.client.ConnectionFactory();
rabbitConnectionFactory.setHost(config.getAliyunHost());
rabbitConnectionFactory.setPort(config.getAliyunPort());
rabbitConnectionFactory.setVirtualHost(config.getAliyunVirtualHost());
AliyunCredentialsProvider credentialsProvider = new AliyunCredentialsProvider(
config.getAliyunUsername(), config.getAliyunPassword(), RESOURCE_OWNER_ID);
rabbitConnectionFactory.setCredentialsProvider(credentialsProvider);
rabbitConnectionFactory.setAutomaticRecoveryEnabled(true);
rabbitConnectionFactory.setNetworkRecoveryInterval(5000);
ConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitConnectionFactory);
((CachingConnectionFactory)connectionFactory).setPublisherConfirms(rabbitProperties.isPublisherConfirms());
((CachingConnectionFactory)connectionFactory).setPublisherReturns(rabbitProperties.isPublisherReturns());
return connectionFactory;
}
/**
* 创建队列
* @return
*/
@Bean
public Queue create() {
return new Queue(config.getAliyunQueueName());
}
/**
* 创建同步用户的队列
* @return
*/
@Bean
public Queue synchroUserQueue() {
return new Queue("synchroUserCenterQueue");
}
/**
* 创建新增用户的队列
* @return
*/
@Bean
public Queue addUserQueue() {
return new Queue("addUserCenterQueue");
}
/**
* 创建广播模式队列 收银台
* @return
*/
@Bean
public Queue queueFirst() {
return new Queue("fanout.first");
}
/**
* 创建广播模式队列 云卡
* @return
*/
@Bean
public Queue queueSecond() {
return new Queue("fanout.second");
}
/**
* 创建广播模式队列 玺商街
* @return
*/
@Bean
public Queue queueThird() {
return new Queue("fanout.third");
}
@Bean
FanoutExchange fanoutExchange() {
return new FanoutExchange(config.getAliyunExchange());
}
/**
* 第一个队列绑定到 Fanout 交换机
* @param queueFirst
* @param fanoutExchange
* @return
*/
@Bean
Binding bindingExchangeFirst(Queue queueFirst,FanoutExchange fanoutExchange) {
return BindingBuilder.bind(queueFirst).to(fanoutExchange);
}
/**
* 第二个队列绑定到 Fanout 交换机
* @param queueSecond
* @param fanoutExchange
* @return
*/
@Bean
Binding bindingExchangeSecond(Queue queueSecond, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(queueSecond).to(fanoutExchange);
}
/**
* 第三个队列绑定到 Fanout 交换机
* @param queueThird
* @param fanoutExchange
* @return
*/
@Bean
Binding bindingExchangeThird(Queue queueThird, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(queueThird).to(fanoutExchange);
}
/**
* 申明队列
*
* @return
*/
@Bean
public Queue queue() {
Map<String, Object> arguments = new HashMap<>(4);
return new Queue("queue-rabbit-springboot-advance5", true, false, false, arguments);
}
@Bean
public Exchange exchange() {
Map<String, Object> arguments = new HashMap<>(4);
return new DirectExchange("exchange-rabbit-springboot-advance5", true, false, arguments);
}
/**
* 申明绑定
*
* @return
*/
@Bean
public Binding binding() {
return BindingBuilder.bind(queue()).to(exchange()).with("product").noargs();
}
}
4. AliyunSender
package com.cecurs.usercenter.common.rabbit;
import com.alibaba.fastjson.JSON;
import com.cecurs.usercenter.common.config.Config;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class AliyunSender {
private static final Logger logger = LoggerFactory.getLogger(AliyunSender.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private Config config;
/**
* 如果消息没有到exchange,则confirm回调,ack=false
* 如果消息到达exchange,则confirm回调,ack=true
* exchange到queue成功,则不回调return
* exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)
*/
private final RabbitTemplate.ConfirmCallback confirmCallback = (correlationData, ack, cause) -> {
if (!ack) {
logger.error("消息发送失败:correlationData: {},cause: {}", correlationData, cause);
}else {
logger.info("消息发送成功:correlationData: {},ack: {}", correlationData, ack);
}
};
private final RabbitTemplate.ReturnCallback returnCallback = (message, replyCode, replyText, exchange, routeKey) ->
logger.error("消息丢失: exchange: {},routeKey: {},replyCode: {},replyText: {}", exchange, routeKey, replyCode, replyText);
/**
* 简单模式
* @param msg
*/
public void sendHandler(String msg) {
logger.info("新增用户推送 mq 消息队列,{}",JSON.toJSON(msg));
this.setSendConfig();
rabbitTemplate.convertAndSend("addUserCenterQueue",msg);
}
/**
* 简单消息模式,同步用户ID
* @param msg
*/
public void sendSynchroUserInfo(Object msg) {
logger.info("同步用户数据,推送 mq 消息队列,{}",JSON.toJSON(msg));
this.setSendConfig();
rabbitTemplate.convertAndSend("synchroUserCenterQueue",msg);
}
void setSendConfig(){
// 针对网络原因导致连接断开,利用retryTemplate重连3次
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(3));
rabbitTemplate.setRetryTemplate(retryTemplate);
rabbitTemplate.setConfirmCallback(confirmCallback);
rabbitTemplate.setReturnCallback(returnCallback);
}
/**
* 广播模式发布消息队列
* @param obj
*/
public void sendByFanout(Object obj) {
this.setSendConfig();
this.rabbitTemplate.convertAndSend(config.getAliyunExchange(),config.getAliyunRoutingKey(), obj);
}
}
import java.util.UUID;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CallBackSender implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(String topic, String message) {
rabbitTemplate.setConfirmCallback(this);
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
System.out.println("消息id:" + correlationData.getId());
// 用RabbitMQ发送MQTT需将exchange配置为amq.topic
this.rabbitTemplate.convertAndSend("amq.topic", topic, message, correlationData);
}
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("消息id:" + correlationData.getId());
if (ack) {
System.out.println("消息发送确认成功");
} else {
System.out.println("消息发送确认失败:" + cause);
}
}
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("return--message:" + new String(message.getBody()) + ",replyCode:" + replyCode
+ ",replyText:" + replyText + ",exchange:" + exchange + ",routingKey:" + routingKey);
}
}
总结下就是:
如果消息没有到exchange,则confirm回调,ack=false
如果消息到达exchange,则confirm回调,ack=true
exchange到queue成功,则不回调return
exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)
Spring Boot 整合RabbitMQ环境下的配置参数解释
★RabbitMQ
★Version: 1.5.13.RELEASE
★属性文件:org.springframework.boot.autoconfigure.amqp.RabbitProperties
★Config:
# base
spring.rabbitmq.host: 服务Host
spring.rabbitmq.port: 服务端口
spring.rabbitmq.username: 登陆用户名
spring.rabbitmq.password: 登陆密码
spring.rabbitmq.virtual-host: 连接到rabbitMQ的vhost
spring.rabbitmq.addresses: 指定client连接到的server的地址,多个以逗号分隔(优先取addresses,然后再取host)
spring.rabbitmq.requested-heartbeat: 指定心跳超时,单位秒,0为不指定;默认60s
spring.rabbitmq.publisher-confirms: 是否启用【发布确认】
spring.rabbitmq.publisher-returns: 是否启用【发布返回】
spring.rabbitmq.connection-timeout: 连接超时,单位毫秒,0表示无穷大,不超时
spring.rabbitmq.parsed-addresses:
# ssl
spring.rabbitmq.ssl.enabled: 是否支持ssl
spring.rabbitmq.ssl.key-store: 指定持有SSL certificate的key store的路径
spring.rabbitmq.ssl.key-store-password: 指定访问key store的密码
spring.rabbitmq.ssl.trust-store: 指定持有SSL certificates的Trust store
spring.rabbitmq.ssl.trust-store-password: 指定访问trust store的密码
spring.rabbitmq.ssl.algorithm: ssl使用的算法,例如,TLSv1.1
# cache
spring.rabbitmq.cache.channel.size: 缓存中保持的channel数量
spring.rabbitmq.cache.channel.checkout-timeout: 当缓存数量被设置时,从缓存中获取一个channel的超时时间,单位毫秒;如果为0,则总是创建一个新channel
spring.rabbitmq.cache.connection.size: 缓存的连接数,只有是CONNECTION模式时生效
spring.rabbitmq.cache.connection.mode: 连接工厂缓存模式:CHANNEL 和 CONNECTION
# listener
spring.rabbitmq.listener.simple.auto-startup: 是否启动时自动启动容器
spring.rabbitmq.listener.simple.acknowledge-mode: 表示消息确认方式,其有三种配置方式,分别是none、manual和auto;默认auto
spring.rabbitmq.listener.simple.concurrency: 最小的消费者数量
spring.rabbitmq.listener.simple.max-concurrency: 最大的消费者数量
spring.rabbitmq.listener.simple.prefetch: 指定一个请求能处理多少个消息,如果有事务的话,必须大于等于transaction数量.
spring.rabbitmq.listener.simple.transaction-size: 指定一个事务处理的消息数量,最好是小于等于prefetch的数量.
spring.rabbitmq.listener.simple.default-requeue-rejected: 决定被拒绝的消息是否重新入队;默认是true(与参数acknowledge-mode有关系)
spring.rabbitmq.listener.simple.idle-event-interval: 多少长时间发布空闲容器时间,单位毫秒
spring.rabbitmq.listener.simple.retry.enabled: 监听重试是否可用
spring.rabbitmq.listener.simple.retry.max-attempts: 最大重试次数
spring.rabbitmq.listener.simple.retry.initial-interval: 第一次和第二次尝试发布或传递消息之间的间隔
spring.rabbitmq.listener.simple.retry.multiplier: 应用于上一重试间隔的乘数
spring.rabbitmq.listener.simple.retry.max-interval: 最大重试时间间隔
spring.rabbitmq.listener.simple.retry.stateless: 重试是有状态or无状态
# template
spring.rabbitmq.template.mandatory: 启用强制信息;默认false
spring.rabbitmq.template.receive-timeout: receive() 操作的超时时间
spring.rabbitmq.template.reply-timeout: sendAndReceive() 操作的超时时间
spring.rabbitmq.template.retry.enabled: 发送重试是否可用
spring.rabbitmq.template.retry.max-attempts: 最大重试次数
spring.rabbitmq.template.retry.initial-interval: 第一次和第二次尝试发布或传递消息之间的间隔
spring.rabbitmq.template.retry.multiplier: 应用于上一重试间隔的乘数
spring.rabbitmq.template.retry.max-interval: 最大重试时间间隔