一、MQ基本概念
1. MQ概述
MQ全称 Message Queue([kjuː])(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。
(队列是一种容器,用于存放数据的都是容器,存放消息的就是消息队列)
分布式系统的调用:
方式一:直接调用
order
product
account
方式二:间接调用
A将数据存放到中间一个系统,通过中间的系统发送到B
中间系统可以成为中间件MQ
生产者-》中间件《--消费者
MQ是用于存放消息的中间件
被调用者叫生产者 调用者是消费者(微服务中说过)
2. MQ的优势和劣势
2.1 优势
应用解耦:提高系统容错性和可维护性。
异步提速:提升用户体验和系统吞吐量。
削峰填谷:提高系统稳定性。
应用解耦
系统的耦合性越高,容错性就越低,可维护性就越低。
例:订单系统 的时候 依赖于库存系统 支付系统 物流系统 当库存系统发生异常,就有可能导致订单系统发生异常 下单失败
追加系统 x 就只能修改订单系统更改代码 导致维护性比较低
使用 MQ 使得应用间解耦,提升容错性和可维护性
库存系统宕机订单系统影响不大,因为消息已经发送到mq了当库存系统恢复的时候就可以正常使用了。
追加系统的时候跟订单系统无关
已将数据发送到MQ了,直接从MQ中拿就行了,无需更改订单中的代码,可维护性提高
异步提速
一个下单操作耗时:20 + 300 + 300 + 300 = 920ms
用户点击完下单按钮后,需要等待920ms才能得到下单响应,太慢!
用户点击完下单按钮后,只需等待25ms就能得到下单响应 (20 + 5 = 25ms)。
提升用户体验和系统吞吐量(单位时间内处理请求的数目)。
以前920ms处理一个请求,现在25ms处理一个请求,系统的吞吐量(单位时间内访问量)增加
削峰填谷(削峰)
使用了 MQ 之后,限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会被积压在 MQ 中,高峰就被“削”掉了,但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫做“填谷”。
使用MQ后,可以提高系统稳定性。
2.2 劣势
系统可用性降低
系统引入的外部依赖越多,系统稳定性越差。一旦 MQ 宕机,就会对业务造成影响。如何保证MQ的高可用?
系统复杂度提高
MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
一致性问题
A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理失败。如何保证消息数据处理的一致性?
2.3 既然 MQ 有优势也有劣势,那么使用 MQ 需要满足什么条件呢?
消费者--》生产者
- 生产者不需要从消费者处获得反馈。引入消息队列之前的直接调用,其接口的返回值应该为空,这才让明明下层的动作还没做,上层却当成动作做完了继续往后走,即所谓异步成为了可能。
订单->库存
- 容许短暂的不一致性。
- 确实是用了有效果。即解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本。
二、RabbitMQ基本介绍
AMQP,即 Advanced Message Queuing Protocol(英[ˈprəʊtəkɒl])(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。2006年,AMQP 规范发布。类比HTTP。
1.RabbitMQ的安装和配置
1.1 安装依赖环境
在线安装依赖环境:
yum -y install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz
1.2. 安装Erlang
# 安装
rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm
1.3. 安装RabbitMQ
#安装依赖的包
rpm -ivh socat-1.7.3.2-2.el7.x86_64.rpm
#安装rabbitmq
rpm -ivh rabbitmq-server-3.7.18-1.el7.noarch.rpm
1.4. 启动RabbitMQ
systemctl start rabbitmq-server # 启动服务
systemctl stop rabbitmq-server # 停止服务
systemctl restart rabbitmq-server # 重启服务
systemctl status rabbitmq-server #查看状态
1.5. 开启管理界面及配置
开启管理界面
rabbitmq-plugins enable rabbitmq_management
修改默认配置信息
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.7.18/ebin/rabbit.app
比如修改密码、配置等等,例如:loopback_users 中的 <<"guest">>,只保留guest
修改之后重启一下rabbitmq
开启web界面管理插件
rabbitmq-plugins enable rabbitmq_management
打开客户端
192.168.221.37:15672
1.6. 搭建工程
1.6.1 添加依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
1.6.2. 编写生产者
package org.example.test;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
public class MyProcedureTest {
@Test
public void procedure() throws IOException, TimeoutException {
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setUsername("lhj");
connectionFactory.setPassword("123");
connectionFactory.setHost("192.168.226.66");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("lhj");
//建立连接
Connection connection=connectionFactory.newConnection();
//管道
Channel channel=connection.createChannel();
channel.queueDeclare("bbb",false,false,false,null);
for (int i=0;i<=20;i++) {
channel.basicPublish("","bbb",null,("我了个豆,好厉害"+i).getBytes(StandardCharsets.UTF_8));
}
}
}
1.7.编写消费者
package org.example;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args ) throws IOException, TimeoutException {
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setUsername("lhj");
connectionFactory.setPassword("123");
connectionFactory.setHost("192.168.226.66");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("lhj");
//建立连接
Connection connection=connectionFactory.newConnection();
//管道
Channel channel=connection.createChannel();
//消费信息
Consumer consumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String s = new String(body);
System.out.println("a---"+s);
}
};
channel.basicConsume("bbb",true,consumer);
}
}
2.Work queues工作队列模式
Work Queens与入门程序的简单模式的代码是几乎一样的;可以完全复制,并复制多一个消费者进行多个消费者同时消费消息的测试。
3.订阅模式类型
前面2个案例中,只有3个角色:
- P:生产者,也就是要发送消息的程序
- C:消费者:消息的接受者,会一直等待消息到来。
- queue:消息队列,图中红色部分
而在订阅模型中,多了一个exchange角色,而且过程略有变化:
生产者发消息给交换机,交换机将消息路由分发给队列,消费者监听队列接收信息
- P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
- C:消费者,消息的接受者,会一直等待消息到来。
- Queue:消息队列,接收消息、缓存消息。
- Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
- Fanout:广播,将消息交给所有绑定到交换机的队列
- Direct:定向,把消息交给符合指定routing key 的队列
- Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
Exchange(交换机)只负责转发消息,不具备存储消息的能力
,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
3.1.交换机绑定队列代码
package org.example.test;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
public class MyProcedureTest1 {
@Test
public void procedure() throws IOException, TimeoutException {
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setUsername("lhj");
connectionFactory.setPassword("123");
connectionFactory.setHost("192.168.226.66");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("lhj");
//建立连接
Connection connection=connectionFactory.newConnection();
//管道
Channel channel=connection.createChannel();
channel.exchangeDeclare("myex1", BuiltinExchangeType.FANOUT,false);
channel.queueDeclare("cccc",false,false,false,null);
channel.queueDeclare("dddd",false,false,false,null);
channel.queueBind("cccc","myex1","");
channel.queueBind("dddd","myex1","");
channel.basicPublish("myex1","",null,"交换机".getBytes(StandardCharsets.UTF_8));
// for (int i=0;i<=20;i++) {
// channel.basicPublish("","bbb",null,("我了个豆,好厉害"+i).getBytes(StandardCharsets.UTF_8));
// }
}
}
3.2总结:
发送消息的时候声明交换机,并且设置交换机的类型为fanout,当指定这个类型之后对应的routingkey的值为"" 最后交换机将消息发送给它绑定的每一个队列,每个消费者只监听自己的队列
交换机需要与队列进行绑定,绑定之后;一个消息可以被多个消费者都收到。
发布订阅模式与工作队列模式的区别
1、工作队列模式不用定义交换机,而发布/订阅模式需要定义交换机。
2、发布/订阅模式的生产方是面向交换机发送消息,工作队列模式的生产方是面向队列发送消息(底层使用默认交换机)。
3、发布/订阅模式需要设置队列和交换机的绑定,工作队列模式不需要设置,实际上工作队列模式会将队列绑 定到默认的交换机 。