不多说,先上代码,后面有空再具体写
Rabbitmq配置
/**
* RabbitMq配置
* @author pig/xiaofa
*/
@Configuration
@Slf4j
public class RabbitmqConfig {
@Value("${spring.rabbitmq.connection.limit:5}")
private Integer connectionLimit;
/**
* 创建连接工厂:CacheMode=CONNECTION
* @param properties Spring配置文件配置的Rabbitmq属性
* @param connectionNameStrategy 连接名的策略
* @return 连接工厂
*/
@Bean(name = "cacheConnectionFactory")
public CachingConnectionFactory cacheConnectionFactory(
RabbitProperties properties, ObjectProvider<ConnectionNameStrategy> connectionNameStrategy) {
PropertyMapper map = PropertyMapper.get();
CachingConnectionFactory factory = buildFactory(properties);
factory.setConnectionLimit(connectionLimit);
factory.setCacheMode(CachingConnectionFactory.CacheMode.CONNECTION);
RabbitProperties.Cache.Connection connection = properties.getCache().getConnection();
map.from(connection::getSize).whenNonNull().to(factory::setConnectionCacheSize);
map.from(connectionNameStrategy::getIfUnique).whenNonNull().to(factory::setConnectionNameStrategy);
return factory;
}
/**
* 创建连接工厂:CacheMode=CHANNEL
* @param properties Spring配置文件配置的Rabbitmq属性
* @return 连接工厂
*/
@Bean(name = "cacheChannelFactory")
public ConnectionFactory cacheChannelFactory(RabbitProperties properties) {
CachingConnectionFactory factory = buildFactory(properties);
factory.setCacheMode(CachingConnectionFactory.CacheMode.CHANNEL);
return factory;
}
/**
* 根据“cacheChannelFactory“创建消费者消息监听容器
* @param configurer 容器配置对象
* @param cacheChannelFactory channel模式连接工厂
* @return 监听容器
*/
@Bean(name = "channelListenerContainerFactory")
@ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
@ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple", matchIfMissing = true)
public SimpleRabbitListenerContainerFactory channelListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory cacheChannelFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, cacheChannelFactory);
return factory;
}
/**
* 使用“cacheConnectionFactory”创建RabbitTemplate
* @param cacheConnectionFactory connection模式连接工厂
* @param properties Spring配置文件配置的Rabbitmq属性
* @return 消息模板
*/
@Bean(name = "connectionTemplate")
public RabbitTemplate connectionTemplate(ConnectionFactory cacheConnectionFactory, RabbitProperties properties) {
RabbitTemplate rabbitTemplate=new RabbitTemplate(cacheConnectionFactory);
// 使用jackson 消息转换器
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
rabbitTemplate.setEncoding("UTF-8");
rabbitTemplate.setMandatory(properties.getTemplate().getMandatory());
return rabbitTemplate;
}
/**
* 创建连接工厂:根据Spring配置文件生成
* @param properties Spring配置文件配置的Rabbitmq属性
* @param connectionNameStrategy 连接名策略
* @return 连接工厂(CacheMode由配置文件决定)
*/
@Bean(name = "connectionFactory")
public CachingConnectionFactory connectionFactory(
RabbitProperties properties, ObjectProvider<ConnectionNameStrategy> connectionNameStrategy) {
PropertyMapper map = PropertyMapper.get();
CachingConnectionFactory factory = buildFactory(properties);
RabbitProperties.Cache.Connection connection = properties.getCache().getConnection();
map.from(connection::getMode).whenNonNull().to(factory::setCacheMode);
map.from(connection::getSize).whenNonNull().to(factory::setConnectionCacheSize);
map.from(connectionNameStrategy::getIfUnique).whenNonNull().to(factory::setConnectionNameStrategy);
return factory;
}
/**
* 根据“cacheChannelFactory“创建消费者消息监听容器
* @param configurer 容器配置对象
* @param cacheChannelFactory channel模式连接工厂
* @return 监听容器
*/
@Bean(name = "rabbitListenerContainerFactory")
@ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
@ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple", matchIfMissing = true)
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory cacheChannelFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, cacheChannelFactory);
return factory;
}
/**
* 创建RabbitTemplate
* @param connectionFactory 连接工厂
* @param properties Spring配置文件配置的Rabbitmq属性
* @return 消息模板
*/
@Bean(name = "rabbitTemplate")
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, RabbitProperties properties) {
RabbitTemplate rabbitTemplate=new RabbitTemplate(connectionFactory);
// 使用jackson 消息转换器
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
rabbitTemplate.setEncoding("UTF-8");
rabbitTemplate.setMandatory(properties.getTemplate().getMandatory());
return rabbitTemplate;
}
private CachingConnectionFactory buildFactory(RabbitProperties properties) {
PropertyMapper map = PropertyMapper.get();
CachingConnectionFactory factory = new CachingConnectionFactory();
map.from(properties::determineAddresses).to(factory::setAddresses);
map.from(properties::determineUsername).to(factory::setUsername);
map.from(properties::determinePassword).to(factory::setPassword);
map.from(properties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost);
map.from(properties::isPublisherConfirms).whenNonNull().to(factory::setPublisherConfirms);
map.from(properties::isPublisherReturns).whenNonNull().to(factory::setPublisherReturns);
RabbitProperties.Cache.Channel channel = properties.getCache().getChannel();
map.from(channel::getSize).whenNonNull().to(factory::setChannelCacheSize);
map.from(channel::getCheckoutTimeout).whenNonNull().as(Duration::toMillis).to(factory::setChannelCheckoutTimeout);
return factory;
}
}
生产者,使用connection模式,CacheModel=CONNECTION
/**
* 生产者
* @author: pig/xiaofa
* @date: 2019-04-03 17:07
*/
@Component
@Slf4j
public class MessageProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(){
SendNotifyMessageDTO dto = new SendNotifyMessageDTO();
dto.setRequestId(UUID.randomUUID());
dto.setMessage("hello world");
dto.setNotifyUrl("http://localhost:8088/hello");
//生成消息唯一标识,回调确认使用
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend(RabbitMqConstants.PAYMENT_CENTER_EXCHANGE,
RabbitMqConstants.PAYMENT_CENTER_ROUTE_KEY, dto, correlationData);
try {
CorrelationData.Confirm confirm = correlationData.getFuture().get(10, TimeUnit.SECONDS);
if (confirm.isAck()) {
log.info("MessageProducer消息发送到exchange成功,correlationDataId={}", correlationData.getId());
} else {
log.error("MessageProducer消息发送到exchange失败,原因: {}", confirm.getReason());
}
} catch (InterruptedException e) {
log.error("MessageProducer消息发送到exchange中断异常", e);
} catch (ExecutionException e) {
log.error("MessageProducer消息发送到exchange执行异常", e);
} catch (TimeoutException e) {
log.error("MessageProducer消息发送到exchange超时异常", e);
}
}
}
消费者,使用channel模式,CacheMode=CHANNEL
/**
* 消息消费者
* @author pig/xiaofa
* @date 2019-07-22 18:00
*/
@Component
@Slf4j
public class PayCallbackConsumer implements MessageRecoverer {
@Value("${spring.rabbitmq.listener.simple.retry.max-attempts}")
private Integer maxAttempts;
private final static String X_RETRY_COUNT = "x_retry_count";
/**
* 消费Rabbitmq消息
* @param channel 信道
* @param message 消息体
*/
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue(
value = RabbitMqConstants.PAYMENT_CENTER_QUEUE,
arguments = {
//转发交换机
@Argument(name = RabbitMqConstants.X_DEAD_LETTER_EXCHANGE_ATTR, value = RabbitMqConstants.RETRY_EXCHANGE),
//转发路由
@Argument(name = RabbitMqConstants.X_DEAD_LETTER_ROUTING_KEY_ATTR, value = RabbitMqConstants.RETRY_DEAD_ROUTE_KEY),
//当前队列消息存活时间
@Argument(name = RabbitMqConstants.X_MESSAGE_TTL_ATTR, value = RabbitMqConstants.X_MESSAGE_TTL, type = "java.lang.Integer")
}
),
exchange = @Exchange(value = RabbitMqConstants.PAYMENT_CENTER_EXCHANGE),
key = RabbitMqConstants.PAYMENT_CENTER_ROUTE_KEY
)
})
public void receiveNotify(Channel channel, Message message) {
long start = System.currentTimeMillis();
String content = new String(message.getBody());
log.info("--> receive notify message, message body: {}", content);
try {
//故意异常抛出
int i = 3/0;
} catch (Exception e) {
this.reject(channel, message);
}
long end = System.currentTimeMillis();
log.info("--> message processing is time-consuming:{}ms", (end-start));
}
/**
* 重试队列
* @param channel 信道
* @param message 消息体
*/
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue(
value = RabbitMqConstants.RETRY_QUEUE,
arguments = {
//死信交换机
@Argument(name = RabbitMqConstants.X_DEAD_LETTER_EXCHANGE_ATTR, value = RabbitMqConstants.DEAD_EXCHANGE),
//死信路由
@Argument(name = RabbitMqConstants.X_DEAD_LETTER_ROUTING_KEY_ATTR, value = RabbitMqConstants.DEAD_ROUTE_KEY),
//当前队列消息存活时间
@Argument(name = RabbitMqConstants.X_MESSAGE_TTL_ATTR, value = RabbitMqConstants.X_MESSAGE_TTL, type = "java.lang.Integer")
}
),
exchange = @Exchange(value = RabbitMqConstants.RETRY_EXCHANGE),
key = RabbitMqConstants.RETRY_ROUTE_KEY
)
})
public void retryQueue(Channel channel, Message message) {
int retryCount = 0;
long start = System.currentTimeMillis();
try {
//获取消息头
MessageProperties messageProperties = message.getMessageProperties();
Map<String, Object> headers = messageProperties.getHeaders();
if(headers != null && headers.containsKey(X_RETRY_COUNT)) {
//获取已重试次数
retryCount = (int) headers.get(X_RETRY_COUNT);
}
retryCount++;
//更新重试次数
messageProperties.setHeader(X_RETRY_COUNT, retryCount);
String content = new String(message.getBody());
this.doSomething(channel, message, content);
} catch (Exception e) {
if(retryCount > maxAttempts) {
log.error("--> retry reaches maximum, retry count: {}, cause: {}",
retryCount, e.getMessage());
this.reject(channel, message);
} else {
log.error("--> retry consume failure, retry count: {}, cause: {}",
retryCount, e.getMessage());
throw e;
}
} finally {
long end = System.currentTimeMillis();
log.info("--> message retry processing is time-consuming:{}ms, retry count: {}", (end-start), retryCount);
}
}
/**
* 执行
* @param channel 信道
* @param message 消息体
*/
private void doSomething(Channel channel, Message message, String content) {
//模拟处理耗时
try {
Thread.sleep(150);
} catch (InterruptedException e) {
log.error("--> thread error", e);
}
this.ack(channel, message);
log.info("--> message consumed successfully");
}
/**
* 对异常消息进行处理,此处理会在listener.retry次数尝试完并还是抛出异常的情况下才会调用 <br/>
* 注意:spring.rabbitmq.listener.retry配置的重发是在消费端应用内处理的,不是rabbitmq重发
* @param message 消息体
* @param cause 异常原因
*/
@Override
public void recover(Message message, Throwable cause) {
log.error("--> retry reaches maximum, dropped into dead letter queue, message body: {}",
new String(message.getBody()));
}
/**
* ACK确认
* @param channel 信道
* @param message 消息体
*/
private void ack(Channel channel, Message message) {
try {
//false只确认当前一个消息收到,true确认所有consumer获得的消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
throw RuntimeException(String.format("channel basicReject exception, message body: %s", new String(message.getBody()));
}
}
/**
* 拒绝消息
* @param channel 信道
* @param message 消息体
*/
private void reject(Channel channel, Message message) {
try {
//requeue=true,重新回到队列;requeue=false,丢弃/死信
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
throw RuntimeException(String.format("channel basicReject exception, message body: %s", new String(message.getBody()));
}
}
}
配置文件:
##RabbitMq配置
spring.rabbitmq.addresses=127.0.0.1:5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#生产者开启回调确认
spring.rabbitmq.publisher-confirms=true
#开启发送失败退回
spring.rabbitmq.publisher-returns=false
#消息route到队列失败时,是否将return给发送者,一般配合publisher-returns使用
spring.rabbitmq.template.mandatory=false
#虚拟空间
spring.rabbitmq.virtual-host=/
#开启ACK
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#指定最小的消费者数量
spring.rabbitmq.listener.simple.concurrency=60
#指定最大的消费者数量
spring.rabbitmq.listener.simple.max-concurrency=120
#是否支持重试
spring.rabbitmq.listener.simple.retry.enabled=true
#最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
#重试时间间隔
spring.rabbitmq.listener.simple.retry.initial-interval=2000
#消费者每次预取消息数,类似缓冲区
spring.rabbitmq.listener.simple.prefetch=300
#当前最大允许空闲的最大channel数,当遇到connetion error的错误时,就可以考虑增大channel cache size
spring.rabbitmq.cache.channel.size=500
#channel缓存超时时间
spring.rabbitmq.cache.channel.checkout-timeout=2000
#缓存模式
spring.rabbitmq.cache.connection.mode=connection
#当前最大允许空闲的最大connection数
spring.rabbitmq.cache.connection.size=5
#连接数限制,默认:5
spring.rabbitmq.connection.limit=5