一、初见MQ
(一)什么是MQ?
MQ(MessageQueue),意思是消息队列,也就是事件驱动架构中的Broker。
(二)同步调用
1、概念: 同步调用是指,某一服务需要多个服务共同参与,但多个服务之间有一定的执行顺序,当每一个服务都需要等待前面一个服务完成才能继续执行。
2、存在的问题
(三)异步调用
1、实现模式: 异步调用常见实现的就是事件驱动模式。
2、事件驱动的优势
3、事件驱动的缺点
(四)MQ常见框架
RabbitMQ(中小企业) | ActiveMQ | RocketMQ(大型企业) | Kafka | |
---|---|---|---|---|
公司/社区 | Rabbit | Apache | Alibaba | Apache |
开发语言 | Erlang | Java | Java | Java |
协议支持 | AMQP,XMPP,SMTP,STOMP | OpenWire,STOMP,REST,XMPP,AMQP | 自定义协议 | 自定义协议 |
可用性 | 高 | 一般 | 高 | 高 |
单机吞吐量 | 一般 | 差 | 高 | 极高 |
消息延迟 | 微妙级 | 毫秒级 | 毫秒级 | 毫秒以内 |
消息可靠 | 高 | 一般 | 高 | 一般 |
二、使用MQ
(一)RabbitMQ概述
RqbbitMQ是基于Erlang语言开发的开源消息通讯中间件,官方地址:https://rabbitmq.com/
(二)安装MQ
docker pull rabbitmq:3-management
(三)运行RabbitMQ
#配置 MQ的用户名和密码,容器名和主机名,端口,镜像名 ,注意:15672端口是MQ的控制台访问端口,5672是对外暴露的消息通信端口
docker run -e RABBITMQ_DEFAULT_USER=xxx -e RABBITMQ_DEFAULT_PASS=xxxx --name mq --hostname mq1 -p 15672:15672 -p 5672:5672 -d rabbitmq:3-management
访问MQ的控制台
(5)RabbitMQ中的几个概念
(6)常见的MQ模型
第一种:基本消息队列的基本使用
包含三种角色:publisher、queue、consumer
收发消息的过程: 获取连接 》 建立通信通道 》 创建消息队列 》 收发消息 》 释放资源
1、publisher和consumer引入依赖
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
dependency>
2、Publisher创建发送消息通道
@SpringBootTest
class PublisherApplicationTests {
@Test
void testSendMessage() throws IOException, TimeoutException {
// 1、建立连接
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2、设置连接参数
connectionFactory.setHost("192.168.92.131");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("root");
connectionFactory.setPassword("root");
// 3、建立连接
Connection connection = connectionFactory.newConnection();
// 4、建立通信通道Channel
Channel channel = connection.createChannel();
// 5、创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName,false,false,false,null);
// 6、发送信息
String message = "hello,rabbitmq!";
channel.basicPublish("",queueName,null,message.getBytes(StandardCharsets.UTF_8));
System.out.println("发送消息成功:【"+message+"】");
// 7、关闭通道和连接
channel.close();
connection.close();
}
}
2、Consumer创建订阅通道
class ConsumerApplicationTests {
public static void main(String[] args) throws IOException, TimeoutException {
// 1、建立连接
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2、设置连接参数
connectionFactory.setHost("192.168.92.131");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("root");
connectionFactory.setPassword("root");
// 3、建立连接
Connection connection = connectionFactory.newConnection();
// 4、建立通信通道Channel
Channel channel = connection.createChannel();
// 5、创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName,false,false,false,null);
// 6、订阅消息
channel.basicConsume(queueName,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 7、处理消息
String message = new String(body);
System.out.println("接收到消息:【"+message+"】");
}
});
System.out.println("等待接收消息....");
}
}
第二种:Work Queue 工作队列
与基本队列的区别在于,它能使用多个订阅队列进行高效的处理请求。(因为一个订阅队列的处理速度是有限的)
使用过程与基本队列几乎一致,只是开启了多个订阅队列。
在使用过程中我们会发现,多个订阅队列对任务的分配是平均的,这就是预取机制。
我们需要的是快速处理的订阅队列获取更多的请求,慢速处理的订阅队列获取少量的请求,它如何实现呢?
通过修改配置文件,设置一个 preFetch 值。
spring:
rabbitmq:
host: 192.168.92.131 #IP
port: 5672 #端口
virtual-host: / #虚拟主机
username: root #用户名
password: root #密码
listener:
simple:
prefetch: 1 # 每次取 1 个请求,处理完才能取下一个。
第三种:FanoutQueue 广播消息队列
SpringAMQP提供声明交换机、队列、绑定关系的API
主要使用的是Exchange.FanoutExchange类。
实现思路:
1、在consumer服务,声明队列,交换机,并将两者绑定。
@Configuration
public class FanoutConfig{
//交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("com.fanout");
}
//队列
@Bean
public Queue fanoutQueue1(){
return new Queue("com.queue1");
}
//绑定关系
@Bean
public Binding bindingQueue(Queue fanoutQueue1,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue).to(fanoutExchange);
}
//...以相同方式声明第2个队列,并完成绑定
}
2、在consumer服务,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "com.queue1")
public void listenFanoutQueue1(String msg) throws InterruptedException {
//...处理结果
}
@RabbitListener(queues = "com.queue2")
public void listenFanoutQueue2(String msg) throws InterruptedException {
//...处理结果
}
}
3、在publisher编写测试方法,向交换机发送信息
@Test
public void sendFanoutExchange() {
//1、交换机
String exchangeName = "com.fanout";
//2、消息
String message = "Hello Fanout";
//3、发送消息
rabbitTemplate.covertAndSend(exchangeName, "", message);
}
第四种:路由信息队列
路由模式的流程: 即设置密钥的绑定关系,只有携带相应的密钥才能进入相应的队列
实现思路:
1、利用 @RabbitListener 声明Exchange、Queue、RoutingKey
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue1"), exchange = @Exchange(name = "com.exchange", type = ExcahngeTypes.DIRECT), key = {"red","blue"}))
public void listenRoutingQueue1(String msg) throws InterruptedException {
//...处理结果
}
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue2"), exchange = @Exchange(name = "com.exchange", type = ExcahngeTypes.DIRECT), key = {"red","green"}))
public void listenRoutingQueue2(String msg) throws InterruptedException {
//...处理结果
2、发送消息实现
//指定队列处理
@Test
public void sendRoutingExchange1(){
//交换机,消息
String exchangeName = "com.exchange";
String message = "Hello,RoutingMQ";
//发送消息
rabbitTemplate.covertAndSend(exchangeName, "blue", message);
}
//多队列处理
@Test
public void sendRoutingExchange2(){
//交换机,消息
String exchangeName = "com.exchange";
String message = "Hello,RoutingMQ";
//发送消息
rabbitTemplate.covertAndSend(exchangeName, "red", message);
}
第五种:主题信息队列(通配key)
TopicExchange 与 DirectExchange 的区别: routingkey必须是多个单词的列表,并且以,
分割。并且Queue与Exchange指定的BindingKey时可使用通配符:
实现思路:
1、通过 @RabbitListener 声明Exchange、Queue、RoutingKey
@RabbitListener(bingdings = @QueueBinding(exchange = @Exchange(name = "com.exchange", type = ExchangeTypes.TOPIC), queue = @Queue(name = "com.queue1"), key = {"china.#"}))
public void listenTopicQueue1(String msg) {
//处理代码....
}
@RabbitListener(bingdings = @QueueBinding(exchange = @Exchange(name = "com.exchange", type = ExchangeTypes.TOPIC), queue = @Queue(name = "com.queue2"), key = {"#.news"}))
public void listenTopicQueue2(String msg) {
//处理代码....
}
2、在publisher服务中,向交换机发送消息
@Test
public void sendTopicMessage(){
//交换机,消息
String exchangeName = "com.exchange";
String message = "Hello,Topic";
rabbitTemplate.convertAndSend(exchangeName,"china.call",message);
}
四、SpringAMQP
(一)概念
(二)实现基础消息队列
1、引入spring-amqp依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
2、publisher服务中利用RabbitTemplate发送消息到任务队列
spring:
rabbitmq:
host: 192.168.92.131 #IP
port: 5672
virtual-host: /
username: root
password: root
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void sendMessage(){
String queueName = "simple.queue";
String message = "Hello World";
rabbitTemplate.convertAndSend(queueName,message);
}
3、在consumer服务中编写消费逻辑,绑定simple.queue队列
spring:
rabbitmq:
host: 192.168.92.131 #IP
port: 5672
virtual-host: /
username: root
password: root
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void getMessage(){
String queueName = "simple.queue";
// receive 表示接收方法,接收到的信息会封装到Message,可以看receive的返回值
Message message = rabbitTemplate.receive(queueName);
// Message.getBody 是 byte[]
System.out.println(new String(message.getBody()));
}
// 注册成 Bean 对象
@Component
public class SpringRabbitListener {
// 监听器注释,queues = 订阅队列,并将返回值注入参数列表中
@RabbitListener(queues = "simple.queue")
public void ListenSimpleQueueMessage(String msg){
System.out.println("Spring 消费者接收到消息:【" + msg + "】");
}
}
(三)消息转换器
为了让我们能够自由识别consumer发送的消息,则需要使用的是消息转换器。
消息转换器如何使用?
Spring对消息对象的处理是由org.springframework.amqp.support.converter.MessageConverter来处理,默认实现的是SimpleMessageConverter,基于ObjectObjectOutputStream完成序列化。
我们只需要定义一个 MessageConverter 类型的Bean即可,推荐使用JSON序列化
1、publisher引入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformatgroupId>
<artifactId>jackson-dataformat-xmlartifactId>
<version>2.9.10version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
dependency>
2、publisher启动类,声明MessageConverter
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
3、consumer启动类,声明MessageConverter
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
4、监听队列消息
@RabbitListener(queues = "object.queue")
public void listenObjectMessage(Object msg) {
//处理数据....
}