MQ全称 Message Queue([kjuː])(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。
(队列是一种容器,用于存放数据的都是容器,存放消息的就是消息队列)
分布式系统的调用:
order
product
account
A将数据存放到中间一个系统,通过中间的系统发送到B
中间系统可以成为中间件MQ
生产者-》中间件《--消费者
MQ是用于存放消息的中间件
被调用者叫生产者 调用者是消费者(微服务中说过)
应用解耦:提高系统容错性和可维护性。
异步提速:提升用户体验和系统吞吐量。
削峰填谷:提高系统稳定性。
应用解耦
系统的耦合性越高,容错性就越低,可维护性就越低。
例:订单系统 的时候 依赖于库存系统 支付系统 物流系统 当库存系统发生异常,就有可能导致订单系统发生异常 下单失败
追加系统 x 就只能修改订单系统更改代码 导致维护性比较低
使用 MQ 使得应用间解耦,提升容错性和可维护性
库存系统宕机订单系统影响不大,因为消息已经发送到mq了当库存系统恢复的时候就可以正常使用了。
追加系统的时候跟订单系统无关
已将数据发送到MQ了,直接从MQ中拿就行了,无需更改订单中的代码,可维护性提高
异步提速
一个下单操作耗时:20 + 300 + 300 + 300 = 920ms
用户点击完下单按钮后,需要等待920ms才能得到下单响应,太慢!
用户点击完下单按钮后,只需等待25ms就能得到下单响应 (20 + 5 = 25ms)。
提升用户体验和系统吞吐量(单位时间内处理请求的数目)。
以前920ms处理一个请求,现在25ms处理一个请求,系统的吞吐量(单位时间内访问量)增加
削峰填谷(削峰)
使用了 MQ 之后,限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会被积压在 MQ 中,高峰就被“削”掉了,但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫做“填谷”。
使用MQ后,可以提高系统稳定性。
系统可用性降低
系统引入的外部依赖越多,系统稳定性越差。一旦 MQ 宕机,就会对业务造成影响。如何保证MQ的高可用?
系统复杂度提高
MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
一致性问题
A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理失败。如何保证消息数据处理的一致性?
既然 MQ 有优势也有劣势,那么使用 MQ 需要满足什么条件呢?
消费者--》生产者
订单->库存
目前业界有很多的 MQ 产品,例如 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,也有直接使用 Redis 充当消息队列的案例,而这些消息队列产品,各有侧重,在实际选型时,需要结合自身需求及 MQ 产品特征,综合考虑。
AMQP,即 Advanced Message Queuing Protocol(英[ˈprəʊtəkɒl])(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。2006年,AMQP 规范发布。类比HTTP。
消息队列中间件
exchange 交换机 分发消息 分发到不同的容器 queue 通过路由来处理
queue 容器
routes 路由
生产者 发布消息到exchange exchange 通过不同的路由规则发布/路由 给不同的queue 进行存储 cunsumer通过队列去监听拿到消息进行消费
2007年,Rabbit 技术公司基于 AMQP 标准开发的 RabbitMQ 1.0 发布。RabbitMQ 采用 Erlang 语言开发。Erlang 语言由 Ericson 设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。
Broker 中间者 服务
procedure 和consumer都是客户端
客户端通过链接和服务端进行通信 所以需要建立起来连接 然后进行通信a
使用channel(管道)节省资源
一个rabbitmq里面有很多的虚拟机 相当于mysql里面有很多数据库,数据库里面有很多表,都是独立的。
每个虚拟机里面有很多的exchange和queue 独立分区的作用
Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker。
Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等。
Connection:publisher/consumer 和 broker 之间的 TCP 连接。
Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销。
Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:
direct (point-to-point)
topic (publish-subscribe)
fanout (multicast)
Queue:消息最终被送到这里等待 consumer 取走
Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据
RabbitMQ 提供了 6 种工作模式:
简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing 路由模式、Topics 主题模式、RPC 远程调用模式(远程调用,不太算 MQ;暂不作介绍)。==
官网对应模式介绍:RabbitMQ Tutorials — RabbitMQ
MQ是消息通信的模型;实现MQ的大致有两种主流方式:AMQP、JMS。
AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。
JMS即Java消息服务(Java Message Service)应用程序接口,
是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
(规定了消息客户端的一套api的东西,rabbitmq没有遵循规则)
JMS 是 JavaEE 规范中的一种,类比JDBC。
JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式。
JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
JMS规定了两种消息模式;而AMQP的消息模式更加丰富
ActiveMQ:基于JMS
ZeroMQ:基于C语言开发
RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
RocketMQ:基于JMS,阿里巴巴产品
Kafka:类似MQ的产品;分布式消息系统,高吞吐量。
在线安装依赖环境:
yum -y install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz
rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm
安装RabbitMQ
rpm -ivh socat-1.7.3.2-2.el7.x86_64.rpm
rpm -ivh rabbitmq-server-3.7.18-1.el7.noarch.rpm
启动RabbitMQ
systemctl start rabbitmq-server # 启动服务
systemctl stop rabbitmq-server # 停止服务
systemctl restart rabbitmq-server # 重启服务
systemctl status rabbitmq-server #查看状态
rabbitmq-plugins enable rabbitmq_management
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.7.18/ebin/rabbit.app
192.168.221.37:15672
使用guest/guest登录之后出现如下即为安装成功
先清除
再分配
查看结果
分配成功
重新使用新用户登录
发送成功后
消费信息
com.rabbitmq amqp-client 5.6.0
public class MyTest { @Test public void aaa() throws IOException, TimeoutException { ConnectionFactory connectionFactory = new ConnectionFactory(); //连接mq connectionFactory.setUsername("账号"); connectionFactory.setPassword("密码"); connectionFactory.setHost("IP地址"); connectionFactory.setPort(端口号); connectionFactory.setVirtualHost("/***"); //建立连接 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("mq:"+s); } }; channel.basicConsume("test",true,consumer); } }
结果
public class MyTest { @Test public void bbb() throws IOException, TimeoutException { ConnectionFactory connectionFactory = new ConnectionFactory(); //连接mq connectionFactory.setUsername("账号"); connectionFactory.setPassword("密码"); connectionFactory.setHost("IP地址"); connectionFactory.setPort(端口号); connectionFactory.setVirtualHost("/***"); //建立连接 Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); /** * String queue, 队列的名称 * boolean durable, 持久化 * boolean exclusive, 是否独占 * boolean autoDelete, 受否自动删除 * Maparguments 参数 */ channel.queueDeclare("test",false,false,false,null);// 创建队列 channel.basicPublish("","test",null,"hello mq1".getBytes()); } }
发消息
官网对应模式介绍:RabbitMQ Tutorials — RabbitMQ
Work Queues
与入门程序的简单模式
相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。
应用场景
:对于 任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
public class MyTestEx { @Test public void bbb() throws IOException, TimeoutException { ConnectionFactory connectionFactory = new ConnectionFactory(); //连接mq connectionFactory.setUsername("账号"); connectionFactory.setPassword("密码"); connectionFactory.setHost("IP地址"); connectionFactory.setPort(端口号); connectionFactory.setVirtualHost("/***"); //建立连接 Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); //创建交换机 channel.exchangeDeclare("myex1", BuiltinExchangeType.FANOUT,false); //创建队列 /** * String queue, 队列的名称 * boolean durable, 持久化 * boolean exclusive, 是否独占 * boolean autoDelete, 受否自动删除 * Maparguments 参数 */ channel.queueBind("testmyex1",false,false,false,null); channel.queueBind("testmyex2",false,false,false,null); //绑定交换机 channel.exchangeBind("testmyex1","myex1",""); channel.exchangeBind("testmyex2","myex1",""); channel.basicPublish("myex1","",null,"testex".getBytes()); } }