目录
初识MQ
同步调用
在实际生产中,比如微服务间基于Feign的调用就是属于同步方式,虽然同步调用的时效性比较强,可以立即得到结果,但是会存在一些问题。
- 耦合度较高,如果不断地需要去对支付系统做功能的增加,比如增加短信服务,就需要不断地去对支付服务源码进行修改
- 性能、吞吐量较低 ,支付业务在调用订单服务时是同步调用,所以必须得等订单服务完成才能调用仓储服务,然后等仓储服务调用完再调短信服务,总耗时很长
- 资源浪费,在调用其他服务执行的过程中,支付服务的cpu等资源一直空闲着,资源利用不充分
- 级联失败问题,比如仓储服务挂掉了,然后支付服务去调用仓储服务就会阻塞
异步调用
异步调用最常见的实现就是事件驱动模式
Broker:事件代理者,一旦支付完成,Broker会通知订阅者服务
支付服务只需要让Broker去通知其他服务,不需要等待订阅者服务调用
优势:
- 服务解耦合:新增功能服务只需要给Broker新增订阅,删除服务只需要取消订阅,不需要修改源码
- 性能 ,吞吐量提高:不存在调用,所以支付服务不需要等待订阅服务,耗时更短
- 故障隔离:服务没有强依赖,不用担心级联失败
- 流量削峰:高并发流量时,通过Broker进行缓存,微服务可以根据自己的能力去Broker获取事件
缺点:
- 依赖于Broker的可靠性、安全性、吞吐能力
- 架构复杂了,业务没有明显的流程线,不好追踪管理
什么是MQ
MQ(MessaqeQueue),中文是消息队列,字面来看就是存放消息的队列。也就是事件驱动架构中的Broker。
比较常用的MQ:
Kafka:吞吐能力见长,稳定性比较差,所以更适合于海量数据的传输,但是对于数据安全要求不高的 ,比如说日志数据传输
RubbitMQ和RocketMQ:可靠性更强,稳定性更高,而且吞吐量也不是特别的差,更适合用于对稳定性要求较高的,比如说业务之间的通信
RabbitMQ介绍
RabbitMO是基于Erlang语言开发的开源消息通信中间件,官网地址: RabbitMQ: easy to use, flexible messaging and streaming — RabbitMQ
RabbitMQ的部署指南
1.下载镜像
方式一:在线拉取
docker pull rabbitmq:3-management
方式二:从本地加载
上传到虚拟机后,使用命令加载镜像即可:
docker load -i mq.tar
2.安装MQ
docker run \
-e RABBITMQ_DEFAULT_USER=zstc \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
然后就可以通过主机地址:端口号进入mq管理页面
RabbitMQ的结构和概念
发布者将消息发到交换机上,由交换机进行路由发送到队列上,然后再由订阅者去队列上取消息
VirtualHost,虚拟主机:每创建一个用户,都会有自己的虚拟主机,各个虚拟主机之间是相互隔离的,避免干扰
消息模型
基本消息队列和工作消息队列都是基于队列进行消息发送的,没有交换机进行路由,不是一个完整的消息驱动模型
发布订阅模型又按照交换机的类型进行划分为3种:广播,路由,主题
HelloWorld
官方的Helloworld是基于最基础的消息队列模型来实现的,只包括三个角色
- publisher:消息发布者,将消息发送到队列queue
- queue:消息队列,负责接受并缓存消息
- consumer:订阅队列,处理队列中的消息
queue是由mq来管理的,而Publisher和consumer是由我们自己编写的
RabbitMQ的消费者一旦拿到消息,消息就会从消息队列删除
基本消息队列的消息发送流程:
1.建立connection
2.创建channel
3.利用channel声明队列
4.利用channel向队列发送消息
基本消息队列的消息接收流程:
1.建立connection
2.创建channel
3.利用channel声明队列
4.定义consumer的消费行为handleDelivery()5.利用channel将消费者与队列绑定
消费者和生产者都需要声明队列,为了应对消费者先于生产者进入队列的情况
public class PublisherTest {
@Test
public void testSendMessage() throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("192.168.150.101");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("itcast");
factory.setPassword("123321");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.发送消息
String message = "hello, rabbitmq!";
channel.basicPublish("", queueName, null, message.getBytes());
System.out.println("发送消息成功:【" + message + "】");
// 5.关闭通道和连接
channel.close();
connection.close();
}
public class ConsumerTest {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("192.168.150.101");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("itcast");
factory.setPassword("123321");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.订阅消息
channel.basicConsume(queueName, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
// 5.处理消息
String message = new String(body);
System.out.println("接收到消息:【" + message + "】");
}
});
System.out.println("等待接收消息。。。。");
}
}
这种方式代码太过繁琐,所以我们推荐使用SpringAMQP,详情可以看我们下一篇的SpringAMQP笔记
学习笔记from黑马程序员