文章目录
源码及安装包已上传到csdn, 下载地址:https://download.csdn.net/download/weixin_45352783/85862706
基本配置
创建两个项目,方便后期测试在这里插入代码片
cus: 消费者
pus: 生产者
pom文件
<dependencies>
<!-- web 方便测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- rabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
application.properties
生产者和消费者只有端口不同就不贴两次了
server.port=9091
# 应用名
spring.application.name=springboot-rabbitmq
# rabbitmq配置信息
# ip
spring.rabbitmq.host=127.0.0.1
# 端口
spring.rabbitmq.port=5672
# 用户名
spring.rabbitmq.username=admin
# 密码
spring.rabbitmq.password=admin
# 配置虚拟机
spring.rabbitmq.virtual-host=/
# 消息开启手动确认
spring.rabbitmq.listener.direct.acknowledge-mode=manual
work模式
这是rabbitMQ官网给的work_queues的图例
生产者添加配置文件
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@Configuration
public class RabbitmqConfig {
/**
* 配置一个工作模型队列
* @return
*/
@Bean
public Queue queueWork1() {
return new Queue("queue_work");
}
}
添加controller方便测试
import com.xianyu.pro.service.RabbitmqService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@RestController
public class RabbitmqController {
@Autowired
private RabbitmqService rabbitmqService;
@RequestMapping("/sendWork")
public Object sendWork() {
rabbitmqService.sendWork();
return "发送成功...";
}
}
service
import com.xianyu.pro.service.RabbitmqService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@Service
public class RabbitmqServiceImpl implements RabbitmqService {
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public void sendWork() {
for (int i = 0; i < 10; i++) {
//消息一次性发送
rabbitTemplate.convertAndSend("queue_work","测试work模型: " + i);
}
}
}
启动,发送测试
在rabbitMQ的控制台已经可以看到消息
编写消费者
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@Component
public class WorkReceiveListener {
@RabbitListener(queues = "queue_work")
public void receiveMessage(String msg) {
// 只包含发送的消息
System.out.println("1接收到消息:" + msg);
}
@RabbitListener(queues = "queue_work")
public void receiveMessage2(String msg) {
// 包含所有的信息
System.out.println("2接收到消息:" + msg);
}
}
启动消费者
已完成消费
控制台上可以看到队列也没有消息了
发布订阅模式
Publish/Subscribe模式
这里用到了交换机Exchange,就是紫色的X
work一个队列里面的消息,被竞争消费,发布订阅模式互补干扰
比如生产者发布了10条消息,有两个消费者,work模式可能就是一个消费者消费5条,发布订阅模式就是每个消费者都能消费10条
修改RabbitmqConfig
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@Configuration
public class RabbitmqConfig {
/**
* 配置一个工作模型队列
* @return
*/
@Bean
public Queue queueWork1() {
return new Queue("queue_work");
}
/**
* 准备一个交换机
* @return
*/
@Bean
public FanoutExchange exchangeFanout() {
return new FanoutExchange("exchange_fanout");
}
/**
* 声明两个队列
* @return
*/
@Bean
public Queue queueFanout1() {
return new Queue("queue_fanout1");
}
@Bean
public Queue queueFanout2() {
return new Queue("queue_fanout2");
}
/**
* 将交换机和队列1进行绑定
* @return
*/
@Bean
public Binding bindingExchange1() {
return BindingBuilder.bind(queueFanout1()).to(exchangeFanout());
}
/**
* 将交换机和队列2进行绑定
* @return
*/
@Bean
public Binding bindingExchange2() {
return BindingBuilder.bind(queueFanout2()).to(exchangeFanout());
}
}
添加接口
controller
@RequestMapping("/sendPublish")
public Object sendPublish() {
rabbitmqService.sendPublish();
return "发送成功...";
}
service
/**
* 发送订阅消息
*/
void sendPublish();
serviceImpl
@Override
public void sendPublish() {
for (int i = 0; i < 5; i++) {
rabbitTemplate.convertAndSend("exchange_fanout", "", "测试发布订阅模型:" + i);
}
}
测试,发送成功,查看控制台
编写消费端
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@Component
public class PublishReceiveListener {
@RabbitListener(queues = "queue_fanout1")
public void receiveMsg1(String msg) {
System.out.println("队列1接收到消息:" + msg);
}
@RabbitListener(queues = "queue_fanout2")
public void receiveMsg2(String msg) {
System.out.println("队列2接收到消息:" + msg);
}
}
接收到消息
控制台
routing和topics模式
routing模式
修改RabbitmqConfig配置文件
mport org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@Configuration
public class RabbitmqConfig {
/**
* 配置一个工作模型队列
* @return
*/
@Bean
public Queue queueWork1() {
return new Queue("queue_work");
}
/**
* 准备一个交换机
* @return
*/
@Bean
public FanoutExchange exchangeFanout() {
return new FanoutExchange("exchange_fanout");
}
/**
* 声明两个队列
* @return
*/
@Bean
public Queue queueFanout1() {
return new Queue("queue_fanout1");
}
@Bean
public Queue queueFanout2() {
return new Queue("queue_fanout2");
}
/**
* 将交换机和队列1进行绑定
* @return
*/
@Bean
public Binding bindingExchange1() {
return BindingBuilder.bind(queueFanout1()).to(exchangeFanout());
}
/**
* 将交换机和队列2进行绑定
* @return
*/
@Bean
public Binding bindingExchange2() {
return BindingBuilder.bind(queueFanout2()).to(exchangeFanout());
}
/**
* routing交换机
* @return
*/
@Bean
public DirectExchange exchangeRouting() {
return new DirectExchange("exchange_routing");
}
/**
* routing队列1
* @return
*/
@Bean
public Queue queueRouting1() {
return new Queue("queue_routing1");
}
/**
* routing队列2
* @return
*/
@Bean
public Queue queueRouting2() {
return new Queue("queue_routing2");
}
@Bean
public Binding bindingRouting1() {
return BindingBuilder.bind(queueRouting1()).to(exchangeRouting()).with("error");
}
@Bean
public Binding bindingRouting2() {
return BindingBuilder.bind(queueRouting2()).to(exchangeRouting()).with("info");
}
}
在交换机绑定队列是添加routingKey,这里是自定义字符串
添加接口
@RequestMapping("/sendRouting")
public Object sendRouting() {
rabbitmqService.sendRouting();
return "发送成功...";
}
service
/**
* 发送routing消息
*/
void sendRouting();
serviceImpl
@Override
public void sendRouting() {
for (int i = 0; i < 5; i++) {
rabbitTemplate.convertAndSend("exchange_routing", "error", "测试routing模型,error:" + i);
rabbitTemplate.convertAndSend("exchange_routing", "info", "测试routing模型,info:" + i);
}
}
消费端添加监听
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@Component
public class RoutingReceiveListener {
@RabbitListener(queues = "queue_routing1")
public void receiveMsg1(String msg) {
System.out.println("消费者1接收到:" + msg);
}
@RabbitListener(queues = "queue_routing2")
public void receiveMsg2(String msg) {
System.out.println("消费者2接收到:" + msg);
}
}
postman测试
消费端打印
这里消费者1只接收到了error,消费者2只接收到了info
topics模式
routing和topics类似,topics可以理解为使用 * # 通配符的routing
topic里面的通配符,*表示一个单词,#表示多个
修改RabbitmqConfig 配置文件
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@Configuration
public class RabbitmqConfig {
/**
* 配置一个工作模型队列
* @return
*/
@Bean
public Queue queueWork1() {
return new Queue("queue_work");
}
/**
* 准备一个交换机
* @return
*/
@Bean
public FanoutExchange exchangeFanout() {
return new FanoutExchange("exchange_fanout");
}
/**
* 声明两个队列
* @return
*/
@Bean
public Queue queueFanout1() {
return new Queue("queue_fanout1");
}
@Bean
public Queue queueFanout2() {
return new Queue("queue_fanout2");
}
/**
* 将交换机和队列1进行绑定
* @return
*/
@Bean
public Binding bindingExchange1() {
return BindingBuilder.bind(queueFanout1()).to(exchangeFanout());
}
/**
* 将交换机和队列2进行绑定
* @return
*/
@Bean
public Binding bindingExchange2() {
return BindingBuilder.bind(queueFanout2()).to(exchangeFanout());
}
/**
* routing交换机
* @return
*/
@Bean
public DirectExchange exchangeRouting() {
return new DirectExchange("exchange_routing");
}
/**
* routing队列1
* @return
*/
@Bean
public Queue queueRouting1() {
return new Queue("queue_routing1");
}
/**
* routing队列2
* @return
*/
@Bean
public Queue queueRouting2() {
return new Queue("queue_routing2");
}
@Bean
public Binding bindingRouting1() {
return BindingBuilder.bind(queueRouting1()).to(exchangeRouting()).with("error");
}
@Bean
public Binding bindingRouting2() {
return BindingBuilder.bind(queueRouting2()).to(exchangeRouting()).with("info");
}
/**
* topic交换机
* @return
*/
@Bean
public TopicExchange exchangeTopic() {
return new TopicExchange("exchange_topic");
}
/**
* topic队列1
* @return
*/
@Bean
public Queue queueTopic1() {
return new Queue("queue_topic1");
}
/**
* topic队列2
* @return
*/
@Bean
public Queue queueTopic2() {
return new Queue("queue_topic2");
}
/**
* 绑定topic队列和交换机
* @return
*/
@Bean
public Binding bindingTopic1() {
return BindingBuilder.bind(queueTopic1()).to(exchangeTopic()).with("topic.#");
}
@Bean
public Binding bindingTopic2() {
return BindingBuilder.bind(queueTopic2()).to(exchangeTopic()).with("topic.*");
}
}
添加controller
@RequestMapping("/sendTopic")
public Object sendTopic() {
rabbitmqService.sendTopic();
return "发送成功...";
}
service
/**
* 发送topic消息
*/
void sendTopic();
serviceImpl
@Override
public void sendTopic() {
for (int i = 0; i < 5; i++) {
rabbitTemplate.convertAndSend("exchange_topic", "topic.hello", "测试topic模型,hello:" + i);
rabbitTemplate.convertAndSend("exchange_topic", "topic.hello.word", "测试topic模型,hello_word:" + i);
}
}
启动,postman测试
编写消费者
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@Component
public class TopicReceiveListener {
@RabbitListener(queues = "queue_topic1")
public void receiveMsg1(String msg) {
System.out.println("消费者1接收到:" + msg);
}
@RabbitListener(queues = "queue_topic2")
public void receiveMsg2(String msg) {
System.out.println("消费者2接收到:" + msg);
}
}
启动测试
可以看到消费者1能接收到所有的消息,消费者2只能接收到hello,不能接收hello.word作为routingKey的消息
rabbitMQ的几种基础用法到这里就说完了
rabbitMQ 3种消息确认机制
# 消息开启手动确认 手动确认后队列才会删除消息
spring.rabbitmq.listener.direct.acknowledge-mode=manual
# 不用确认,消费端接收到消息后,队列中删除
spring.rabbitmq.listener.direct.acknowledge-mode=none
# 自动模式,根据报错信息来判断是否删除消息,较麻烦,实际使用也很少
spring.rabbitmq.listener.direct.acknowledge-mode=auto
消息的确认投递和确认消费
后续再补充
TTL队列
死信队列
一般来说,producer 将消息投递到 broker 或者直接到queue 里了,consumer 从 queue 取出消息
进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有
后续的处理,就变成了死信,有死信自然就有了死信队列。
为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息
消费发生异常时,将消息投入死信队列中.
死信的来源
消息 TTL 过期
队列达到最大长度(队列满了,无法再添加数据到 mq 中)
消息被拒绝(basic.reject 或 basic.nack)并且 requeue=false.
代码架构图
RabbitmqConfig配置文件中添加业务交换机,业务队列,死信队列
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@Configuration
public class RabbitmqConfig {
/**
* 配置一个工作模型队列
*
* @return
*/
@Bean
public Queue queueWork1() {
return new Queue("queue_work");
}
/**
* 准备一个交换机
*
* @return
*/
@Bean
public FanoutExchange exchangeFanout() {
return new FanoutExchange("exchange_fanout");
}
/**
* 声明两个队列
*
* @return
*/
@Bean
public Queue queueFanout1() {
return new Queue("queue_fanout1");
}
@Bean
public Queue queueFanout2() {
return new Queue("queue_fanout2");
}
/**
* 将交换机和队列1进行绑定
*
* @return
*/
@Bean
public Binding bindingExchange1() {
return BindingBuilder.bind(queueFanout1()).to(exchangeFanout());
}
/**
* 将交换机和队列2进行绑定
*
* @return
*/
@Bean
public Binding bindingExchange2() {
return BindingBuilder.bind(queueFanout2()).to(exchangeFanout());
}
/**
* routing交换机
*
* @return
*/
@Bean
public DirectExchange exchangeRouting() {
return new DirectExchange("exchange_routing");
}
/**
* routing队列1
*
* @return
*/
@Bean
public Queue queueRouting1() {
return new Queue("queue_routing1");
}
/**
* routing队列2
*
* @return
*/
@Bean
public Queue queueRouting2() {
return new Queue("queue_routing2");
}
@Bean
public Binding bindingRouting1() {
return BindingBuilder.bind(queueRouting1()).to(exchangeRouting()).with("error");
}
@Bean
public Binding bindingRouting2() {
return BindingBuilder.bind(queueRouting2()).to(exchangeRouting()).with("info");
}
/**
* topic交换机
*
* @return
*/
@Bean
public TopicExchange exchangeTopic() {
return new TopicExchange("exchange_topic");
}
/**
* topic队列1
*
* @return
*/
@Bean
public Queue queueTopic1() {
return new Queue("queue_topic1");
}
/**
* topic队列2
*
* @return
*/
@Bean
public Queue queueTopic2() {
return new Queue("queue_topic2");
}
/**
* 绑定topic队列和交换机
*
* @return
*/
@Bean
public Binding bindingTopic1() {
return BindingBuilder.bind(queueTopic1()).to(exchangeTopic()).with("topic.#");
}
@Bean
public Binding bindingTopic2() {
return BindingBuilder.bind(queueTopic2()).to(exchangeTopic()).with("topic.*");
}
// 声明业务Exchange
@Bean
public FanoutExchange businessExchange() {
return new FanoutExchange("business_exchange");
}
// 声明死信Exchange
@Bean
public DirectExchange deadLetterExchange() {
return new DirectExchange("dead_letter_exchange");
}
// 声明业务队列A
@Bean
public Queue businessQueueA() {
Map<String, Object> args = new HashMap<>(2);
args.put("x-dead-letter-exchange", "dead_letter_exchange");
args.put("x-dead-letter-routing-key", "dead_letter_queuea_routing_key");
return QueueBuilder.durable("business_queueA").withArguments(args).build();
}
// 声明业务队列B
@Bean
public Queue businessQueueB() {
Map<String, Object> args = new HashMap<>(2);
args.put("x-dead-letter-exchange", "dead_letter_exchange");
args.put("x-dead-letter-routing-key", "dead_letter_queueb_routing_key");
return QueueBuilder.durable("business_queueB").withArguments(args).build();
}
// 声明死信队列A
@Bean
public Queue deadLetterQueueA() {
return new Queue("dead_letter_queueA");
}
// 声明死信队列B
@Bean
public Queue deadLetterQueueB() {
return new Queue("dead_letter_queueB");
}
// 声明业务队列A绑定关系
@Bean
public Binding businessBindingA() {
return BindingBuilder.bind(businessQueueA()).to(businessExchange());
}
// 声明业务队列B绑定关系
@Bean
public Binding businessBindingB() {
return BindingBuilder.bind(businessQueueB()).to(businessExchange());
}
// 声明死信队列A绑定关系
@Bean
public Binding deadLetterBindingA() {
return BindingBuilder.bind(deadLetterQueueA()).to(deadLetterExchange()).with("dead_letter_queuea_routing_key");
}
// 声明死信队列B绑定关系
@Bean
public Binding deadLetterBindingB() {
return BindingBuilder.bind(deadLetterQueueB()).to(deadLetterExchange()).with("dead_letter_queueb_routing_key");
}
}
参数map的key是固定的,在RabbitMQ控制台中能够看到
添加controller方便测试
@RequestMapping("/sendDead")
public Object sendDead() {
rabbitmqService.sendDead();
return "发送成功...";
}
service
/**
* 测试死信队列
*/
void sendDead();
serviceImpl
@Override
public void sendDead() {
rabbitTemplate.convertSendAndReceive("business_exchange", "", "测试死信队列");
}
消费端代码实现
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @authoer:xianyu
* @createDate:2022/7/1
* @description:
*/
@Component
public class BusinessMessageReceiver {
@RabbitListener(queues = "business_queueA")
public void receiveA(Message message, Channel channel) throws IOException {
String msg = new String(message.getBody());
boolean ack = true;
try {
//手动制造异常
int i=1/0;
} catch (Exception e){
ack = false;
}
if (!ack){
//拒绝消息,并不放回队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
} else {
//确认消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
@RabbitListener(queues = "business_queueB")
public void receiveB(Message message, Channel channel) throws IOException {
System.out.println("收到业务消息B:" + new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
这里basicNack方法第三个参数:是否重回队列,false否,如果绑定了死信队列,就会进入死信队列
这里只展示者一种进入死信队列的方式,其他的后续补充