一、MQ 的相关概念
1.1、 什么是MQ
MQ(Message Queue)是一种消息代理(接收和转发消息),本质是个队列,遵循 FIFO 先进先出原则,只不过队列中存放的内容是 Message 而已。
1.2、MQ的作用
1.2.1、异步处理
应用场景:用户注册。
-
同步调用:用户注册成功后,将用户注册信息写入数据库( 耗时50ms ),然后调用发邮件方法给用户发一封邮件( 耗时50ms ),接着再给用户发一条短信( 耗时50ms )。所以在150ms后响应用户,注册成功了。
-
多线程处理:用户注册成功后,先将用户注册信息写入数据库( 耗时50ms ),然后通过多线程并发执行发邮件和发短信操作( 耗时50ms )。所以在100ms后响应用户,注册成功了。
-
消息队列异步处理:用户注册成功后,先将用户注册信息写入数据库( 耗时50ms ),然后将注册成功的信息写入消息队列中( 这个过程耗时很短 ),写入消息队列之后就可以立马响应用户,注册成功了。而发邮件和发短信操作,可以通过异步读取的方式从消息队列中取出注册成功的信息,给用户发邮件和发短信。(因为用户只需要知道注册成功了就行,并不需要很快就受到邮件和短信)。
1.2.2、应用解耦
解析:使用消息队列来作为两个系统的通讯媒介进行数据传递,使得两个系统不再相互依赖,解耦两个应用。
- 将订单系统 和 库存系统都单独抽取出来 ( 可以通过微服务的方式来抽取一个服务 ),订单系统将下单信息写入消息队列,库存系统通过订阅消息队列获取订单信息,进行库存计算相关操作。
1.2.3、流量削峰
应用场景:1000万用户秒杀1万件商品
-
如果1000万用户发送请求,每个请求都要去判断库存,这样不仅麻烦,而且可能会卡死。
-
用户的秒杀请求直接进入队列,对消息队列设置定长,只能存储一万个数据( message )。先到先进队,队列满了之后,后面999万个用户的请求进不了队列,请求被抛弃,响应秒杀失败。
二、安装RabbitMQ
2.1、安装RabbitMQ
docker拉取rabbitmq镜像,拉取带management标签的镜像,带management标签的rabbitmq是带web管理界面的,方便我们操作。
docker pull rabbitmq:3.9-management
可以使用docker中国官方镜像加速加速地址拉取:registry.docker-cn.com/library
例如:docker pull registry.docker-cn.com/library/rabbitmq:3.9-management
如果使用镜像加速地址后,报:Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)错误
则vi /etc/resolv.conf编辑文件
添加下面两行
nameserver 8.8.8.8
nameserver 8.8.4.4
重启
2.2、运行RabbitMQ
docker run -d -p 5672:5672 -p 15672:15672 --name cd-rabbitmq b47e1fb19937
run 运行
-d 在后台运行
-p 端口映射(将服务器的端口映射到docker中,否则外面只能访问到centos,而访问不到docker
带management管理界面的rabbitmq有两个端口,一个是客户端与rabbitmq通信的端口5672,一个是管理界面访问web页面的端口15672
--name 给运行的容器起个名字
b47e1fb19937 要启动镜像的id
虚拟机关闭后,RabbitMQ容器也会关闭,(使用docker ps查看不到了,但可以用docker ps -a可以看到关闭了的容器)
使用docker start 容器ID 或 docker start 容器名 来重新启动已经关闭的容器
2.3、使用 youip:15672 访问 RabbitMQ 的web页面
三、RabbitMQ相关名词解释
3.1、Message
消息,由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括router-key ( 路由键 )、priority ( 相对其它消息的优先级 )、delivery-mode ( 指出该消息可能需要持久化存储)等。
3.2、Publisher
消息的生产者,可以理解为一个向客户端发布消息的客户端程序。
3.3、Exchange
交换器,用来接收生产者的发送的消息,并将这些消息路由给服务器中的队列。
Exchange有4种类型,direct ( 默认 ),fanout、topic 和 headers,不同类型的的Exchange转发消息的策略有所区别。
3.4、Queue
消息队列,用来保存消息知道发送给消费者。它是消息的容器,也是消息的终点。一个消息可以投入一个或多个消息队列。消息一直在队列里,等待消费者连接到这个队列将其取走。
3.5、Binding
绑定,用于消息队列和交换器指尖的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个绑定构成的路由表。
3.6、Connection
网络连接,比如一个tcp连接
3.7、Channel
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的tcp连接内的虚拟连接,AMQP命令都是通过信道发出去的,不管是发布消息,订阅队列还是接受消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁tcp都是非常昂贵的开销,所以引入了信道的概念,一服用一条tcp连接。
3.8、Consumer
消息的消费者,表示一个从消息队列中取得消息的客户端程序。
3.9、Virtual Host
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个vhost本质是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。vhost是AMQP开年的基础,必须在连接时指定,RabbitMQ默认的vhost是/。
3.10、Broker
表示消息队列服务器实体。
四、RabbitMQ提供的消息模型
4.1、direct exchange
直连交换器 (默认),根据router key路由键完全匹配进行路由消息队列。
4.2、fanout exchange
主题交换器,模糊匹配
- #号匹配一个或多个单词,*号匹配一个单词,用 . 隔开的为一个单词
- beijing.# == beijing.queue.abc,beijing.queue.abc.xyz
- beijing.* == beijing.abc,beijing.xyz,beijing.haha
4.3、topic exchange
扇形交换器,不处理router key路由键 ( 不匹配路由键 ),将消息投递到所有绑定的队列。
4.4、headers exchange
报头交换器,基于消息体中的headers属性内容进行匹配。
五、SpringBoot整合RabbitMQ
5.1、SpringBoot自动配置
-
RabbitAutoConfiguration
-
spring-boot-starter-amqp提供了对amqp的支持
-
需要ConnectionFactory的实现来连接消息代理
-
提供RabbitTemplate来发送消息
-
@EnableRabbit开启支持
-
@RabbitListenner(AMQP)注解在方法上监听消息代理发送的消息
5.2、RabbitMQ中Exchange对象的一些基本属性
- Name:交换器名称
- Type:交换器类型 ( direct、topic、fanout、headers )
- Durability:是否需要持久化,true表示需要持久化 ( 持久化到硬盘,可以防止RabbitMQ宕机造成消息丢失 )
- Auto Delete:当最后一个绑定到Exchange交换器上的queue队列被删除后,是否自动删除改Exchange交换器,true表示自动删除
- Internal:当前Exchange是否用于RabbitMQ内部使用,默认为false。
- Arguments:扩展参数
5.3、Direct 单播模式测试
1)application.properties
#[ RabbitMQ配置 ]
#安装了rabbitmq的服务器IP
spring.rabbitmq.host=192.168.184.129
#rabbitmq服务器端口( 默认为5672 )
spring.rabbitmq.port=5672
#用户名( 默认用户名为guest )
spring.rabbitmq.username=guest
#用户密码( 默认密码为guest )
spring.rabbitmq.password=guest
#vhost虚拟主机地址( 默认为/ )
spring.rabbitmq.virtual-host=/
2)通过实体类封装消息
import lombok.*;
@Setter //setter方法
@Getter //getter方法
@ToString //toString方法
@AllArgsConstructor //全参构造
@NoArgsConstructor //无参构造
public class User {
private Integer userId;
private String username;
private String password;
}
3)使用 Direct 交换器,必须指定具体的 router key 路由键,不能使用模糊匹配
@SpringBootTest
public class RabbitMQTest {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息后,Queue消息队列中相应的会增加一条消息
*/
@Test
public void sendMsg(){
//对象被通过java默认序列化后发送出去
rabbitTemplate.convertAndSend("exchange.direct", "atguigu.new", new User(1001, "张三", "123456"));
}
/**
* 接收消息后,Queue消息队列中相应的会减少一条消息
*/
@Test
public void receiveMsg(){
//将接收到的数据反序列化
Object o = rabbitTemplate.receiveAndConvert("atguigu.new");
System.out.println(o);
}
}
4)由于RabbitMQ默认使用java默认的序列化工具进行序列化 (序列化成字节码格式),不方便查看。所以可以通过配置类配置成以json格式进行发送/接收,而不是使用jdk默认的序列化格式
@Configuration
public class RabbitMQConfig {
/**
* 配置RabbitMQ的消息以json格式进行发送/接收,而不是使用jdk默认的序列化格式
* @return
*/
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
5.4、Fanout 广播模式测试
1)Fanout模式不用设置路由键 ( 不匹配路由键 ),它会将消息投递到所有绑定的队列。
@SpringBootTest
public class RabbitMQTest {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息后,所有Queue消息队列中相应的会增加一条消息
*/
@Test
public void sendMsg(){
rabbitTemplate.convertAndSend("exchange.fanout", "", new User(1001, "张三", "123456"));
}
}
2)fanout交换器绑定的所有队列都增加了一条消息
5.5、@EnableRabbit + @RabbitListener:监听消息队列内容
1)在启动类中添加@EnableRabbit注解 ( 用来开启基于注解的RabbitMQ模式 )
/**
* 自动配置:
* 1、RabbitAutoConfiguration
* 2、自动配置了连接工厂ConnectionFactory
* 3、RabbitProperties封装了RabbitMQ的配置
* 4、RabbitTemplate:给RabbitMQ发送和接收消息
* 5、AmqpAdmin:RabbitMQ系统管理功能组件, 比如用代码创建交换器、创建队列、创建路由键,都是通过AmqpAdmin来完成
* 6、@EnableRabbit + @RabbitListener:监听消息队列内容
*/
@EnableRabbit
@SpringBootApplication
public class SpringbootRabbitmqApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRabbitmqApplication.class, args);
System.out.println("项目启动成功!");
}
}
2)使用@RabbitListener注解监听消息队列的消息 ( 只要消息队列中有消息,就能自动接收,直到队列中没有为止 )
@Service
public class UserService {
@RabbitListener(queues = {"atguigu"})
public void receive01(User user){
System.out.println("atguigu队列中的消息:" + user);
}
@RabbitListener(queues = {"atguigu.new"})
public void receive02(Message msg){
System.out.println("atguigu.new队列中消息的->消息头:" + msg.getHeaders());
System.out.println("atguigu.new队列中消息的->消息体:" + msg.getPayload());
}
@RabbitListener(queues = {"atguigu.emps"})
public void receive03(User user){
System.out.println("atguigu.emps队列中的消息:" + user);
}
}
5.6、RabbitMQ系统管理功能组件:AmqpAdmin
1)使用RabbitMQ系统管理功能组件:AmqpAdmin来创建交换器、队列、绑定规则
@SpringBootTest
public class RabbitMQTest {
@Autowired
AmqpAdmin amqpAdmin;
/**
* 使用AmqpAdmin系统管理组件创建exchange交换器
*/
@Test
public void createExchange(){
/**
* 1.交换器名称
* 2.是否持久化
* 3.当最后一个绑定在Exchange交换器上的queue队列被删除后,是否自动删除改Exchange交换器
*/
amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange", true, false));
}
/**
* 使用AmqpAdmin系统管理组件创建queue队列
*/
@Test
public void createQueue(){
/**
* 1.队列名称
* 2.是否持久化
*/
amqpAdmin.declareQueue(new Queue("amqpadmin.queue", true));
}
/**
* 使用AmqpAdmin系统管理组件创建Binding绑定规则
*/
@Test
public void createBinding(){
/**
* 1.绑定的目的地( 可以使Queue 或 Exchange的名称 )
* 2.绑定的目的地的类型( 可以使Queue 或 Exchange )
* 3.交换机名称
* 4.路由键
* 5.参数信息(Map类型)
*/
amqpAdmin.declareBinding(new Binding("amqpadmin.queue",
Binding.DestinationType.QUEUE, "amqpadmin.exchange",
"amqp.news", null));
}
/**
* 使用AmqpAdmin系统管理组件删除Exchange交换器
*/
@Test
public void deleteExchange(){
amqpAdmin.deleteExchange("amqpadmin.queue");
}
/**
* 使用AmqpAdmin系统管理组件删除Queue队列
*/
@Test
public void deleteQueue(){
amqpAdmin.deleteQueue("amqpadmin.exchange");
}
}