rabbitmq springboot 使用篇
1 引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
2 配置
详细参考:
spring: rabbitmq: host: 127.0.0.1 port: 5672 username: 12345 password: 54321 virtual-host: / publisher-confirms: true #开启发送确认 publisher-returns: true #开启发送失败回退
3 config注入配置
3.1 声明交换机 队列 以及绑定关系
/**
* 创建普通交换机.
*/
@Bean
public TopicExchange lindExchange() {
//消息持久化
return (TopicExchange) ExchangeBuilder.topicExchange(EXCHANGE).durable(true).build();
}
@Bean
public TopicExchange deadExchange() {
return (TopicExchange) ExchangeBuilder.topicExchange(LIND_DL_EXCHANGE).durable(true).build();
}
/**
* 基于消息事务的处理方式,当消费失败进行重试,有时间间隔,当达到超时时间,就发到死信队列,等待人工处理.
* @return
*/
@Bean
public Queue testQueue() {
//设置死信交换机
return QueueBuilder.durable(QUEUE).withArgument("x-dead-letter-exchange", LIND_DL_EXCHANGE)
//毫秒
.withArgument("x-message-ttl", CONSUMER_EXPIRE)
//设置死信routingKey
.withArgument("x-dead-letter-routing-key", LIND_DEAD_QUEUE).build();
//或者也可以写作
Map<String,Object> params = new HashMap<>();
params.put("x-message-ttl",CONSUMER_EXPIRE);
params.put("x-dead-letter-routing-key",LIND_DEAD_QUEUE);
return QueueBuilder.durable(QUEUE).withArguments(params).build();
} @Bean public Queue deadQueue() { return new Queue(LIND_DEAD_QUEUE); } @Bean public Binding bindBuildersRouteKey() { return BindingBuilder.bind(testQueue()).to(lindExchange()).with(ROUTER); } @Bean public Binding bindDeadBuildersRouteKey() { return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(LIND_DEAD_QUEUE); }
3.2 配置publisher-confirms 和 publisher-returns 回调
这里注意:ConfirmCallback 和 ReturnCallback的区别?
ConfirmCallback 是发送到交换机exchange失败调用的回调函数,
ReturnCallback 是消息发送到交换机exchange了,但是没有成功投递到队列失败而调用的函数,消息被退回。
发送的消息怎么样才算失败或成功?如何确认?
当消息无法路由到队列时,确认消息路由失败。消息成功路由时,当需要发送的队列都发送成功后,进行确认消息,对于持久化队列意味着写入磁盘,对于镜像队列意味着所有镜像接收成功
需要添加配置
spring:
rabbitmq:
publisher-returns: true
publisher-confirms: true
方式1
import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RabbitConfig { @Bean public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){ RabbitTemplate rabbitTemplate = new RabbitTemplate(); rabbitTemplate.setConnectionFactory(connectionFactory); //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数 rabbitTemplate.setMandatory(true); rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println("ConfirmCallback: "+"相关数据:"+correlationData); System.out.println("ConfirmCallback: "+"确认情况:"+ack); System.out.println("ConfirmCallback: "+"原因:"+cause); } }); rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() { @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { System.out.println("ReturnCallback: "+"消息:"+message); System.out.println("ReturnCallback: "+"回应码:"+replyCode); System.out.println("ReturnCallback: "+"回应信息:"+replyText); System.out.println("ReturnCallback: "+"交换机:"+exchange); System.out.println("ReturnCallback: "+"路由键:"+routingKey); } }); return rabbitTemplate; } }
方式2
@Component public class RabbitTemplateConfig implements RabbitTemplate.ConfirmCallback{ @Autowired private RabbitTemplate rabbitTemplate; @PostConstruct public void init(){ rabbitTemplate.setConfirmCallback(this); //指定 ConfirmCallback } @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println("消息唯一标识:"+correlationData); System.out.println("确认结果:"+ack); System.out.println("失败原因:"+cause); } }
@Component public class RabbitTemplateConfig implements RabbitTemplate.ReturnCallback{ @Autowired private RabbitTemplate rabbitTemplate; @PostConstruct public void init(){ rabbitTemplate.setReturnCallback(this); //指定 ReturnCallback } @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); } }
3 生产者
@Autowired RabbitTemplate rabbitTemplate; //使用RabbitTemplate,这提供了接收/发送等等方法 @GetMapping("/sendMessage") public String sendDirectMessage() {
rabbitTemplate.convertAndSend("exchange", "routekey", "hello world");
return "ok";
}
4 消费者
4.1自动应答
spring boot 默认是自动应答
@RabbitListener(queues = MqConfig.QUEUE) public void testSubscribe(String msg) {
System.out.println("TopicManReceiver消费者收到消息 : " + msg.toString());
}
4.2手动应答
默认情况下消息消费者是自动 ack (确认)消息的,如果要手动 ack(确认)则需要修改确认模式为 manual
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
或在 RabbitListenerContainerFactory 中进行开启手动 ack
@Bean public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){ SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); factory.setMessageConverter(new Jackson2JsonMessageConverter()); factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); //开启手动 ack return factory; }
下面是手动确认的 消费者代码
/** * 延时队列:不应该有RabbitListener订阅者,应该让它自己达到超时时间后自动转到死信里去消费 * 消息异常处理:消费出现异常后,延时几秒,然后从新入队列消费,直到达到TTL超时时间,再转到死信,证明这个信息有问题需要人工干预 * * @param message */ @RabbitListener(queues = MqConfig.QUEUE) public void testSubscribe(Message message, Channel channel) throws IOException, InterruptedException { try { System.out.println(LocalDateTime.now() + ":Subscriber:" + new String(message.getBody(), "UTF-8")); //当程序处理出现问题时,消息使用basicReject上报 int a = 0; int b = 1 / a; channel.basicAck(message.getMessageProperties().getDeliveryTag(), true); } catch (Exception ex) { //出现异常手动放回队列 Thread.sleep(2000); channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } /** * 死信队列. * * @param message */ @RabbitListener(queues = MqConfig.LIND_DEAD_QUEUE) public void dealSubscribe(Message message, Channel channel) throws IOException { System.out.println("Dead Subscriber:" + new String(message.getBody(), "UTF-8")); channel.basicAck(message.getMessageProperties().getDeliveryTag(), true); }
================================================================下面摘抄自博客 https://blog.csdn.net/qq_35387940/article/details/100514134=======================================
到这里,我们其实已经掌握了怎么去使用消息消费的手动确认了。
但是这个场景往往不够! 因为很多伙伴之前给我评论反应,他们需要这个消费者项目里面,监听的好几个队列都想变成手动确认模式,而且处理的消息业务逻辑不一样。
没有问题,接下来看代码
场景: 除了直连交换机的队列TestDirectQueue需要变成手动确认以外,我们还需要将一个其他的队列
或者多个队列也变成手动确认,而且不同队列实现不同的业务处理。
那么我们需要做的第一步,往SimpleMessageListenerContainer里添加多个队列:
然后我们的手动确认消息监听类,MyAckReceiver.java 就可以同时将上面设置到的队列的消息都消费下来。
但是我们需要做不用的业务逻辑处理,那么只需要 根据消息来自的队列名进行区分处理即可,如:
import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; @Component public class MyAckReceiver implements ChannelAwareMessageListener { @Override public void onMessage(Message message, Channel channel) throws Exception { long deliveryTag = message.getMessageProperties().getDeliveryTag(); try { //因为传递消息的时候用的map传递,所以将Map从Message内取出需要做些处理 String msg = message.toString(); String[] msgArray = msg.split("'");//可以点进Message里面看源码,单引号直接的数据就是我们的map消息数据 Map<String, String> msgMap = mapStringToMap(msgArray[1].trim(),3); String messageId=msgMap.get("messageId"); String messageData=msgMap.get("messageData"); String createTime=msgMap.get("createTime"); if ("TestDirectQueue".equals(message.getMessageProperties().getConsumerQueue())){ System.out.println("消费的消息来自的队列名为:"+message.getMessageProperties().getConsumerQueue()); System.out.println("消息成功消费到 messageId:"+messageId+" messageData:"+messageData+" createTime:"+createTime); System.out.println("执行TestDirectQueue中的消息的业务处理流程......"); } if ("fanout.A".equals(message.getMessageProperties().getConsumerQueue())){ System.out.println("消费的消息来自的队列名为:"+message.getMessageProperties().getConsumerQueue()); System.out.println("消息成功消费到 messageId:"+messageId+" messageData:"+messageData+" createTime:"+createTime); System.out.println("执行fanout.A中的消息的业务处理流程......"); } channel.basicAck(deliveryTag, true); // channel.basicReject(deliveryTag, true);//为true会重新放回队列 } catch (Exception e) { channel.basicReject(deliveryTag, false); e.printStackTrace(); } } //{key=value,key=value,key=value} 格式转换成map private Map<String, String> mapStringToMap(String str,int enNum) { str = str.substring(1, str.length() - 1); String[] strs = str.split(",",enNum); Map<String, String> map = new HashMap<String, String>(); for (String string : strs) { String key = string.split("=")[0].trim(); String value = string.split("=")[1]; map.put(key, value); } return map; } }
ok,这时候我们来分别往不同队列推送消息,看看效果:
调用接口/sendDirectMessage 和 /sendFanoutMessage ,
如果你还想新增其他的监听队列,也就是按照这种方式新增配置即可(或者完全可以分开多个消费者项目去监听处理)。