以下是一篇关于 RabbitMQ 的博客内容,涵盖了从基础到死信队列的实现,以及 RabbitMQ 其他常用知识点的补充。内容逻辑清晰,代码完整,适合直接发布。


使用 RabbitMQ 实现消息队列与死信队列:从基础到高级

在现代分布式系统中,消息队列(如 RabbitMQ)是解耦和异步通信的重要工具。本文将基于 Spring Boot 和 RabbitMQ,从基础到高级,逐步实现以下功能:

  1. 发送消息到队列
  2. 发送消息到交换机
  3. 消息可靠性机制
    • 消息确认机制(Publisher Confirms)。
    • 消息持久化(Durable Queues and Messages)。
    • 消费者手动确认(Manual Acknowledgement)。
  4. 死信队列(Dead Letter Queue, DLQ):处理无法被正常消费的消息。

我们将使用一个简单的 User 对象作为消息内容,User 类包含 nameage 字段。


1. 创建 User

首先,在 service-aservice-b 中创建 User 类。

package com.example.common;

import java.io.Serializable;

public class User implements Serializable {
private String name;
private int age; // 必须有无参构造函数
public User() {
} public User(String name, int age) {
this.name = name;
this.age = age;
} // Getter 和 Setter
public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} @Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}

2. 发送消息到队列

2.1 配置队列

service-a 中配置一个队列。

package com.example.servicea.config;

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class RabbitMQQueueConfig { @Bean
public Queue userQueue() {
return new Queue("userQueue", true); // 第二个参数表示持久化
}
}

2.2 发送消息

service-a 中发送 User 对象到队列。

package com.example.servicea.service;

import com.example.common.User;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; @Service
public class QueueMessageSender { @Autowired
private RabbitTemplate rabbitTemplate; public void sendUserToQueue(User user) {
rabbitTemplate.convertAndSend("userQueue", user);
System.out.println("Sent user to queue: " + user);
}
}

2.3 接收消息

service-b 中监听队列并接收 User 对象。

package com.example.serviceb.service;

import com.example.common.User;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service; @Service
public class QueueMessageReceiver { @RabbitListener(queues = "userQueue")
public void receiveUserFromQueue(User user) {
System.out.println("Received user from queue: " + user);
}
}

3. 发送消息到交换机

3.1 配置交换机和队列

service-a 中配置一个 Direct Exchange 并绑定队列。

package com.example.servicea.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class RabbitMQExchangeConfig { @Bean
public DirectExchange userExchange() {
return new DirectExchange("userExchange", true, false); // 第二个参数表示持久化
} @Bean
public Queue userExchangeQueue() {
return new Queue("userExchangeQueue", true); // 第二个参数表示持久化
} @Bean
public Binding bindingUserExchangeQueue(DirectExchange userExchange, Queue userExchangeQueue) {
return BindingBuilder.bind(userExchangeQueue)
.to(userExchange)
.with("user.routing.key");
}
}

3.2 发送消息

service-a 中发送 User 对象到交换机。

package com.example.servicea.service;

import com.example.common.User;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; @Service
public class ExchangeMessageSender { @Autowired
private RabbitTemplate rabbitTemplate; public void sendUserToExchange(User user) {
rabbitTemplate.convertAndSend("userExchange", "user.routing.key", user);
System.out.println("Sent user to exchange: " + user);
}
}

3.3 接收消息

service-b 中监听队列并接收 User 对象。

package com.example.serviceb.service;

import com.example.common.User;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service; @Service
public class ExchangeMessageReceiver { @RabbitListener(queues = "userExchangeQueue")
public void receiveUserFromExchange(User user) {
System.out.println("Received user from exchange: " + user);
}
}

4. 消息可靠性

4.1 消息确认机制(Publisher Confirms)

application.yml 中启用 Publisher Confirms 和 Returns。

spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
publisher-confirm-type: correlated # 启用 Publisher Confirms
publisher-returns: true # 启用 Publisher Returns

service-a 中配置 RabbitTemplate 以支持 Publisher Confirms 和 Returns。

package com.example.servicea.config;

