个人印象笔记地址:https://app.yinxiang.com/fx/c19556bc-e80f-4f4c-bf1f-04595af67180
项目github地址:https://github.com/ztb0312/southwind.git
SpringBoot整合RabbitMQ
搭建初始环境
1. 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.设置yml文件
spring:
application:
name: rabbitmq-springboot
rabbitmq:
host: 192.168.64.130
port: 5672
username: ems
password: 123456
virtual-host: /ems
消息模板-RabbitTemplate
新建测试类:
package com.ztb.test;
import com.ztb.RabbitmqSpringbootApplication;
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;
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
//注入rabbitTemplet工具
@Autowired
private RabbitTemplate rabbitTemplate;
//写一个helloworld
@Test
public void test(){
rabbitTemplate.convertAndSend("hello","hello word");
}
}
新建消费者类:
package com.ztb.entity;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queuesToDeclare = @Queue("hello"))
//启动队列监听 ,queuesToDeclare 声明队列
public class HelloConsumer {
@RabbitHandler
public void recievel(String message){//随便指定一个方法
System.out.println("message======="+message);
}
}
测试结果:
第二种模型 worke模型(任务模型 多个消费者共同绑定到同一个队列,消费消息)
新建消费者类:
package com.ztb.work;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListeners;
import org.springframework.stereotype.Component;
@Component
public class WorkCustomer {
//消费者1
@RabbitListener(queuesToDeclare = @Queue("work"))
public void receive1(String message){
System.out.println("message1"+message);
}
//消费者2
@RabbitListener(queuesToDeclare = @Queue("work"))
public void receive2(String message){
System.out.println("message2"+message);
}
}
新建test测试类
//work模型
@Test
public void testWork(){
for (int i=0;i<10;i++) {
rabbitTemplate.convertAndSend("work", "work 模型");
}
}
测试结果
第三种:fanout(广播模型)
新建消费者fanoutcustomer类
package com.ztb.fanout;
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;
@Component
public class FanOutCustomer {
@RabbitListener(bindings = {
@QueueBinding(
value=@Queue,//创建临时队列
exchange=@Exchange(value = "logs",type = "fanout")//绑定交换机
)
})
public void recive1(String message){
System.out.println("message1 = "+message);
}
@RabbitListener(bindings = {
@QueueBinding(
value=@Queue,//创建临时队列
exchange=@Exchange(value = "logs",type = "fanout")//绑定交换机
)
})
public void recive2(String message){
System.out.println("message2 = "+message);
}
}
新建测试类
//fanout 广播模型
@Test
public void testFanOut(){
rabbitTemplate.convertAndSend("logs","", "fanout 模型");
}
测试结果:
第四种模型:routing 路由模式
Routing 之订阅模型-Direct(直连)
在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
在Direct模型下:
-
队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
-
消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey。
-
Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息
新建路由消费者:
package com.ztb.routing;
import ch.qos.logback.core.boolex.EvaluationException;
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;
@Component
public class DirectsCustomer {
//消费者1
@RabbitListener(bindings = {
@QueueBinding(
value= @Queue,//创建临时队
exchange = @Exchange(value = "directs",type = "direct"),//自定义交换机类型和名称
key = {"info","error","warn"}
)
})
public void receive1(String message){
System.out.println("message1 ="+message);
}
//消费者2
@RabbitListener(bindings = {
@QueueBinding(
value= @Queue,//创建临时队列
exchange = @Exchange(value = "directs",type = "direct"),//自定义交换机类型和名称
key = {"error"}
)
})
public void receive2(String message){
System.out.println("message2 ="+message);
}
}
新建测试类
//routing的模型
@Test
public void testRouting(){
rabbitTemplate.convertAndSend("directs","info","发送info类型的路由的类的信息");
}
测试结果:
第五种Routing 之订阅模型-Topic
新建topic类型的消费者:
package com.ztb.topic;
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;
@Component
public class TopicCustomer {
//消费者1
@RabbitListener(bindings = {
@QueueBinding(
value= @Queue,//创建临时队
exchange = @Exchange(name = "topics",type = "topic"),//自定义交换机类型和名称
key = {"service.#","product.#","user.*"}
)
})
public void receive1(String message){
System.out.println("message1 ="+message);
}
//消费者2
@RabbitListener(bindings = {
@QueueBinding(
value= @Queue,//创建临时队列
exchange = @Exchange(name= "topics",type = "topic"),//自定义交换机类型和名称
key = {"user.save","user.*"}
)
})
public void receive2(String message){
System.out.println("message2 ="+message);
}
}
//topic 动态路由订阅模型
@Test
public void testTopic(){
rabbitTemplate.convertAndSend("topics","user.save","user.save 类型的路由的类的信息");
}
MQ的应用场景
1,异步处理
-
串行方式: 将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。 这有一个问题是,邮件,短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西.
-
并行方式:将注册信息写入数据库后,发送邮件的同时,发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间。
-
消息队列:假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。虽然并行已经提高的处理时间,但是,前面说过,邮件和短信对我正常的使用网站没有任何影响,客户端没有必要等着其发送完成才显示注册成功,应该是写入数据库后就返回. 消息队列: 引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理
由此可以看出,引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列后处理后,响应时间是串行的3倍,是并行的2倍。
2,应用解耦


-
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
-
库存系统:订阅下单的消息,获取下单消息,进行库操作。 就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失.
3, 流量削峰
场景: 秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。
作用:
1.可以控制活动人数,超过此一定阀值的订单直接丢弃(我为什么秒杀一次都没有成功过呢^^)
2.可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)
1.用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面.
2.秒杀业务根据消息队列中的请求信息,再做后续处理.
RabbitMQ的集群
1 集群架构
1.1 普通集群(副本集群):只能同步复制exchange ,能同步队列
-
架构图
核心问题:解决了当集群中某一时刻master节点宕机,可以对Quene中信息,进行备份 (主备模式,slave节点无法在主节点宕机之后经行故障转移)
1.2 镜像集群
-
集群架构图