基础RabbitMQ消息队列(消息中间件)
MQ基本概念
- MQ为消息队列,存储消息的中间件
- 分布式系统通信的两种方式:(1)直接远程调用。(2)借助第三方 完成通信
- 发送方-----消息中间件---->接收方
MQ的优势
1. 应用解耦
在新增额外系统、删除系统、子系统发生错误,都不会影响订单系统,只需要对MQ发送消息即可实现
2. 异步提速
在订单系统和数据库系统之间添加消息MQ,订单系统从MQ中获取数据,子系统将数据可以提前提交至MQ中。
3. 削峰填谷
- 瞬时消息请求在增多,承受不住请求的压力
- 引入消息队列MQ,将请求挂靠在MQ中,让MQ承担请求的压力
总结
- 应用解耦:提高了系统的容错性和可维护性
- 异步提速:提升了系统的吞吐量和用户体验度
- 削峰填谷:提升了系统的稳定性
MQ的劣势
1. 系统可用性降低
添加了MQ之后,增加了系统的组件,增加了系统出错的概率:MQ宕机了,系统也会受影响
2. 系统复杂性提高
MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
3. 一致性问题
A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理
失败。如何保证消息数据处理的一致性?
总结
- 生产者不需要从消费者处获取反馈,则可以用MQ
- 解耦、提速、削峰的收益,超过维护MQ的成本,可以使用。
常见MQ产品
目前业界有很多的 MQ 产品,例如 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,也有直接使用 Redis 充当消息队列的案例,而这些消息队列产品,各有侧重,在实际选型时,需要结合自身需求及 MQ 产品特征,综合考虑。
RabbitMQ简介
- RabbitMQ是基于AMQP协议使用的Erlang语言开发的消息队列产品。
- AMQP(高级消息队列协议)类比HTTP。
Connection:生产者/消费者与broker之间的TCP连接;
broker:接收和分发消息的中介,就是消息服务器;
Virtual host:每一个用户有一个host,类似私人账户一样;
channel:每个channel之间相互隔离,可以减少TCP connection的开销;
exchange:根据分发规则,将message分发到不同的queue中;
Queue:装在message,被消费者取走;
Binding:exchange和queue之间的虚拟连接。
RabbitMQ六种工作模式
1. 简单模式 “Hello World”
RabbitMQ 是一个消息代理: 它接受和转发消息。你可以把它想象成一个邮局: 当你把你想要投递的邮件放在一个邮箱里时,你可以确定信件的承运人最终会把邮件投递给你的收件人。在这个类比中,RabbitMQ 是一个邮箱、一个邮局和一个信件载体。
P是一个生产者,红色部分是一个消息缓存队列,C是消费者
Producer
public class Producer{
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
//1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("localhost"); //设置主机ip
//3. 创建连接
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建消息队列
/*
参数1: 队列名
参数2:durable,是否持久化
参数3:exclusive,是否独占,只能有一个消费者监听队列,一般为false
参数4:autodelete,是否自动删除队列,没有消费者时自动删除
参数5:参数
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello World!";
//6. 发送消息
/*
参数1: exchange,交换机,默认为“”
参数2:routingkey,路由名,简单模式下,交换机与路由名相互绑定,为队列名称QUEUE_NAME
参数3:props,配置信息
参数4:body,message 消息内容,字节信息
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
//7. 关闭资源,可以选择性关闭
channel.close();
connection.close();
}
}
consumer
public class Consumer{
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//接收消息
/*
回调方法 ,当收到消息之后,会自动执行
参数:
参数1:consumerTag,消息标识
参数2:envelope,交换机,routingkey名
参数3:properties,配置信息
参数4:message,消息数据
*/
Consumer deliverCallbackConsumer = new DefaultConsumer(channel){
@override
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] message){
System.out.println(“consumerTag:”+consumerTag);
System.out.println(“exchange:”+envelope.getExchange());
System.out.println(“rountingkey:”+envelope.getrountingkey());
System.out.println(“properties:”+properties);
System.out.println(“message:”+new String(message));
}
}
/*
参数1: routingkey,路由名,简单模式下,交换机与路由名相互绑定,为队列名称QUEUE_NAME
参数2:autoAck,自动确认
参数3:callback,回调对象,deliverCallback
参数4:body,message 消息内容,字节信息
*/
channel.basicConsume(QUEUE_NAME, true, deliverCallbackConsumer);
//消费者不能关闭资源,因为需要不间断的监听消息队列
}
}
2. work queues模式
工作队列(又名: 任务队列)背后的主要思想是避免立即执行资源密集型任务,并且不得不等待它完成。相反,我们将任务安排在以后完成。我们将任务封装为消息并将其发送到队列。在后台运行的辅助进程将弹出任务并最终执行作业。当您运行许多工作线程时,任务将在它们之间共享。
- C1、C2是相互竞争资源的关系
- 应用场景:适用任务过重过多情况,提升任务处理的速度
- 同时启动多个consumer,然后启动producer,发送message,观察消息消费情况
Producer
public class Producer{
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
//1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("localhost"); //设置主机ip
//3. 创建连接
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建消息队列
/*
参数1: 队列名
参数2:durable,是否持久化
参数3:exclusive,是否独占,只能有一个消费者监听队列,一般为false
参数4:autodelete,是否自动删除队列,没有消费者时自动删除
参数5:参数
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//6. 发送10条消息
for( int i= 0 ; i< 10; i++){
String message = i + "Hello World!";
/*
参数1: exchange,交换机,默认为“”
参数2:routingkey,路由名,简单模式下,交换机与路由名相互绑定,为队列名称QUEUE_NAME
参数3:props,配置信息
参数4:body,message 消息内容,字节信息
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
}
System.out.println(" [x] Sent '" + message + "'");
//7. 关闭资源,可以选择性关闭
//channel.close();
//connection.close();
}
}
consumer1
public class Consumer1{
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//接收消息
/*
回调方法 ,当收到消息之后,会自动执行
参数:
参数1:consumerTag,消息标识
参数2:envelope,交换机,routingkey名
参数3:properties,配置信息
参数4:message,消息数据
*/
Consumer deliverCallbackConsumer = new DefaultConsumer(channel){
@override
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] message){
System.out.println(“message:”+new String(message));
}
}
/*
参数1: routingkey,路由名,简单模式下,交换机与路由名相互绑定,为队列名称QUEUE_NAME
参数2:autoAck,自动确认
参数3:callback,回调对象,deliverCallback
参数4:body,message 消息内容,字节信息
*/
channel.basicConsume(QUEUE_NAME, true, deliverCallbackConsumer);
//消费者不能关闭资源,因为需要不间断的监听消息队列
}
}
consumer2
public class Consumer2{
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//接收消息
/*
回调方法 ,当收到消息之后,会自动执行
参数:
参数1:consumerTag,消息标识
参数2:envelope,交换机,routingkey名
参数3:properties,配置信息
参数4:message,消息数据
*/
Consumer deliverCallbackConsumer = new DefaultConsumer(channel){
@override
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] message){
System.out.println(“message:”+new String(message));
}
}
/*
参数1: routingkey,路由名,简单模式下,交换机与路由名相互绑定,为队列名称QUEUE_NAME
参数2:autoAck,自动确认
参数3:callback,回调对象,deliverCallback
参数4:body,message 消息内容,字节信息
*/
channel.basicConsume(QUEUE_NAME, true, deliverCallbackConsumer);
//消费者不能关闭资源,因为需要不间断的监听消息队列
}
}
================================================================================================
3. Publish/Subscribe模式
producer发送一条消息,两个消费者都可以收到
- producer将消息发送给X(exchange),不直接发送给队列。
- X:交换机。转发消息,有三种处理方式:
- Fanout:广播,将消息交给所有与其绑定的队列
- Direct:定向,将消息交给指定的routingkey队列
- Topic:通配符,将消息交给符合的routing pattern的队列
- X,交换机,只负责消息的转发,不具备存储能力,如果没有队列接收,消息将被丢失。
Producer
public class Producer{
public static void main(String[] argv) throws Exception {
//1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("localhost"); //设置主机ip
//3. 创建连接
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建交换机
String exchange_name = "exc_name";
/*
交换机参数列表:
参数1:交换机名称
参数2:交换机类型,四种
参数3:是否持久化
参数4:自动删除
参数5:内部使用
参数6:参数
*/
channel.exchangeDeclare(exchange_name ,'fanout',true,false,false,null);
//6. 创建队列
private String queue_name1 = "hello1";
private String queue_name2 = "hello2";
channel.exchangeQueue(queue_name1 ,true,false,false,null);
channel.exchangeQueue(queue_name2 ,true,false,false,null);
//7. 绑定交换机与队列
/*
绑定参数列表:
参数1:队列名
参数2:交换机名
参数3:routingkey,绑定规则,默认为fonout,“”
*/
channel.queueBind(queue_name1 ,exchange_name ,"");
channel.queueBind(queue_name2 ,exchange_name ,"");
//8. 发送消息
String message = "publish/subscribe";
channel.basicPublish(exchange_name,"",null,message.getBytes() )
//9. 释放资源
channel.close();
channel.connection();
}
}
consumer1
public class Consumer1{
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//接收消息
Consumer deliverCallbackConsumer = new DefaultConsumer(channel){
@override
public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] message){
System.out.println(“message:”+new String(message));
}
}
//两个消费者,监听自己的队列,绑定对应的队列名
channel.basicConsume(queue_name1, true, deliverCallbackConsumer);
//消费者不能关闭资源,因为需要不间断的监听消息队列
}
}
==============================================================================
4. Routing模式
- 队列与路由指定routingkey绑定
- producer向exchange转发消息的时候,也必须指定routingkey
- exchange与消息队列不在固定绑定,而是根据消息的routingkey进行判断,队列的routingkey与消息的routingkey一致,才可以接收到消息。
Producer
public class Producer{
public static void main(String[] argv) throws Exception {
//1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置参数
factory.setHost("localhost"); //设置主机ip
//3. 创建连接
Connection connection = factory.newConnection();
//4. 创建Channel
Channel channel = connection.createChannel();
//5. 创建交换机
String exchange_name = "exc_name";
channel.exchangeDeclare(exchange_name ,BuiltinExchangeType.DIRECT,true,false,false,null);
//6. 创建队列
private String queue_name1 = "hello1";
private String queue_name2 = "hello2";
channel.exchangeQueue(queue_name1 ,true,false,false,null);
channel.exchangeQueue(queue_name2 ,true,false,false,null);
//7. 绑定交换机与队列
//队列1绑定一个routingkey
channel.queueBind(queue_name1 ,exchange_name ,"error");
//队列2绑定三个routingkey
channel.queueBind(queue_name2 ,exchange_name ,"info");
channel.queueBind(queue_name2 ,exchange_name ,"error");
channel.queueBind(queue_name2 ,exchange_name ,"warning");
//8. 发送消息
String message = "publish/subscribe";
//发送一个routingkey=="info"的消息
channel.basicPublish(exchange_name,"info",null,message.getBytes() )
//9. 释放资源
channel.close();
channel.connection();
}
}
consumer1
consumer与publish/subscribe一样。。。。。。
============================================================================
5. Topics通配符模式
与routing模式类似,将routingkey改进为通配符模式,通配符之间用“ . ”隔开
- *,代表一个单词
- #,代表多个单词
============================================================================
6. RPC远程调用模式(不介绍)
============================================================================
SpringBoot整合RabbitMQ
生产者
- 创建生产者SpringBoot工程
- 引入start,依赖坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 编写yml配置,基本信息配置
# 配置ip,端口,username ,password
spring:
rabbitmq:
host: loscakhost
port: 5672
username: ....
password: ****
virtual-host: /
- 定义交换机,队列以及绑定关系的配置类
@Configuration
public class RabbitMQConfig{
public static final String EXCHANGE_NAME = "EXCHANGE_NAME";
public static final String QUEUE_NAME = "QUEUE_NAME";
//1. 创建交换机
@Bean("createExchage")
public Exchage createExchage(){
return ExchageBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//2. 创建队列
@Bean("createQueue")
public Queue createQueue(){
return QueueBuilder..durable(QUEUE_NAME).build();
}
//3. 绑定队列和交换机
@Bean
public Binding bindQueueExchange(Queue queue,Exchage exchange){
return BindingBuilder.bind(queue).to(exchange).with("*.*");
}
}
- 注入RabbitTemplate,调用方法,完成消息发送
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProducerTest{
//1. 注入RabbitTemplate
@AutoWired
private RabbitTemplate rabbitTempate;
@Test
public void testSend(){
//参数列表(exchange,routingkey,message)
rabbitTempate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "xxx.xxx", “hello world” );
}
}
消费者
创建消费者SpringBoot工程
引入start,依赖坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
编写yml配置,基本信息配置
定义监听类,使用@RabbitListener注解完成队列监听
@Component
public class RabbitMQListener{
// @RabbitListener完成消息接收
@RabbitListener(queue = QUEUE_NAME )
public void listenerQueue(Message message){
System.out.println(message.getBytes());
}
}
============================================================================