import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class RabbitMQConfig { @Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); // 启用 Publisher Confirms 和 Returns
rabbitTemplate.setMandatory(true); // 设置确认回调
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
System.out.println("Message confirmed with correlation data: " + correlationData);
} else {
System.out.println("Message failed with cause: " + cause);
}
}); // 设置返回回调
rabbitTemplate.setReturnsCallback(returned -> {
System.out.println("Returned message: " + returned.getMessage());
System.out.println("Reply code: " + returned.getReplyCode());
System.out.println("Reply text: " + returned.getReplyText());
System.out.println("Exchange: " + returned.getExchange());
System.out.println("Routing key: " + returned.getRoutingKey());
}); return rabbitTemplate;
}
}

4.2 消息持久化

在配置队列和交换机时启用持久化。

@Bean
public Queue userQueue() {
return new Queue("userQueue", true); // 第二个参数表示持久化
} @Bean
public DirectExchange userExchange() {
return new DirectExchange("userExchange", true, false); // 第二个参数表示持久化
}

在发送消息时设置消息为持久化。

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import com.fasterxml.jackson.databind.ObjectMapper; @Service
public class ReliableMessageSender { @Autowired
private RabbitTemplate rabbitTemplate; @Autowired
private ObjectMapper objectMapper; public void sendUserWithConfirmation(User user) throws IOException {
// 生成唯一的 CorrelationData
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); // 设置消息属性
MessageProperties properties = new MessageProperties();
properties.setContentType("application/json"); // 明确设置 content-type
properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 持久化消息
byte[] body = objectMapper.writeValueAsBytes(user);
Message message = new Message(body, properties); // 发送消息
rabbitTemplate.send("userExchange", "user.routing.key", message, correlationData);
System.out.println("Sent user with confirmation: " + user);
}
}

4.3 消费者手动确认

service-bapplication.yml 中启用手动确认。

spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual

service-b 中实现手动确认逻辑。

package com.example.serviceb.service;

import com.example.common.User;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service; import java.io.IOException; @Service
public class ManualAckReceiver { @RabbitListener(queues = "userQueue")
public void receiveUser(User user, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
try {
System.out.println("Received user from queue: " + user);
// 手动确认消息
channel.basicAck(tag, false);
} catch (Exception e) {
// 拒绝消息并重新入队
channel.basicNack(tag, false, true);
}
}
}

5. 死信队列(Dead Letter Queue, DLQ)

5.1 配置死信队列

service-a 中配置死信队列和普通队列。

package com.example.servicea.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class RabbitMQDLXConfig { // 普通交换机
@Bean
public DirectExchange normalExchange() {
return new DirectExchange("normalExchange");
} // 普通队列,配置死信交换机
@Bean
public Queue normalQueue() {
return QueueBuilder.durable("normalQueue")
.deadLetterExchange("dlxExchange") // 指定死信交换机
.deadLetterRoutingKey("dlx.routing.key") // 指定死信路由键
.build();
} // 绑定普通队列到普通交换机
@Bean
public Binding bindingNormalQueue(DirectExchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue)
.to(normalExchange)
.with("normal.routing.key");
} // 死信交换机
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange("dlxExchange");
} // 死信队列
@Bean
public Queue dlqQueue() {
return new Queue("dlqQueue");
} // 绑定死信队列到死信交换机
@Bean
public Binding bindingDlqQueue(DirectExchange dlxExchange, Queue dlqQueue) {
return BindingBuilder.bind(dlqQueue)
.to(dlxExchange)
.with("dlx.routing.key");
}
}

5.2 发送消息到普通队列

service-a 中发送消息到普通队列。

package com.example.servicea.service;

import com.example.common.User;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; @Service
public class NormalMessageSender { @Autowired
private RabbitTemplate rabbitTemplate; public void sendUserToNormalQueue(User user) {
rabbitTemplate.convertAndSend("normalExchange", "normal.routing.key", user);
System.out.println("Sent user to normal queue: " + user);
}
}

5.3 消费普通队列的消息

service-b 中消费普通队列的消息,并模拟消息处理失败。

package com.example.serviceb.service;

import com.example.common.User;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service; import java.io.IOException; @Service
public class NormalMessageReceiver { @RabbitListener(queues = "normalQueue")
public void receiveUserFromNormalQueue(User user, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
try {
System.out.println("Received user from normal queue: " + user);
if (user.getName().equals("Bob")) {
throw new RuntimeException("Simulated processing failure");
}
// 手动确认消息
channel.basicAck(tag, false);
} catch (Exception e) {
// 拒绝消息并重新入队
channel.basicNack(tag, false, false); // 不重新入队,消息会被路由到死信队列
System.out.println("Message rejected and sent to DLQ: " + user);
}
}
}

