Spring Boot使用事务向RabbitMQ发送消息
前言
使用SpringBoot向RabbitMQ发送消息非常简单,只需要引入spring-boot-starter-amqp
包,再做些简单的配置即可。由于项目对消息有比较高的要求,要求不能丢失任何一条消息,若还是使用默认的发送方式,当网络状况不稳定的时候,有可能会出现消息丢失的情况。这时,我们就需要使用RabbitMQ的事务机制,保证消息发送出去后不会丢失。
事务配置步骤如下:
1. 先引入amqp库
在SpringBoot项目的pom.xml中加入以下依赖库(我这里使用的是Spring Boot 2.1.0.RELEASE版本)。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 配置application.properties
spring.rabbitmq.host=x.x.x.x
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=xxxxxx
spring.rabbitmq.publisher-confirms=false
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.virtual-host=/
spring.rabbitmq.listener.direct.acknowledge-mode=manual
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#以下为队列名称
mq.exchange=mq.exchange
mq.ingate.queue=mq.ingate.queue
spring.rabbitmq.publisher-confirms一定要配置为false,否则会与事务处理相冲突,启动时会报异常。
3. RabbitConfig.java配置
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.transaction.RabbitTransactionManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
@Value("${mq.exchange}")
private String exchange;
@Value("${mq.ingate.queue}")
private String ingateQueue;
@Bean
public DirectExchange exchange() {
return new DirectExchange(exchange,true,false);
}
@Bean
public Queue ingateQueue() {
return new Queue(ingateQueue,true);
}
@Bean
public Binding ingateQueueBinding() {
return BindingBuilder.bind(ingateQueue()).to(exchange()).withQueueName();
}
/**
* 配置启用rabbitmq事务
* @param connectionFactory
* @return
*/
@Bean("rabbitTransactionManager")
public RabbitTransactionManager rabbitTransactionManager(CachingConnectionFactory connectionFactory) {
return new RabbitTransactionManager(connectionFactory);
}
}
本类用于配置RabbitMQ的Exchange和Queue,同时声明了事务管理器(这个很重要)。
4. 配置消息发送者 RabbitSender.java
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import com.xxxx.dto.TradeRefundDTO;
/**
* RabbitMQ消息发送类
*/
@Component
public class RabbitSender implements RabbitTemplate.ReturnCallback {
private static final Logger logger = LoggerFactory.getLogger("mq");
@Autowired
private RabbitTemplate rabbitTemplate;
@Value("${mq.exchange}")
private String exchange;
@Value("${mq.ingate.queue}")
private String ingateQueue;
@PostConstruct
private void init() {
//启用事务模式,不能开确认回调
//rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
rabbitTemplate.setChannelTransacted(true);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
}
@Transactional(rollbackFor = Exception.class,transactionManager = "rabbitTransactionManager")
public void sendIngateQueue(TradePayModelRes msg) {
logger.info("进闸消息已发送 {}",msg.getOutTradeNo());
rabbitTemplate.convertAndSend(exchange,ingateQueue,msg);
}
@Override
public void returnedMessage(Message message, int replyCode, String replyText,String exchange, String routingKey) {
logger.info("消息被退回 {}" , message.toString());
}
}
本类用于统一向RabbitMQ发送消息,在发送消息的sendIngateQueue()方法上,加了@Transactional注解,表示这个方法将启用事务(此时的事务即是RabbitMQ事务,因为前面定义了RabbitTransactionManager )。
由于启用了事务,所以需要在系统初始化时,调用rabbitTemplate.setChannelTransacted(true),以激活rabbitTemplate对象事务处理功能。
5. 编写消息消费者 IngateConsumer.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import com.alibaba.dubbo.config.annotation.Reference;
import com.rabbitmq.client.Channel;
import com.xxxx.gateway.model.TradePayModelRes;
@Component
@RabbitListener(queues = "${mq.ingate.queue}")
public class IngateConsumer {
private static Logger logger = LoggerFactory.getLogger("mq");
@RabbitHandler
public void process(TradePayModelRes tradePayModelRes, Channel channel, Message message) {
logger.info("收到支付消息 {}",tradePayModelRes.toString());
try {
//do samothing
logger.info("确认消费进闸支付消息 {}",tradePayModelRes.getOutTradeNo());
} catch (Exception e) {
logger.info("进闸支付入库异常 {} - {}",tradePayModelRes.getOutTradeNo(),e);
}
}
}
至此,配置完成。
发送消息时,调用rabbitSender.sendIngateQueue()方法,即可启用事务发送消息机制,以保证消息不丢失。
这些只是示例代码,留下记录,方便参考。