提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、MQ的开端
MQ全称是【Message Queue】(消息队列)是一种应用程序对应用程序的消息通信,一端只管往队列不断发布信息,另一端只管往队列中读取消息,发布者不需要关心读取消息的谁,读取消息者不需要关心发布消息的是谁,各干各的互不干扰。
其主要作用包括
- 应用解耦:一个业务需要多个模块共同实现,或一条消息有多个系统对应处理,只需要在主业务完成以后,发送一条MQ,其余模块消费MQ消息,即可实现业务,降低模块之间的耦合。
- 异步提速:主业务执行结束后,从属业务通过MQ异步处理,减少业务的响应时间,提高用户体验。
- 削峰填谷:高并发情况下,业务异步处理,提供高峰期业务处理能力,避免系统瘫痪。
话不多说,理论知识,百度一大堆,慢慢理解,直接开干 ~~
二、docker安装MQ
1、在线拉取镜像
docker pull rabbitmq:3-management
2、运行镜像
docker run \
-e RABBITMQ_DEFAULT_USER=root \
-e RABBITMQ_DEFAULT_PASS=root \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
#指定mq面板的账号和密码,默认账号密码 guest
-e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456
3、访问页面
使用ip + 15672 运行MQ的浏览器页面
登陆进去页面
在开始代码之前,了解其中的一些名词
publisher:生产者(顾名思义:生产消息的商家)
consumer:消费者(顾名思义:消费消息的买家)
exchange:交换机,负责消息路由(可以比喻成 快递方)
queue:队列,存储消息(相当于 快递小哥)
三、SpringAMQP
直接干spring,原始工程过于繁杂,需要建立连接,指定主机名、端口号、vhost、用户名、密码;创建通道Channel;创建队列等操作
SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。
1、依赖、配置文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
**publisher(商家)**服务 和 **consumer(买家)**服务的application.yml中添加配置
注意的是,两个boot项目,区分开服务端口,以面冲突(service.port: XXX)
spring:
rabbitmq:
host: 192.168.200.250
port: 5672
username: guest
password: guest
2、WorkQueue-工作队列
publisher(生产者)
新建配置文件,创建消息队列
@Configuration
public class RabbitmqConfig {
/*
* 单独创建队列 - 名字为:myQueue
* */
@Bean
protected Queue queue() {
Queue queue = new Queue("myQueue");
return queue;
}
}
发送消息的代码
@SpringBootTest
class DemoApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
/*
* 普通直接发送队列中
* */
@Test
public void OneQueue(){
//参数一:需要指向发送的目标队列名称
//参数二:发送的内容
rabbitTemplate.convertAndSend("myQueue","Hello World");
}
}
consumer(消费者)
使用@RabbitListener注解进行监听队列是否有值,
@Component
public class DemeReceive {
/*
* 监听队列 -->> myQueue
* */
@RabbitListener(queues = "myQueue")
public void demo(String msg) throws InterruptedException {
//参数msg: 就是消费队列消息的内容
System.out.println("\033[32m demo获取到的消息 --->>> \" + " + msg + "\033[0m");
}
2.1、WorkQueue-扩展
如果在生产方,生产大量的消息,而消费者绑定并且指定不同的睡眠时间来模拟日常代码执行速度
生产方:
@Test
public void OneQueue(){
//循环发送10条数据,方面查看
int index = 10;
for (int i = 0; i < index; i++) {
rabbitTemplate.convertAndSend("myQueue","这是我发的第" + i +"条消息");
}
}
消费方:
/*
* 监听者1号 --》》 myQueue
* */
@RabbitListener(queues = "myQueue")
public void demo(String msg) throws InterruptedException {
System.out.println("\033[32m demo获取到的消息 --->>> \" + " + msg + "\033[0m");
//睡眠时间 20
Thread.sleep(20);
}
/*
* 监听者2号 --》》 myQueue
*
* */
@RabbitListener(queues = "myQueue")
public void demo2(String msg) throws InterruptedException {
System.out.println("\033[31m demo2取到的消息 --->>> \" +" + msg + "\033[0m");
//睡眠时间 100
Thread.sleep(100);
}
效果如下:
方便查看,颜色区分两个监听者----
可以看到监听者1很快完成了自己的5条消息。监听者2却在缓慢的处理自己的5条消息。
也就是说消息是平均分配给每个消费者,并没有考虑到消费者的处理能力。这样显然是有问题的。
如需要修改则需要变动配置文件,通过设置prefetch来控制消费者预取的消息数量,
spring:
rabbitmq:
host: 192.168.200.250
username: guest
password: guest
listener:
simple:
# 通过设置prefetch来控制消费者预取的消息数量
prefetch: 1
每一个监听者,每次只处理一条数据,数据结束可以重新处理新的数据
效果如下:
能者多劳!!!
3、发布/订阅
可以看到,在订阅模型中,多了一个exchange角色,而且过程略有变化:
Publisher:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给exchange(交换机)
Exchange:交换机,图中的exchange。
- 一方面,接收生产者发送的消息。
- 另一方面,知道如何处理消息,
Exchange有以下3种类型: - Fanout:广播,将消息交给所有绑定到交换机的队列。
- Direct:定向,把消息交给符合指定routing key 的队列。
- Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列)
3.1、Fanout
生产者
创建消息队列和交换机,之间进行绑定
@Configuration
public class RabbitmqConfig {
//创建 消息队列1
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");
}
//创建 消息队列2
@Bean
public Queue fanoutQueue1() {
return new Queue("fanoutQueue1");
}
//创建 fanout类型 交换机
@Bean
public Queue fanoutQueue2() {
return new Queue("fanoutQueue2");
}
//通过 BindingBuilder 绑定消息队列1 到交换机上
//参数一:需要绑定的消息队列
//参数二:指定需绑定的交换机
@Bean
public Binding bindingFanoutQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
//通过 BindingBuilder 绑定队列2 到交换机上
//参数一:需要绑定的消息队列
//参数二:指定需绑定的交换机
@Bean
public Binding bingdingFanoutQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
发送消息
@Test
public void fanoutQueue(){
//参数一:指定需要发送的交换机命长
//参数二:路由key,此类型交换机无需指定
//参数三:需要发送的内容
rabbitTemplate.convertAndSend("fanoutExchange","","单独发送一条消息");
}
消费者
@RabbitListener(queues = "fanoutQueue1")
public void demo3(String msg){
System.out.println("\033[32m demo3获取到的消息 --->>> \" + " + msg + "\033[0m");
}
@RabbitListener(queues = "fanoutQueue2")
public void demo4(String msg){
System.out.println("\033[31m demo4取到的消息 --->>> \" +" + msg + "\033[0m");
}
效果如下:
FanoutExchange 的会将消息路由到每个绑定的队列,每个队列绑定的消费者都可以或者到消息进行消费
3.2、Direct
在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
队列与交换机的绑定之间是需要指定一个RoutingKey(路由key),理解成一个暗号
消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey(暗号)。
Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息
生产者
创建消息队列和交换机,之间进行绑定
@Configuration
public class RabbitmqConfig {
//创建 消息队列1
@Bean
public Queue directQueue1() {
return new Queue("directQueue1");
}
//创建 消息队列2
@Bean
public Queue directQueue2() {
return new Queue("directQueue2");
}
//创建 direct类型 交换机
public DirectExchange directExchange() {
return new DirectExchange("directExchange");
}
//通过 BindingBuilder 绑定消息队列1 到交换机上
//参数一:需要绑定的消息队列
//参数二:指定需绑定的交换机
//并在绑定的同时,执行routingKey(暗号)
@Bean
public Binding bindingDirectQueue1(Queue directQueue1, DirectExchange directExchange) {
return BindingBuilder.bind(directQueue1).to(directExchange).with("red");
}
//通过 BindingBuilder 绑定队列2 到交换机上
//参数一:需要绑定的消息队列
//参数二:指定需绑定的交换机
//并在绑定的同时,执行routingKey(暗号)
@Bean
public Binding bindingDirectQueue2(Queue directQueue2, DirectExchange directExchange) {
return BindingBuilder.bind(directQueue2).to(directExchange).with("black");
}
发送消息
@Test
public void directQueue(){
rabbitTemplate.convertAndSend("directExchange","red","发给红色方的消息");
}
消费者
@RabbitListener(queues = "directQueue1")
public void demo5(String msg){
System.out.println("\033[32m demo5获取到的消息 --->>> \" + " + msg + "\033[0m");
}
@RabbitListener(queues = "directQueue2")
public void demo6(String msg){
System.out.println("\033[31m demo6取到的消息 --->>> \" +" + msg + "\033[0m");
}
效果如下:
此时 Direct交换机根据RoutingKey(暗号)判断路由给哪个队列,具体哪个消费者来进行消费
3.3、Topic
Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割。
#:匹配一个或多个词
*:匹配不多不少恰好1个词
其余内容同Direct交换机一样,在交换机和队列绑定时,做出微调,.with(XXXX)不再是具体的routingKey,而是由通配符组成的rontingKey