消息队列需求场景
与服务之间的通信方式有两种:同步调用 和 异步消息调用
同步调用:远程过程调用,REST和RPC
异步消息调用:消息队列
消息队列概念
MQ全称为Message Queue,消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。
消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。
RabbitMQ 稳定可靠,数据一致,支持多协议,有消息确认,基于erlang语言
Kafka 高吞吐,高性能,快速持久化,无消息确认,无消息遗漏,可能会有有重复消息,依赖于zookeeper,成本高.
ActiveMQ 不够灵活轻巧,对队列较多情况支持不好.
RocketMQ 性能好,高吞吐,高可用性,支持大规模分布式,协议支持单一
搭建RabbitMQ
创建用户
创建虚拟主机
创建Maven项目
添加依赖
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.5.0</version>
</dependency>
</dependencies>
简单模式
入门工程-生产者
// 1.创建连接工厂(设置RabbitMQ的连接参数)
//2.创建连接
//3.创建频道
//4.声明队列
//5.发送消息
//6.关闭资源
package com.itheima.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//简单模式:发送消息
public class Producer {
static final String QUEUE_NAME = "simple";
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂(设置RabbitMQ的连接参数)
ConnectionFactory connectionFactory =new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("47.103.193.112");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/jiajia");
//连接用户名;默认为guest
connectionFactory.setUsername("jiajia");
//连接密码;默认为guest
connectionFactory.setPassword("123456");
//2.创建连接
Connection connection = connectionFactory.newConnection();
//3.创建频道
Channel channel = connection.createChannel();
//4.声明队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列(消息会持久保存在服务器)
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//5.发送消息
String message = "hello world";
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("已发消息:"+message);
//6.关闭资源
channel.close();
connection.close();
}
}
入门工程-消费者
//1.创建连接工厂
//2.创建连接
//3.创建频道
//4.声明(创建)队列
//5.创建消费者;并设置消息处理
//6.监听消息
package com.itheima.rabbitmq.simple;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
//1.创建连接工厂
//2.创建连接
Connection connection = ConnectionUtil.getConnection();
//3.创建频道
Channel channel = connection.createChannel();
//4.声明(创建)队列
channel.queueDeclare(Producer.QUEUE_NAME,true,false,false,null);
//5.创建消费者;并设置消息处理
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("接收到的消息为:" + new String(body, "utf-8"));
}
};
//6.监听消息
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.QUEUE_NAME,true,defaultConsumer);
//不关闭资源,应该一直监听消息
//channel.close();
//connection.close();
}
}
//ConnectionUtil.java
package com.itheima.rabbitmq.util;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionUtil {
public static Connection getConnection() throws IOException, TimeoutException {
// 1.创建连接工厂(设置RabbitMQ的连接参数)
ConnectionFactory connectionFactory =new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("47.103.193.112");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/jiajia");
//连接用户名;默认为guest
connectionFactory.setUsername("jiajia");
//连接密码;默认为guest
connectionFactory.setPassword("123456");
//2.创建连接
return connectionFactory.newConnection();
}
}
入门工程测试
运行Producer.java
运行Consumer.java
此时:
Work queues工作队列模式
在同一个队列中有多个消费者,竞争关系
生产者:发30个消息
消费者:两个消费者监听同一个消息队列,查看两个消费者接收消息是否重复。
//Producer.java
//5.发送消息
for (int i = 0; i <= 30 ; i++) {
String message = "hello world,work模式" + i;
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("已发消息: "+message);
}
Consumer1.java
Consumer2.java (同Consumer1.java)
结论:一个消息只能被一个消费者接收,其他消费者不能接收到同一条消息
应用场景:消费者处理任务比较耗时时,添加同一个队列的消费者来提高任务处理能力。
订阅模式
比前两种多了Exchange交换机,接收生产者发送的消息并决定如何投递消息到其绑定的队列。消息的投递取决于交换机的类型。
交换机类型:广播(fanout),定向(direct),通配符(topic)
交换机只做消息转发,自身不存储数据。
生产者(发送10个消息):
1.创建连接
2.创建频道
3.声明交换机
4.声明队列
5.队列绑定到交换机
6.发送消息
7.关闭资源
消费者(至少两个)
- 创建连接
- 创建频道
- 声明交换机
- 声明队列
- 队列绑定到交换机
- 创建消费者
- 监听队列
Consumer2.java
结论:一个消息可以被多个消费者接收;一个消费者对应的队列,该队列只能被一个消费者监听。使用了订阅模式中的交换机类型为:广播。
路由模式
生产者:发送两条消息(路由key分别为:insert,update)
消费者:创建两个消费者,监听的队列分别绑定路由key为:insert,update
1.消息中路由key为insert 的,会被绑定路由为insert的队列接收,并被其监听的消费者接收、处理
2.消息中路由key为update的,会被绑定路由为update的队列接收,并被其监听的消费者接收、处理
结论:Routing模式要求队列绑定到交换机的时候指定路由key;消费发送时候需要携带路由key;只有消息的路由key与队列的路由key完全一致,才能让该队列接收到消息。
通配符模式
绑定 rouer key时可以使用通配符
#:匹配1个或多个
*:匹配不多不少恰好一个
生产者:发送包含有 item.insert 、item.update 、 item.delete的3种路由key消息
消费者1:监听的队列绑定的交换机类型为 item.insert 、item.delete
消费者1:监听的队列绑定的交换机类型为 item.*
结论:可以根据路由key将信息传递到对应的路由key的队列;队列绑定到交换机的路由key可以有多个;通配符模式中路由key可以使用*
,#
;使用了通配符模式后对应路由key配置更灵活。
RabbitMQ模式总结
●不直接Exchange交换机(默认交换机)
1.simple简单模式:一个生产者生产一个消息,到一个队列,被一个消费者接收
2.work工作队列模式:生产者发送消息到一个队列中,然后可以被多个消费者监听该队列; -一个消息只能被一个消费者接收,消费者之间是竞争关系
●使用Exchange交换机;订阅模式(交换机:广播fanout、 定向direct、 通配符topic )
1.发布与订阅模式:使用了fanout广播类型的交换机,可以将一个消息发送到所有绑定了该交换机的队列
2. 路由模式:使用了direct定向类型的交换机,消费会携带路由key ,交换机根据消息的路由key与队列的路由key进行对比,一致的话那么该队列可以接收到消息
3.通配符模式:使用了topic通配符类型的交换机,消费会携带路由key(*, #) , 交换机根据消息的路由key与队列的路由key进行对比,匹配的话那么该队列可以接收到消息
整合springboot
springboot提供了AMQP的整合;可以使用RabbitTemplate发送消息,使用**@RabbitListener**接收消息。
生产者工程:spring-boot-rabbitmq-producer:发送消息
消费者工程:spring-boot-rabbitmq-customer:接收消息
file new moudle maven
<!-- pom.xml(producer)-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<!-- pom.xml(customer)-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
启动引导类,配置文件yml
配置生产者工程
使用通配符模式
1.配置RabbitMQ的连接参数:主机、连接端口、虚拟主机、用户名、密码
2.声明交换机、队列并将队列绑定到交换机,指定的路由key
spring:
rabbitmq:
host: 47.103.193.112
post: 5672
virtual-host: /jiajia
username: jiajia
password: 123456
@Configuration
public class RabbitMQConfig {
//声明交换机
public static final String ITEM_TOPIC_EXCHANGE = "item_topic_exchange";
//声明队列
public static final String ITEM_QUEUE = "item_queue";
//声明交换机
@Bean("itemTopicExchange")
public Exchange topicExchange() {
return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build();
}
//声明队列
@Bean("itemQueue")
public Queue itemQueue() {
return QueueBuilder.durable(ITEM_QUEUE).build();
}
//将队列绑定到交换机
@Bean
public Binding itemQueueExchange(@Qualifier("itemQueue") Queue queue,
@Qualifier("itemTopicExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs();
}
}
配置消费者工程
1.配置applixation.yml文件,设置RibbitMQ连接参数
2.编写消息监听器监听接收队列(item_queue)消息;使用注解 @RabbitListener接收队列消息
spring:
rabbitmq:
host: 47.103.193.112
post: 5672
virtual-host: /jiajia
username: jiajia
password: 123456
@Component
public class MyListener {
@RabbitListener(queues = "item_queue")
public void myListener1(String message) {
System.out.println("消费者接收到消息:" + message);
}
}
测试消息发送接收
生产者:编写测试类RabbitMQTest,利用RabbitTemplate发送3条消息,路由key 分别为 item.insert 、item.update 、 item.delete
消费者:查看能否收到数据
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitMQTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test() {
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE,
"item.insert", "商品新增,路由key为item.insert");
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE,
"item.update", "商品修改,路由key为item.update");
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE,
"item.delete", "商品删除,路由key为item.delete");
}
}
先启动测试类,再启动consumerApplication
测试成功