一、RabbitMQ初识
RabbitMQ是一个实现了高级消息队列协议(AMQP的消息代理(也叫消息中间件),它接受并转发消息。它可以帮你处理一些逻辑的事务,从而进行解耦,比如用户注册落库之后,还需要发送邮件验证、需要发送新人红包等等事情,就可以交给中间件去做。也可以把它当成一个邮局:当你想邮寄信件的时候,你会把信件放在投递箱中,并确信邮递员最终会将信件送到收件人的手里。在这个例子中,RabbitMQ就相当与投递箱、邮局和邮递员。
1.1 AMQP协议
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件产品、不同的开发语言等条件的限制。
1.2 典型应用场景
- 跨系统的异步通信 ,异步、解耦、削峰。
- 应用内的同步变成异步 秒杀:自己发送给自己
- 基于Pub/Sub模型实现的事件驱动 ,摒弃ELT(比如全量 同步商户数据); 摒弃API(比如定时增量获取用户、获取产品,变成增量广播)。
- 利用RabbitMQ实现事务的最终一致性
1.3 RabbitMQ的特性
RabbitMQ使用Erlang语言编写,使用Mnesia数据库存储消息。
- 可靠性(Reliability) RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。
- 灵活的路由(Flexible Routing) 在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在 一起,也通过插件机制实现自己的 Exchange 。
- 消息集群(Clustering) 多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。
- 高可用(Highly Available Queues) 队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下
队列仍然可用。 - 多种协议(Multi-protocol) RabbitMQ 支持多种消息队列协议,比如 AMQP、STOMP、MQTT 等等。
- 多语言客户端(Many Clients) RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby、PHP、C#、 JavaScript 等等。
- 管理界面(Management UI) RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集群 中的节点。
- 插件机制(Plugin System) RabbitMQ提供了许多插件,以实现从多方面扩展,当然也可以编写自己的插件。
1.4 工作模型
- Broker
我们要使用 RabbitMQ 来收发消息,必须要安装一个 RabbitMQ 的服务,可以安装在 Windows 上面也可以安装在 Linux 上面,默认是 5672 的端口。这台 RabbitMQ 的服务器我们把它叫做 Broker, MQ 服务器帮助我们做的事情就是存储、转发消息。 - Connection
无论是生产者发送消息,还是消费者接收消息,都必须要跟 Broker 之间建立一个连接,这个连接是一个 TCP 的长连接。 - Channel
如果所有的生产者发送消息和消费者接收消息,都直接创建和释放 TCP 长连接的话,对于 Broker 来说肯定会造成很大的性能损耗,因为 TCP 连接是非常宝贵的资源,创建和释放也要消耗时间。所以在 AMQP 里面引入了 Channel 的概念,它是一个虚拟的连接。我们把它翻译成通道,或者消息信道。这样我们就可以在保持的 TCP 长连接里面去创建和释放Channel,大大了减少了资源消耗。另外一个需要注意的是,Channel 是 RabbitMQ 原生 API 里面的最重要的编程接口,也就是说我们定义交换机、队列、绑定关系,发送消息消费消息,调用的都是 Channel 接口上的方法。 - Queue
在其他一些 MQ 里面,比如ActiveMQ ,消息都是发送到队列上的。队列是真正用来存储消息的,是一个独立运行的进程,有自己的数据库。消费者获取消息有两种模式,一种是 Push 模式,只要生产者发到服务器,就马上推送给消费者。另一种是 Pull 模式,消息存放在服务端,只有消费者主动获取才能拿到消息。消费者需要写一个 while 循环不断地从队列获取消息吗?不需要,我们可以基于事件机制,实现消费者对队列的监听。
由于队列有 FIFO 的特性,只有确定前一条消息被消费者接收之后,才会把这条消息从数据库删除,继续投递下一条消息。 - Exchange
在 RabbitMQ 里面永远不会出现消息直接发送到队列的情况。因为在 AMQP 里面引入了交换机(Exchange)的概念,用来实现消息的灵活路由。
交换机是一个绑定列表,用来查找匹配的绑定关系。队列使用绑定键(Binding Key)跟交换机建立绑定关系。生产者发送的消息需要携带路由键(Routing Key),交换机收到消息时会根据它保存的绑定列表,决定将消息路由到哪些与它绑定的队列上。注意:交换机与队列、队列与消费者都是多对多的关系。 - Vhost
我们每个需要实现基于 RabbitMQ 的异步通信的系统,都需要在服务器上创建自己要用到的交换机、队列和它们的绑定关系。如果某个业务系统不想跟别人混用一个系统,怎么办?再采购一台硬件服务器单独安装一个 RabbitMQ 服务?这种方式成本太高了。在同一个硬件服务器上安装多个 RabbitMQ 的服务呢?比如再运行一个 5673 的端口?
没有必要,因为 RabbitMQ 提供了虚拟主机 VHOST。VHOST 除了可以提高硬件资源的利用率之外,还可以实现资源的隔离和权限的控制。它的作用类似于编程语言中的 namespace 和 package,不同的 VHOST 中可以有同名的 Exchange 和 Queue,它们是完全透明的。
这个时候,我们可以为不同的业务系统创建不同的用户(User),然后给这些用户分配 VHOST 的权限。比如给风控系统的用户分配风控系统的 VHOST 的权限,这个用户可以访问里面的交换机和队列。给超级管理员分配所有 VHOST 的权限。 - Producer生产者:主要将消息投递到对应的Exchange上面。一般是独立的程序。
- Consumer消费者:消息的接收者,一般是独立的程序。
1.5 三种主要的交换机
1.5.1 Direct Exchange 直连交换机
定义:直连类型的交换机与一个队列绑定时,需要指定一个明确的binding key。
路由规则:发送消息到直连类型的交换机时,只有routing key跟binding key完全匹配时,绑定的队列才能收到消息。
例如:
// 只有队列1能收到消息
channel.basicPublish("MY_DIRECT_EXCHANGE", "key1", null, msg.getBytes());
1.5.2 Topic Exchange 主题交换机
定义:主题类型的交换机与一个队列绑定时,可以指定按模式匹配的routing key。
通配符有两个,*
代表匹配一个单词。#
代表匹配零个或者多个单词。单词与单词之间用 .
隔开。
路由规则:发送消息到主题类型的交换机时,routing key符合binding key的模式时,绑定的队列才能收到消息。
例如:
// 只有队列1能收到消息
channel.basicPublish("MY_TOPIC_EXCHANGE", "ab.123", null, msg.getBytes());
// 队列2和队列3能收到消息
channel.basicPublish("MY_TOPIC_EXCHANGE", "bc.abc", null, msg.getBytes());
1.5.2 Fanout Exchange 广播交换机
定义:广播类型的交换机与一个队列绑定时,不需要指定binding key。
路由规则:当消息发送到广播类型的交换机时,不需要指定routing key,所有与之绑定的队列都能收到消息。
例如:
// 3个队列都会收到消息
channel.basicPublish("MY_FANOUT_EXCHANGE", "", null, msg.getBytes());
二、安装运行
2.1 windows和linux系统
直接官网下载压缩包,解压即可,到sbin目录启动rabbitmq-server即可。
2.2 mac
mac系统下载安装包也可以,本人是用homebrew安装的。
brew install rabbitmq
安装后会有启动命令。
# 启动
$ brew services start rabbitmq
# 重启
$ brew services restart rabbitmq
# 停止
$ brew services stop rabbitmq
启动完成访问http://localhost:15672/#/
。看到下面页面就是安装成功了。
账号密码都是guest。也不用输入。直接login就行。
三、简单的生产消费例子
先引入pom依赖。基于springboot的。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3.1 生产者
package com.example.demo;
/**
* @author : pengweiwei
* @date : 2020/1/29 7:09 下午
*/
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.Map;
public class MyProducer {
private final static String QUEUE_NAME = "ORIGIN_QUEUE";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
// 连接IP
factory.setHost("127.0.0.1");
// 连接端口
factory.setPort(5672);
// 虚拟机
factory.setVirtualHost("/");
factory.setUsername("guest");
factory.setPassword("guest");
// 建立连接
Connection conn = factory.newConnection();
// 创建消息通道
Channel channel = conn.createChannel();
String msg = "Hello, RabbitMQ";
// 声明队列
/**
* 第一个参数:String queue
* 第二个参数:boolean durable
* 第三个参数:boolean exclusive,
* 第四个参数:boolean autoDelete
* 第五个参数:Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 发送消息(发送到默认交换机AMQP Default,Direct) 如果有一个队列名称跟Routing Key相等,那么消息会路由到这个队列
/**
* 第一个参数:String exchange
* 第二个参数:String routingKey
* 第三个参数:BasicProperties props
* 第四个参数:byte[] body
*/
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
channel.close();
conn.close();
}
}
3.2 消费者
package com.example.demo;
/**
* @author : pengweiwei
* @date : 2020/1/29 7:09 下午
*/
import com.rabbitmq.client.*;
import java.io.IOException;
public class MyConsumer {
private final static String QUEUE_NAME = "ORIGIN_QUEUE";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
// 连接IP
factory.setHost("127.0.0.1");
// 默认监听端口
factory.setPort(5672);
// 虚拟机
factory.setVirtualHost("/");
// 设置访问的用户
factory.setUsername("guest");
factory.setPassword("guest");
// 建立连接
Connection conn = factory.newConnection();
// 创建消息通道
Channel channel = conn.createChannel();
/**
* 声明队列
* 第一个参数:String queue
* 第二个参数:boolean durable
* 第三个参数:boolean exclusive,
* 第四个参数:boolean autoDelete
* 第五个参数:Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" Waiting for message....");
// 创建消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("Received message : '" + msg + "'");
}
};
/**
* 开始获取消息
* 第一个参数:String queue
* 第二个参数:boolean autoAck
* 第三个参数:Consumer callback
*/
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
2.3 运行结果
2.4 参数说明
声明交换机的参数
String type:交换机的类型,direct, topic, fanout中的一种。
boolean durable:是否持久化,代表交换机在服务器重启后是否还存在。
声明队列的参数
boolean durable:是否持久化,代表队列在服务器重启后是否还存在。
boolean exclusive:是否排他性队列。排他性队列只能在声明它的Connection中使用,连接断开时自动删除。
boolean autoDelete:是否自动删除。如果为true,至少有一个消费者连接到这个队列,之后所有与这个队列连接 的消费者
都断开时,队列会自动删除。
Map<String, Object> arguments:队列的其他属性。含义详细见下表。
属性 | 含义 |
---|---|
x-message-ttl | 队列中消息的存活时间,单位毫秒 |
x-expires | 队列在多久没有消费者访问以后会被删除 |
x-max-length | 队列的最大消息数 |
x-max-length-bytes | 队列的最大容量,单位 Byte |
x-dead-letter-exchange | 队列的死信交换机 |
x-dead-letter-routing-key | 死信交换机的路由键 |
x-max-priority | 队列中消息的最大优先级,消息的优先级不能超过它 |
消息属性 BasicProperties
主要的参数:
参数 | 释义 |
---|---|
Map<String,Object> headers | 消息的其他自定义参数 |
Integer deliveryMode | 持久化,其他:瞬态 |
Integer priority | 消息的优先级 |
String correlationId | 关联 ID,方便 RPC 相应与请求关联 |
String replyTo | 回调队列 |
String expiration TTL | , 消息过期时间,单位毫秒 |
来源:CSDN
作者:忘不掉就记着吧
链接:https://blog.csdn.net/weiwei_six/article/details/104108709