5.4 消费死信队列的消息

service-b 中消费死信队列的消息。

package com.example.serviceb.service;

import com.example.common.User;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service; @Service
public class DLQMessageReceiver { @RabbitListener(queues = "dlqQueue")
public void receiveUserFromDLQ(User user) {
System.out.println("Received user from DLQ: " + user);
}
}

6. 测试死信队列

6.1 发送消息

service-a 中发送消息到普通队列:

normalMessageSender.sendUserToNormalQueue(new User("Alice", 25));
normalMessageSender.sendUserToNormalQueue(new User("Bob", 30));

6.2 观察日志

  • 正常消息(Alice)会被消费并确认:
    Received user from normal queue: User{name='Alice', age=25}
  • 失败消息(Bob)会被拒绝并路由到死信队列:
    Received user from normal queue: User{name='Bob', age=30}
    Message rejected and sent to DLQ: User{name='Bob', age=30}
    Received user from DLQ: User{name='Bob', age=30}

7. 总结

通过以上步骤,我们实现了 RabbitMQ 的死信队列功能:

  1. 普通队列:绑定到普通交换机,配置了死信交换机和路由键。
  2. 死信队列:绑定到死信交换机,用于存储无法被正常消费的消息。
  3. 消息处理
    • 正常消息被消费并确认。
    • 失败消息被拒绝并路由到死信队列。
  4. 死信队列消费:单独消费死信队列中的消息。

这种机制非常适合处理异常情况下的消息,确保系统的可靠性和可维护性。


8. 其他常用知识点

8.1 消息过期(TTL)

可以为队列或消息设置过期时间(Time-To-Live, TTL)。过期后的消息会被路由到死信队列。

设置队列 TTL:

@Bean
public Queue normalQueue() {
return QueueBuilder.durable("normalQueue")
.deadLetterExchange("dlxExchange")
.deadLetterRoutingKey("dlx.routing.key")
.ttl(60000) // 设置队列中消息的 TTL 为 60 秒
.build();
}

设置消息 TTL:

MessageProperties properties = new MessageProperties();
properties.setExpiration("60000"); // 设置消息的 TTL 为 60 秒
Message message = new Message(body, properties);
rabbitTemplate.send("normalExchange", "normal.routing.key", message);

8.2 优先级队列

可以为队列设置优先级,优先级高的消息会被优先消费。

设置优先级队列:

@Bean
public Queue priorityQueue() {
return QueueBuilder.durable("priorityQueue")
.maxPriority(10) // 设置最大优先级为 10
.build();
}

发送优先级消息:

MessageProperties properties = new MessageProperties();
properties.setPriority(5); // 设置消息优先级为 5
Message message = new Message(body, properties);
rabbitTemplate.send("priorityExchange", "priority.routing.key", message);

希望这篇博客对你有所帮助!如果有任何问题或建议,欢迎在评论区留言。

