RabbitMQ常用消息模型应用
RabbitMQ消息模型官方提供了如下七种,但是常用的也就前面五种:
- 简单队列模式----helloword
- 工作队列模式----work queue 在第一种模型(helloworld)之上,如果每一个消息在消费的时候如果处理时间过程,队列中消息越来越多,会导致消息不及时消费,造成很多问题,所以有了work
queue(任务)的消息模型,这种消息模型就是多个消费者消费同一个队列里面的消息,消息不会被多次消费- 广播模式----fanout 生产者把消息发送给交换机,在由交换机发送给各个订阅的队列(一个队列对应一个消费者),在由消费者消费,这种很消息模型还是比较常见的,比如说微信的订阅号,你订阅过后,就相当于队列订阅了交换机,然后生产者给交换机发送消息时,交换机就会给所有订阅他的队列发送消息
- 路由模式----Routing (路由)路由,就是可以按照路由key来匹配,确定要发送的队列,在有时候有些消息需要全部的消费者消费,有时候需要一部分消费者消费
- 主题模式----Topics (动态路由)在路由的消息模型之上,允许key使用通配符,这样就可以更加的灵活
- 远程过程调用模式----RPC
- 发布者确认模式----Publisher Confirms
其实那五种也很好区分,基本消息队列,工作消息队列,然后再根据发布订阅又分为三种广播、路由、主题。
提前声明:这里主要是各个模式代码的应用,每个模式都标注的很清楚,希望对你有帮助
先来说一下RabbitMQ中的几个角色,方便后续代码的理解:
- publisher:生产者
- consumer:消费者
- exchange个:交换机,负责消息路由,注意:exchange只负责消息路由,而不是存储,路由失败则消息丢失
- queue:队列,存储消息
- virtualHost:虚拟主机,隔离不同用户的exchange、queue、消息的隔离
首先创建一个父工程mq-demo用来管理项目依赖,再创建两个模块,分别是publisher:消息的发送者,consumer:消息的消费者。构建如下所示:
导入父工程pom坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.nuo.demo</groupId>
<artifactId>mq-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>publisher</module>
<module>consumer</module>
</modules>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--JSON方式序列化-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
在publisher和consumer服务中编写application.yml,添加mq连接信息内容都一样,来确定RabbitMQ地址【我是用Docker启动的RabbitMQ】:
logging:
pattern:
dateformat: MM-dd HH:mm:ss:SSS
spring:
rabbitmq:
host: 192.168.16.129 # 你自己的主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: admin # 你自己的rabbitmq用户名
password: 123 # 你自己的rabbitmq密码
在consumer服务中新建一个SpringRabbitListener类,编写消费逻辑:
package cn.nuo.mq.listener;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
import java.util.Map;
@Component
public class SpringRabbitListener {
// 1.helloworld模式
/* @RabbitListener(queues = "simple.queue")
public void listenerSimpleQueue(String message){
System.out.println("消费者接收到simple.queue队列的消息"+message);
}*/
/* 2.WorkQueue模型
* Work queues,也被称为(Task queues)任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。
* 长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。
* 队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
* 消费者1,领取任务并且完成任务,假设完成速度较慢
消费者2:领取任务并完成任务,假设完成速度快
*/
@RabbitListener(queues = "simple.queue")
public void listenerWorkQueue1(String message) throws InterruptedException {
System.out.println("消费者1接收到的消息"+message+ LocalTime.now());
Thread.sleep(20);
}
@RabbitListener(queues = "simple.queue")
public void listenerWorkQueue2(String message) throws InterruptedException {
System.err.println("消费者2----接收到的消息"+message+LocalTime.now());
Thread.sleep(200);
}
//3.FanoutExchange模式会将接收到的消息广播到每一个跟其绑定的queue
@RabbitListener(queues = "fanout.queue1")
public void listenerFanoutExchange1(String message) {
System.out.println("消费者1接收到的消息"+message);
}
@RabbitListener(queues = "fanout.queue2")
public void listenerFanoutExchange2(String message) {
System.err.println("消费者2----接收到的消息"+message);
}
/*描述下Direct交换机与Fanout交换机的差异?
Fanout交换机将消息路由给每一个与之绑定的队列
Direct交换机根据RoutingKey判断路由给哪个队列
如果多个队列具有相同的RoutingKey,则与Fanout功能类似*/
//4.路由模式-DirectExchange,会将接收到的消息根据规则路由到指定的Queue,
@RabbitListener(bindings = @QueueBinding(value =@Queue(name = "direct.queue1"),
exchange =@Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
key ={"red","blue"}))
public void listenerDirectExchange1(String message) {
System.out.println("消费者1接收到的消息"+message);
}
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
key = {"red","pink"}))
public void listenerDirectExchange2(String message) {
System.err.println("消费者2----接收到的消息"+message);
}
/* TopicExchange与DirectExchange类似,区别在于Topic交换机接收的消息RoutingKey必须是多个单词,并且以 . 分割。
Queue与Exchange指定BindingKey时可以使用通配符:
#:代指0个或多个单词
*:代指一个单词
*/
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
key = {"china.#"}))
public void listenerTopicExchange1(String message){
System.err.println("消费者1----接收到的消息"+message);
}
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
key = {"china.*"}))
public void listenerTopicExchange2(String message){ //
System.err.println("消费者2----接收到的消息"+message);
}
//6.消息转换器
@RabbitListener(queues = "object.queue")
public void listenerObjectQueue(Map<String,Object> message){
System.err.println("消费者----接收到的消息"+message);
}
}
在consumer服务中新建FanoutConfig类
package cn.nuo.mq.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//FanoutExchange模式
@Configuration
public class FanoutConfig {
/*- 创建一个交换机 itcast.fanout,类型是Fanout
-创建两个队列fanout.queue1和fanout.queue2,
绑定到交换机itcast.fanout*/
//声明FanoutExchange交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("itcast.fanout");
}
//声明一个队列
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
//将队列1绑定到交换机
@Bean
public Binding bindQueue1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
//声明第二个队列
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
//将队列2绑定到交换机
@Bean
public Binding bindQueue2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
//这是消息转换器的,与广播无关
@Bean
public Queue objectMessageQueue(){
return new Queue("object.queue");
}
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
}
在publisher服务创建SpringAmqpTest类用来发送消息的测试
package cn.nuo.mq.spring;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashMap;
import java.util.Map;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
//简单队列模式----helloword
@Test
public void testAmqp(){
String queueName = "simple.queue";
String message = "hello, SpringAmqp!";
rabbitTemplate.convertAndSend(queueName,message);
}
//WorkQueue模型
@Test
public void testWorkQueue() throws InterruptedException {
String queueName = "simple.queue";
String message = "hello, WorkQueue!";
for (int i = 0; i < 50; i++) {
rabbitTemplate.convertAndSend(queueName,message+i);
Thread.sleep(20);
}
}
//FanoutExchange模式
@Test
public void testFanoutExchange(){
String exchangeName = "itcast.fanout";
String message = "hello, FanoutExchange!";
// 发送消息,参数分别是:交互机名称、RoutingKey(暂时为空)、消息
rabbitTemplate.convertAndSend(exchangeName,"",message);
}
//路由模式,发布订阅-DirectExchange
@Test
public void testDirectExchange(){
String exchangeName = "itcast.direct";
String message = "hello,DirectExchange";
rabbitTemplate.convertAndSend(exchangeName,"red",message);
}
//发布订阅-TopicExchange
@Test
public void testTopicExchange(){
String exchangeName = "itcast.topic";
String message = "hello,TopicExchange";
rabbitTemplate.convertAndSend(exchangeName,"china.news",message);
}
//消息转换器
@Test
public void testSendMap(){
String queueName = "object.queue";
Map<String,Object> msg = new HashMap<>();
msg.put("name","jack");
msg.put("age",18);
rabbitTemplate.convertAndSend("object.queue",msg);
}
}