这是我参与更文挑战的第10天,活动详情查看: 更文挑战
相关概念
启动后页面
connections:Connection是RabbitMQ的socket链接,它封装了socket协议相关部分逻辑
channels:信道,信道是建立在真实的TCP连接内的虚拟连接。AMQP的命令都是通过信道发送出去的,每条信道都会被指派一个唯一ID。一个TCP连接,对应多个信道,理论上无限制,减少TCP创建和销毁的开销,实现共用TCP的效果
queues: rabbitmq内部对象,存储消息
Messags状态:
Ready:就绪状态,处于队列中说明没有被监听
Unacked: 已被监听,但未确认消费,断开后变成Ready状态
total:ready+unacked
confirm 消息确认机制、return 消息机制和消息确认消费通知
confirm消息确认:是否成功发送到相应交换机的相应队列中
return消息:return Listener 用于处理一些不可路由的消息,在发送消息时,当前的 exchange 不存在或者指定的 routingkey 路由不到,这个时候如果要监听这种不可达的消息,就要使用 return Listener
springboot使用:
配置 application.yml
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
# 开启confirm和return
publisher-confirms: true
publisher-returns: true
# 开启手动确认
listener:
simple
# none意味着没有任何的应答会被发送。manual意味着监听者必须通过调用Channel.basicAck()来告知所有的消息。auto意味着容器会自动应答,除非MessageListener抛出异常,这是默认配置方式。
acknowledge-mode: manual
# 该配置项是决定由于监听器抛出异常而拒绝的消息是否被重新放回队列。默认值为true。
default-requeue-rejected
# 开启重试次数和重试机制
retry:
max-attempts: 3
enabled: true
发送者Sender.java 实现Confirm,Return
@RestController
public class Sender implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostMapping("/send1")
public void send1() {
String context = "hello " + new Date();
System.out.println("Sender : " + context);
//mandatory,true消息未发送成功退回,false自动删除消息
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(this);
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.convertAndSend("exchange","topic.message", context);
}
// @PostMapping("/send2")
// public void send2() {
// String context = "world " + new Date();
// System.out.println("Sender : " + context);
// this.rabbitTemplate.convertAndSend("topic.messages", context);
// }
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
System.out.println("消息发送成功");
} else {
System.out.println("消息发送失败:" + cause );
}
}
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("returnMessage:"+message + i + s + s1 + s2);
}
}
消费者 Receiver.java 确认消费
@RabbitListener(queues="topic.message") //监听器监听指定的Queue
public void process1(String str,Message message,Channel channel) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
System.out.println("message:"+str);
}
处理return消息(未成功路由)
添加Return未路由配置
@Bean
public Queue unRouteQueue() {
return new Queue("queue-unroute");
}
@Bean
public Exchange exchange() {
Map<String, Object> arguments = new HashMap<>(4);
// 当发往exchange-rabbit-springboot-advance的消息,routingKey和bindingKey没有匹配上时,将会由exchange-unroute交换器进行处理
arguments.put("alternate-exchange", "exchange-unroute");
return new DirectExchange("exchange", true, false, arguments);
}
@Bean
public FanoutExchange unRouteExchange() {
// 此处的交换器的名字要和 exchange() 方法中 alternate-exchange 参数的值一致
return new FanoutExchange("exchange-unroute");
}
@Bean
public Binding unRouteBinding() {
return BindingBuilder.bind(unRouteQueue()).to(unRouteExchange());
}
死信队列
介绍:
1.有消息被拒绝,并且没有重新入队requeue=false
2.队列达到最大长度
3.消息TTL过期
消息进入死信队列的过程:消息 -> 队列 (触发以上条件)-> DLX交换机 -> DLK队列
配置
@Configuration
public class RabbitmqConfig {
@Bean("deadLetterExchange")
public Exchange deadLetterExchange() {
return ExchangeBuilder.directExchange("DL_EXCHANGE").durable(true).build();
}
@Bean("deadLetterQueue")
public Queue deadLetterQueue() {
Map<String, Object> args = new HashMap<>(2);
// x-dead-letter-exchange 声明 死信交换机
args.put("x-dead-letter-exchange", "DL_EXCHANGE");
// x-dead-letter-routing-key 声明 死信路由键
args.put("x-dead-letter-routing-key", "KEY_R");
return QueueBuilder.durable("DL_QUEUE").withArguments(args).build();
}
@Bean("redirectQueue")
public Queue redirectQueue() {
return QueueBuilder.durable("REDIRECT_QUEUE").build();
}
/**
* 死信路由通过 DL_KEY 绑定键绑定到死信队列上.
*
* @return the binding
*/
@Bean
public Binding deadLetterBinding() {
return new Binding("DL_QUEUE", Binding.DestinationType.QUEUE, "DL_EXCHANGE", "DL_KEY", null);
}
/**
* 死信路由通过 KEY_R 绑定键绑定到死信队列上.
*
* @return the binding
*/
@Bean
public Binding redirectBinding() {
return new Binding("REDIRECT_QUEUE", Binding.DestinationType.QUEUE, "DL_EXCHANGE", "KEY_R", null);
}
}
生产者
String context = "hello " + new Date();
System.out.println("Sender : " + context);
MessagePostProcessor messagePostProcessor = message -> {
MessageProperties messageProperties = message.getMessageProperties();
// 设置编码
messageProperties.setContentEncoding("utf-8");
// 设置过期时间10*1000毫秒
messageProperties.setExpiration("5000");
return message;
};
rabbitTemplate.convertAndSend("DL_EXCHANGE", "DL_KEY", context, messagePostProcessor);
消费者监听重新定向的队列
延时队列
队列里的消息不需要立即消费,等待一段时间后取出。如订单,预约,业务失败重试等。
一般有两种实现方式
-
死信队列,设置message时间,如上
-
利用rabbitmq中的插件rabbitmq_delayed_message_exchange
下载:rabbitmq_delayed_message_exchange
解压后放入rabbitmq安装目录plugins下 执行命令:rabbitmq-plugins enable rabbitmq_delayed_message_exchange
重启服务配置
// 创建一个立即消费队列
@Bean
public Queue immediateQueue() {
// 第一个参数是创建的queue的名字,第二个参数是是否支持持久化
return new Queue("delayqueue", true);
}
@Bean
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-delayed-type", "direct");
return new CustomExchange("DELAYED_EXCHANGE", "x-delayed-message", true, false, args);
}
@Bean
public Binding bindingNotify() {
return BindingBuilder.bind(immediateQueue()).to(delayExchange()).with("DELAY_ROUTING_KEY").noargs();
}
生产者Sender
修改message延迟方法
messageProperties.setDelay(5000);