【消息利器RabbitMQ】RabbitMQ常用内容浅析的更多相关文章

  1. java框架之SpringBoot(12)-消息及整合RabbitMQ

    前言 概述 大多数应用中,可通过消息服务中间件来提升系统异步通信.扩展解耦的能力. 消息服务中两个重要概念:消息代理(message broker)和目的地(destination).当消息发送者发送 ...

  2. 消息队列&Celery&RabbitMQ&zeromq

    一.消息队列 什么是消息队列? “消息队列”是在消息的传输过程中保存消息的容器. “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更复杂,可能包含嵌入对象. 消息 ...

  3. 消息队列之 RabbitMQ

    https://www.jianshu.com/p/79ca08116d57 关于消息队列,从前年开始断断续续看了些资料,想写很久了,但一直没腾出空,近来分别碰到几个朋友聊这块的技术选型,是时候把这块 ...

  4. RabbitMQ,Apache的ActiveMQ,阿里RocketMQ,Kafka,ZeroMQ,MetaMQ,Redis也可实现消息队列,RabbitMQ的应用场景以及基本原理介绍,RabbitMQ基础知识详解,RabbitMQ布曙

    消息队列及常见消息队列介绍 2017-10-10 09:35操作系统/客户端/人脸识别 一.消息队列(MQ)概述 消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以 ...

  5. 转 消息队列之 RabbitMQ

    转 https://www.jianshu.com/p/79ca08116d57 消息队列之 RabbitMQ 预流 2017.05.06 16:03* 字数 4884 阅读 80990评论 18喜欢 ...

  6. C# 消息队列之 RabbitMQ 基础入门

    Ø  简介 C# 实现消息队列的方式有很多种,比如:MSMQ.RabbitMQ.EQueue 等,本文主要介绍使用 RabbitMQ 实现消息队列的基础入门.包括如下内容: 1.   什么是消息队列? ...

  7. 主流消息队列rocketMq,rabbitMq比对使用

    首先整理这个文章是因为我正好有机会实战了一下rocketmq,阿里巴巴的一个开源消息中间件.所以就与以往中rabbitmq进行小小的比较一下.这里主线的根据常见面试问题进行整理. 一.消息队列常用的场 ...

  8. 消息队列之 RabbitMQ【验证通过】

    消息队列之 RabbitMQ 预流 关注  22.9 2017.05.06 16:03* 字数 4884 阅读 284691评论 41喜欢 618赞赏 2 关于消息队列,从前年开始断断续续看了些资料, ...

  9. C# 消息队列之 RabbitMQ 进阶篇

    Ø  简介 在之前的 C# 消息队列之 RabbitMQ 基础入门 中介绍了 RabbitMQ 的基本用法,其实要更全面的掌握 RabbitMQ 这个消息队列服务,我们还需要掌握以下内容: 1.   ...

  10. 分布式消息通信之RabbitMQ Tutorials

    目录 官网 1 Hello World! 1.1 生产者demo producer 1.2 消费者demo consumer 1.3 查看queue队列中的信息 页面查看,可看到有4条消息 命令查看 ...

随机推荐

  1. MySql 建表出现的问题 : [ERR] 1064 - You have an error in your SQL syntax; check the manual.......

    使用 MySql 建表出现的问题 在使用 Navicat Premium 运行 sql 语句进行建表时,MySQL 报错如下: 1064 - You have an error in your SQL ...

  2. sql server版本太老,java客户端连接失败问题定位

    背景 最近半路接手了一个系统的优化需求,这个系统有个遗留问题还没解决,随着新需求的上线,系统正式开放使用,这个遗留问题也必须解决. 这个系统大概是下面这样的,支持录入各种数据源的信息(ip.端口.数据 ...

  3. 2020年最新网络编程面试题-copy

    计算机网络体系结构 在计算机网络的基本概念中,分层次的体系结构是最基本的.计算机网络体系结构的抽象概念较多,在学习时要多思考.这些概念对后面的学习很有帮助. 网络协议是什么? 在计算机网络要做到有条不 ...

  4. weixueyuan-Nginx核心配置指令2

    https://www.weixueyuan.net/nginx/config/ Nginx配置文件详解 Nginx 默认编译安装后,配置文件都会保存在 /usr/local/nginx/conf 目 ...

  5. Java Spring Cloud Nacos 配置修改不生效的解决方法

    一.引言 在微服务架构中,配置管理是一个关键部分.Nacos作为一个动态服务发现.配置管理和服务管理平台,广泛应用于Java Spring Cloud项目中.然而,有时在修改Nacos配置后,这些更改 ...

  6. css漂亮的弧形

    我们有时会遇到要用实现一个弧形,而这样的弧形要怎么实现呢? 用图片?好像不大现实,因为这样就要无故多加载一张图片了 ,这里我们来说说怎么用css的after伪类来实现弧形. 如果想要调整弧度的话,可以 ...

  7. pkill 踢出某个终端

    是ps命令和kill命令的结合,按照进程名来杀死指定进程 选项 -o:仅向找到的最小(起始)进程号发送信号: -n:仅向找到的最大(结束)进程号发送信号: -P:指定父进程号发送信号: -g:指定进程 ...

  8. NOIP 游记

    前情提要:color \(100\to 0\),arena \(92/100\to 36\). 最后一场模拟赛喜提 0+0+100+0,挺乐的. Day 0 晚上九点睡,然而还是很早就醒了,但是时间体 ...

  9. dbeaver软件使用问题

    一.dbeaver导出数据表到csv后数据乱码 按如下方式导出即可 勾选插入BOM即可 Excel在读取csv的时候是通过读取文件头上的bom来识别编码的,如果文件头无bom信息,则默认按照unico ...

  10. Luogu P1220 关路灯 题解 [ 蓝 ][ 区间dp ]

    原题 关路灯 题目描述 某一村庄在一条路线上安装了 \(n\) 盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少).老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏 ...