因项目紧急所以停更了一段时间,废话不多说,直接开始干货。
因为这几种模式在我的前面几篇博客中已经详细介绍了,不清楚的可以回顾我的上一篇博客进行系统的学习。现在直接开始整和代码,其实和java连接区别不大。
首先新建一个maven项目
生产者的maven的依赖
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
生产者的项目结构为:
生产者的application.yml配置文件中的配置为:
server:
port: 8080
spring:
application:
name: producer
rabbitmq:
host: localhost
port: 5672
username: test
password: 123456
virtual-host: v-test
#消息到达交换机进行通知,默认是不做通知
publisher-confirm-type: correlated #开启消息到达交换机的确认机制
publisher-returns: true #消息由交换机到达队列时失败出发
使用的rabbitmq的账号和之前文章中用的的账号一样,大家可以在rabbitmq客户端操作界面上自行操作创建自己的账号。
另创建一个maven项目作为消费者
maven依赖和生产者的maven依赖一样
消费者的项目结构为:
消费者的application.yml配置为:
server:
port: 8081
spring:
application:
name: producer
rabbitmq:
host: localhost
port: 5672
username: test
password: 123456
virtual-host: v-test
注意,生产者和消费者的端口号不能重复,否则不能启动项目。
开始RabbitMQ五种模式的测试
1.简单模式
包含一个生产者、一个消费者和一个队列,生产者向队列发送消息,消费者从队列中获取并消费消息。
P为生产者,C为消费者,中间为消息队列,下面我们简单的用代码实现。
生产者新建SendRabbitMQConfig设置类
@Configuration
public class SendRabbitMQConfig {
/**
* 创建一个队列
* @return
*/
@Bean
public Queue hello(){
return new Queue("hello");
}
}
生产者新建一个测试类并且注入rabbitTemplate,并且测试是否成功注入。
@SpringBootTest
class SpringbootRabbitmqApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
}
@Test
void contextLoads() {
System.out.println(rabbitTemplate);
}
生产者添加直连模式的发送代码
/**
* 直连模型的消息发送
*/
@Test
void sendHello() {
rabbitTemplate.convertAndSend("hello", "hello world");
System.out.println("消息发送成功");
}
消费者接收代码
/**
* @author: BigGreysheep
*
* 消费者(直连模式)
*/
@Component
@RabbitListener(queuesToDeclare = {@Queue("hello")})
public class Coustmer {
@RabbitHandler
public void receiveMessage(String body){
System.out.println("消费者收到消息,内容为:"+body);
}
}
先启动消费者再启动生产者结果为:
消费者接收的结果:
2.工作模式(workPattern)
工作模式是指多个相互竞争的消费者发送消息的模式,它包含一个生产者、两个消费者和一个队列。两个消费者同时绑定到一个队列上去,当消费者获取消息处理耗时任务时,空闲的消费者从队列中获取b并消费队列。
生产者的Config代码:
@Configuration
public class WorkConfig {
/**
* 创建一个队列
* @return
*/
@Bean
public Queue work(){
Queue work = new Queue("work");
return work;
}
}
在发送测试类中添加第二种方法的发送代码:
/**
* 第二种模型的消息发送
*/
@Test
void sendWork() {
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("work", "hello world-----" + i);
}
System.out.println("消息发送完成");
}
消费者的消费代码
@Component
public class WorkCoustmer {
@RabbitListener(queuesToDeclare = {@Queue("work")})
public void receiveMessage1(String body) {
System.out.println("消费者[1]收到消息,内容为:" + body);
}
@RabbitListener(queuesToDeclare = {@Queue("work")})
public void receiveMessage2(String body) {
System.out.println("消费者[2]收到消息,内容为:" + body);
}
}
运行的结果为:
3.发布/订阅模式(fanout)
指同时向多个消费者发送消息的模式(类似广播),它包括一个生产者、两个消费者、两个队列和一个交换机。两个消费者同时绑定到不同的队列上,两个队列绑定到交换机上去,生产者通过发送消息到交换机,所有消费者接收并消费消息。
生产者的Config
public class FanoutConfig {
/**
* 声明交换机
*/
@Bean
public FanoutExchange fanoutExange() {
FanoutExchange fanoutExchange = new FanoutExchange("logs");
return fanoutExchange;
}
/**
* 声明队列1
*/
@Bean
public Queue fanoutQueue1() {
Queue queue = new Queue("fanout_queue1");
return queue;
}
/**
* 声明队列2
*/
@Bean
public Queue fanoutQueue2() {
Queue queue = new Queue("fanout_queue2");
return queue;
}
/**
* 队列1绑定交换机
*/
@Bean
public Binding binding1(){
Binding binding= BindingBuilder.bind(fanoutQueue1()).to(fanoutExange());
return binding;
}
/**
* 队列2绑定交换机
*/
@Bean
public Binding binding2(){
Binding binding= BindingBuilder.bind(fanoutQueue2()).to(fanoutExange());
return binding;
}
}
在发送测试类中添加第三种方法的发送代码:
/**
* 第三种模型的消息发送(fanout)
*/
@Test
void sendFanout() {
rabbitTemplate.convertAndSend("logs", "", "这是一个fanout的日志消息");
System.out.println("消息发送完成");
}
消费者的接收代码:
@Component
public class FanoutCoustmer {
/**
* 接收第一个队列里的信息
* @param message
*/
@RabbitListener(bindings = @QueueBinding( //把队列和交换机进行绑定
value=@Queue("fanout_queue1"), //绑定队列
exchange = @Exchange(name = "logs",type= ExchangeTypes.FANOUT) //绑定交换机
))
public void receive1(String message){
System.out.println("消费者【1】接收到消息"+message);
}
/**
* 接收第二个队列里的信息
* @param message
*/
@RabbitListener(bindings = @QueueBinding(
value=@Queue("fanout_queue2"),
exchange = @Exchange(name = "logs",type= ExchangeTypes.FANOUT)
))
public void receive2(String message){
System.out.println("消费者【2】接收到消息"+message);
}
}
生产者的运行结果
消费者运行结果
4.路由模式
路由模式是可以根据路由键选择性给多个消费者发送消息的模式,它包括一个生产者、两个消费者、两个队列和一个交换机。两个消费者同时绑定到不同的不同的队列上去,两个队列通过路由键绑定到交换机上,生产者发送消息到交换机,交换机通过路由键转发到不同队列,队列绑定的消费者接受消息。
生产者的Config
@Configuration
public class RoutingDirectConfig {
/**
* 声明交换机
*/
@Bean
public DirectExchange directExange() {
DirectExchange directExchange = new DirectExchange("directs");
return directExchange;
}
}
在发送测试类中添加第四种发送代码:
/**
* 第四种模型的消息发送(RoutingDirect)
*/
@Test
void sendRoutingDirect() {
rabbitTemplate.convertAndSend("directs", "info", "info的日志信息");
rabbitTemplate.convertAndSend("directs", "warm", "warm的日志信息");
rabbitTemplate.convertAndSend("directs", "debug", "debug的日志信息");
rabbitTemplate.convertAndSend("directs", "error", "error的日志信息");
System.out.println("消息发送成功");
}
运行结果:
因没有创建消费者四所以没有接收到warm消息,此知识点后面会说。
5.通配符模式
通配符模式是可以根据路由键匹配规则选择性给多个消费者发送消息的模式,它包含一个生产者、两个消费者、两个队列和一个交换机。两个消费者同时绑定到不同的队列上去,两个队列通过路由键匹配规则绑定到交换机上去,生产者发送消息到交换机,交换机通过路由键匹配规则转发到不同队列,队列绑定的消费者接收并消费消息。
生产者的Config
@Configuration
public class RoutingTopicConfig {
/**
* 声明交换机
*/
@Bean
public TopicExchange topicExchange() {
TopicExchange topicExchange = new TopicExchange("topic");
return topicExchange;
}
/**
* 声明队列1
*/
@Bean
public Queue fanoutQueue1() {
Queue queue = new Queue("topic_queue1");
return queue;
}
/**
* 声明队列2
*/
@Bean
public Queue fanoutQueue2() {
Queue queue = new Queue("topic_queue2");
return queue;
}
/**
* 队列1绑定交换机
*/
@Bean
public Binding binding1(){
Binding binding= BindingBuilder.bind(fanoutQueue1()).to(topicExchange()).with("user.*");
return binding;
}
/**
* 队列2绑定交换机
*/
@Bean
public Binding binding2(){
Binding binding= BindingBuilder.bind(fanoutQueue2()).to(topicExchange()).with("user.#");
return binding;
}
}
生产者发送消息的测试代码
/**
* 第五种模型的消息发送(RoutingTopic)
*/
@Test
void sendRoutingTopic() {
rabbitTemplate.convertAndSend("topic", "user.save", "用户的保存操作");
rabbitTemplate.convertAndSend("topic", "user.update", "用户的更新操作");
rabbitTemplate.convertAndSend("topic", "user", "用户的操作");
}
消费者的消费端代码
@Component
public class RoutingTopicCoustmer {
/**
* 接收第一个队列里的信息
* *匹配路由键的一个词
* @param message
*/
@RabbitListener(bindings = @QueueBinding( //把队列和交换机进行绑定
value=@Queue(value = "topic_queue1", durable = "true", autoDelete = "false"), //绑定队列
key = {"user.*"},
exchange = @Exchange(name = "topic",type= ExchangeTypes.TOPIC) //绑定交换机
))
public void receive1(String message){
System.out.println("消费者【1】接收到消息"+message);
}
/**
* 接收第二个队列里的信息
* #号匹配路由键的一个或多个词
* @param message
*/
@RabbitListener(bindings = @QueueBinding( //把队列和交换机进行绑定
value=@Queue(value = "topic_queue2", durable = "true", autoDelete = "false"), //绑定队列
key = {"user.#"},
exchange = @Exchange(name = "topic",type= ExchangeTypes.TOPIC) //绑定交换机
))
public void receive2(String message){
System.out.println("消费者【2】接收到消息"+message);
}
}
运行结果:
消息监听类
生产者的消息监听类
@Component
public class WatchMessageImpl implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void initRabbitTemplate(){
rabbitTemplate.setReturnsCallback(this);
rabbitTemplate.setConfirmCallback(this);
}
/**
* 当消息到达交换机之后触发(到达交换机之后,该方法会被回调)
*
* @param correlationData 相关数据
* @param ack 交换机接收消息是否成功
* @param cause 如果没有成功,返回原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
System.out.println("交换机接收消息成功");
} else {
System.out.println("交换机接收消息失败,失败原因是:" + cause);
}
}
/**
* 消息如果未正常到达队列里面 会回调
*
* @param returnedMessage 消息内容 回调相应码 响应文本 交换机 路由key
*/
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
System.out.println("队列未接收到消息");
System.out.println("ReplyText:" + returnedMessage.getReplyText());
System.out.println("RoutingKey:" + returnedMessage.getRoutingKey());
System.out.println("ReplyCode:" + returnedMessage.getReplyCode());
System.out.println("Message:" + returnedMessage.getMessage());
}
}
手动签收:
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "mac_id", durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "data", type = ExchangeTypes.DIRECT), key = "MAC"))
@RabbitHandler
public void receivedHomeMac(Message message, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel)
throws IOException {
boolean result = true;
try {
log.info("数据接收成功", message);
} catch (Exception e) {
log.error("数据同步异常,", e);
result = false;
}
if (result) {
// RabbitMQ的ack机制中,第二个参数返回true,表示需要将这条消息投递给其他的消费者重新消费(不用改)
channel.basicAck(deliveryTag, false);
} else {
/**
* 参数说明:
*
* 参数一:要签收的投递ID是多少
* 参数二:是否批量签收
* 参数三:true;拒绝签收并将消息重新放回消息队列里面 false;拒绝签收,消息不放回消息队列里面
*/
channel.basicNack(deliveryTag, false, true);
}
}