目录
1、创建Springboot项目、导入依赖
2、配置文件中添加rabbitmq的连接信息
3、编写rabbitmq的配置类
4、编写投递消息接口
5、编写邮件消费者
6、编写短信消费者
7、测试
8、使用两个邮件消费者监听邮件队列
9、实现能者多劳和手动消息确认
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-amqp
org.apache.commons
commons-lang3
com.alibaba
fastjson
1.2.49
org.projectlombok
lombok
spring:
rabbitmq:
#连接地址
host: 127.0.0.1
#端口号
port: 5672
#账号
username: guest
#密码
password: guest
#虚拟主机
virtual-host: /rkVirtualHost
server:
port: 8080
package com.rk.config;
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;
/**
* @author :Rk.
* @date : 2022/10/8
* 定义完成后 rabbitmq服务器会自动创建交换机和队列 以及绑定关系
*/
@Configuration
public class RabbitMQConfig {
/**
* 定义交换机名称
*/
private String EXCHANGE_NAME="springboot_falout_exchange";
/**
* 短信队列
*/
private String FANOUT_SMS_QUEUE = "fanout_sms_queuerk";
/**
* 邮件队列
*/
private String FANOUT_EMAIL_QUEUE = "fanout_email_queuerk";
/**
* 定义 扇形交换机
*/
@Bean
public FanoutExchange fanoutExchange(){
//参数 交换机名称
return new FanoutExchange(EXCHANGE_NAME);
}
/**
*参数一:队列名称
*参数二durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
*参数三exclusive:默认也是false,是否独占队列
*参数四autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
*/
@Bean
public Queue smsQueue(){
return new Queue(FANOUT_SMS_QUEUE,true, false,false);
}
/**
*参数一:队列名称
*参数二:durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
*参数三:exclusive:默认也是false,是否独占队列
*参数四:autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
*/
@Bean
public Queue emailQueue(){
return new Queue(FANOUT_EMAIL_QUEUE,true,false,false);
}
/**
* 短信队列绑定交换机
* @param smsQueue 短信队列注入到容器的id,也就是方法名 sumQueue
* @param fanoutExchange 交换机注入到容器的id,也就是方法名 fanoutExchange
* @return
*/
@Bean
public Binding bindingSmsFanoutExchange(Queue smsQueue,FanoutExchange fanoutExchange){
return BindingBuilder.bind(smsQueue).to(fanoutExchange);
}
/**
* 邮件队列队列绑定交换机
* @param emailQueue 短信队列注入到容器的id,也就是方法名 emailQueue
* @param fanoutExchange 交换机注入到容器的id,也就是方法名 fanoutExchange
* @return
*/
@Bean
public Binding bindingEmailFanoutExchange(Queue emailQueue,FanoutExchange fanoutExchange){
return BindingBuilder.bind(emailQueue).to(fanoutExchange);
}
}
这里使用的是扇形交换机,交换机会把消息路由到绑定到它的所有队列。
在Spring启动时,利用Spring Bean管理工厂BeanFactory接口,实现动态
创建交换机
、队列
、交换机和队列的绑定关系
,让我们无需进行重复的编码工作。
package com.rk.controller;
import com.rk.domain.User;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author :Rk.
* @date : 2022/10/8
*/
@RestController
public class FanoutProducerController {
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 模拟注册接口
* @param
* @return
*/
@GetMapping("/registered")
public String sendMsg(){
User user=new User("rk",22,"男","陕西西安");
//1、将user存入数据库
//2、发送邮件和短信
//投递消息到交换机 交换机将消息路由到邮件队列和短信队列
/**
* 参数一 交换机名称
* 参数二 路由key
* 参数三 发送内容
*/
amqpTemplate.convertAndSend("springboot_falout_exchange","",user);
return "注册完成";
}
}
这里直接发送的user对象,必须要将user对象实现Serializable接口。
import com.rk.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author :Rk.
* @date : 2022/10/8
/* */
@Slf4j
@Component
@RabbitListener(queues = "fanout_email_queuerk") //监听队列
public class EmailConsumer {
//监听到队列消息后执行
@RabbitHandler
public void process(User user) {
System.err.println("邮件消费者接收到消息发送邮件:"+user);
}
}
import com.rk.domain.User;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author :Rk.
* @date : 2022/10/8
*/
@Component
@RabbitListener(queues = "fanout_sms_queuerk")
public class SumConsumer {
@RabbitHandler
public void process(User user){
System.out.println("短信消费者接收到消息发送短信:"+user);
}
}
访问注册接口,会立即响应。
邮件消费者1:
import com.rk.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author :Rk.
* @date : 2022/10/8
/* */
@Slf4j
@Component
@RabbitListener(queues = "fanout_email_queuerk") //监听队列
public class EmailConsumer1 {
//监听到队列消息后执行
@RabbitHandler
public void process(User user) throws InterruptedException {
//休眠10ms 模拟处理时间
Thread.sleep(10);
System.err.println("邮件消费者1接收到消息发送邮件:"+user);
}
}
邮件消费者2:
import com.rk.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author :Rk.
* @date : 2022/10/8
/* */
@Slf4j
@Component
@RabbitListener(queues = "fanout_email_queuerk")
public class EmailConsumer2 {
@RabbitHandler
public void process(User user) throws InterruptedException {
//休眠20ms 模拟处理时间
Thread.sleep(20);
System.out.println("邮件消费者2接收到消息发送邮件:"+user);
}
}
测试: 生产者往交换机发送20条消息:
两个消费者使用轮询的方式对消息进行消费,而且不存在重复消费。 这里消费者1的处理时间是10ms,消费者2的处理时间为20ms,所以使用轮询的方式来进行消费并不合理,需要实现能者多劳。
更改配置文件的参数来解决这一个问题:
邮件消费者1:
package com.rk.service;
import com.rabbitmq.client.Channel;
import com.rk.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author :Rk.
* @date : 2022/10/8
/* */
@Slf4j
@Component
@RabbitListener(queues = "fanout_email_queuerk")
public class EmailConsumer1 {
@RabbitHandler
public void process(User user, Message message, Channel channel) throws IOException {
try {
//休眠20ms 模拟处理时间
Thread.sleep(10);
System.err.println("邮件消费者1接收到消息发送邮件:"+user);
//确认一条消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}catch (Exception e){
//消费者告诉队列信息消费失败
/**
* 拒绝确认消息:
* channel.basicNack(long deliveryTag, boolean multiple, boolean requeue) ;
* deliveryTag:该消息的index
* multiple:是否批量true:将一次性拒绝所有小于deliveryTag的消息
* requeue:被拒绝的是否重新入队列
*/
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
邮件消费者2:
import com.rabbitmq.client.Channel;
import com.rk.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author :Rk.
* @date : 2022/10/8
/* */
@Slf4j
@Component
@RabbitListener(queues = "fanout_email_queuerk")
public class EmailConsumer4 {
@RabbitHandler
public void process(User user, Message message, Channel channel) throws IOException {
try {
//确认一条消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//休眠20ms 模拟处理时间
Thread.sleep(20);
System.out.println("邮件消费者2接收到消息发送邮件:"+user);
}catch (Exception e){
//消费者告诉队列信息消费失败
/**
* 拒绝确认消息:
* channel.basicNack(long deliveryTag, boolean multiple, boolean requeue) ;
* deliveryTag:该消息的index
* multiple:是否批量true:将一次性拒绝所有小于deliveryTag的消息
* requeue:被拒绝的是否重新入队列
*/
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
实现能者多劳和手动确认。上面的演示过程都是将生产者和消费者写在同一个项目中,在实际开发过程中,一般使用2个项目,一个项目来投递消息,一个专门用来消费消息,使用不同的JVM来运行生产者和消费者。
整合直连交换机和主题交换机可以看下面这篇博客:
Springboot 整合RabbitMq ,用心看完这一篇就够了_小目标青年的博客-CSDN博客_springboot整合rabbitmq