RabbitMQ支持多种协议,包括:AMQP、STOMP、MQTT、HTTP、WebSockets。
向RabbitMQ发送消息的一端。
从RabbitMQ接收消息的一端。
可以理解为安装了RabbitMQ服务的服务器。默认是5672端口。
无论是生产者发送消息,还是消费者接收消息,都必须要跟 Broker 之间建立一个连接(Connection)。这个连接是一个 TCP 的长连接。
根据消息到底是 Broker 推送给消费者的?还是消费者主动获取的?消费者消费消息可以分为两种模式。
Pull模式:对应的方法是 basicGet。消息存放在服务端,只有消费者主动获取才能拿到消息。消息的实时性会降低。但是好处是可以根据自己的消费能力决定获取消息的频率。
Push模式:对应的方法是 basicConsume。只要生产者发消息到服务器,就马上推送给消费者,消息保存在客户端。消息的实时性很高。但是如果消费不过来有可能会造成消息积压。
Spring AMQP 是 push 方式,通过事件机制对队列进行监听,只要有消息到达队列,就会触发消费消息的方法。
RabbitMQ 中 pull 和 push 都有实现。而 kafka 和 RocketMQ 只有 pull。
生产者发送消息、消费者接收消息都是在TCP的长连接上进行,如果每个生产者、消费者使用时创建TCP长连接,结束时释放TCP长连接。频繁创建和释放 TCP 长连接会对 Broker 造成很大的性能损耗,也会浪费时间。
为了解决这个问题,在 AMQP 里面引入了 Channel 的概念,它是一个虚拟的连接。我们把它翻译成通道,或者消息信道。这样我们就可以在保持的 TCP 长连接情况下,在里面去创建和释放Channel,从而大大了减少了资源消耗。
所以,一个Connection中可以有多个Channel。
Channel 仅存在于连接的上下文中,不能独立存在。当连接关闭时,其上的所有通道也会关闭。不同的 Channel 之间是相互隔离的,每个 Channel 都有自己的编号(通道号,是一个整数)。对于每个客户端线程来说,Channel 不共享,各自用自己的 Channel。
Channel 是 RabbitMQ 原生 API 里面的最重要的编程接口。我们定义交换机、队列、绑定关系,发送消息,消费消息,调用的都是Channel 接口上的方法。
队列是生产者和消费者之间的纽带。生产者发送的消息到达队列,在队列中存储。消费者从队列消费消息。
**一个消费者是可以监听多个队列的,一个队列也可以被多个消费者监听。**但是在生产环境中,**建议一个消费者只处理一个队列的消息。**如果需要提升处理消息的能力,可以增加多个消费者。这个时候消息会在多个消费者之间轮询。
用于把一条消息分发给多个队列的场景。
生产者把消息发送给Exchange交换机,Exchange交换机根据规则分发消息到不同的队列中。
所以,Exchange交换机必须要和接收消息的队列建立一个绑定关系,并且为每个队列指定一个特殊的标识。
Vhost虚拟机就是一个mini版的RabbitMQ服务器。Vhost虚拟机之于RabbitMQ就像虚拟机之于物理机。Vhost虚拟机提供如下功能:
RabbitMQ自定义配置文件名:
它的路径:
安装目录下的/etc/rabbitmq/
/etc/rabbitmq/
rabbitmq.conf文件是可能没有的,此时,需要我们自己创建
touch /etc/rabbitmq/rabbitmq.conf
rabbitmq.conf配置文件示例
https://github.com/rabbitmq/rabbitmq-server/blob/main/deps/rabbit/docs/rabbitmq.conf.example
rabbitmq.conf配置文件的配置项说明
https://www.rabbitmq.com/configure.html#config-items
RabbitMQ应用了很多端口,最常用的是5672和15672
5672:AMQP客户端端口,用于应用程序访问。
15672:HTTP_API端口,管理员用户才能访问,用于管理RbbitMQ,需要启用management插件。
# 启用Web管理插件
rabbitmq-plugins enable rabbitmq_management
#添加用户,用户名/密码:admin/admin
rabbitmqctl add_user admin admin
#为用户分配权限
rabbitmqctl set_user_tags admin administrator
#为用户分配资源权限
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
RabbitMQ 的用户角色分类:none、management、policymaker、monitoring、administrator
不能访问 management plugin,通常就是普通的生产者和消费者。
默认的用户guest就是administrator角色
根据前文可知,Exchange交换机有4种类型:Direct、Topic、Fanout、Headers。其中Headers一般不用。
生产者发送消息时会携带一个路由键(routing key)。当消息的路由键与某个队列的绑定键完全匹配时,这条消息才会从交换机路由到这个队列上。多个队列也可以使用相同的绑定键。
类似于sql中的=
精确查找。
直连类型的交换机,适用于一些业务用途明确的消息。比如 HR 系统跟销售系统之间通信,传输的是销售系统专用的消息,就可以建一个直连类型的交换机,使用明确的绑定键。
一个队列与主题类型的交换机绑定时,可以在绑定键中使用通配符。支持两个通配符:
#
:代表匹配0个或者多个单词*
:代表匹配有且只有一个单词单词:rabbitmq中,每个单词用符号.隔开。如xuyang.com.haha,就是3个单词。
如下图,
channel.basicPublish("MY_TOPIC_EXCHANGE","junior.abc.jvm","msg 2");
:只能被第1个队列接收。
channel.basicPublish("MY_TOPIC_EXCHANGE","python.java.v1","msg 2");
:能被第2,3个队列接收。
广播类型的交换机与队列绑定时,不需要指定绑定键。因此生产者发送消息到广播类型的交换机上,也不需要携带路由键。消息达到交换机时,所有与之绑定了的队列,都会收到相同的消息的副本。
Headers类型一般不用。
RabbitMQ的持久化分为:交换机持久化、队列持久化、消息持久化
交换机默认不是持久化的,在服务器重启之后,交换机会消失。
或者
channel.exchangeDeclare(EXCHANGE_NAME, "fanout", true);
与交换机的持久化相同,队列的持久化也是通过durable参数实现的。
或者用api
Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException;
第二个参数跟交换机方法的参数一样,true表示做持久化,当RabbitMQ服务重启时,队列依然存在。
这里说一下另外两个参数:
exclusive
:排他队列。如果一个队列被声明为排他队列,那么这个队列只能被第一次声明他的连接所见,并在连接断开的时候自动删除。这里有三点需要说明:
即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的,这种队列适用于一个客户端发送读取消息的应用场景
autoDelete
:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于临时队列。
消息的持久化是指当消息从交换机发送到队列之后,被消费者消费之前,服务器突然宕机重启,消息仍然存在。消息持久化的前提是队列持久化,假如队列不是持久化,那么消息的持久化毫无意义。
通过代码设置消息持久化
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
其中BasicProperties的类型如下,其中deliveryMode
是设置消息持久化的参数。等于1:非持久化,等于2:持久化。
public BasicProperties(
String contentType,
String contentEncoding,
Map<String,Object> headers,
Integer deliveryMode,
Integer priority,
String correlationId,
String replyTo,
String expiration,
String messageId,
Date timestamp,
String type,
String userId,
String appId,
String clusterId)
内存控制:是RabbitMQ控制内存使用量的方法。
rabbitmqctl set_vm_memory_high_watermark
其中,fraction 为内存阈值,默认是 0.4,表示 RabbitMQ 使用的内存超过系统内存的40%时,会产生内存告警,通过此命令修改的阈值在重启后会失效。
# rabbitmq.conf
vm_memory_high_watermark.relative=0.4
#vm_memory_high_watermark.absolute=1GB
RabbitMQ 提供 relative 与 absolute 两种配置方式。
rabbitmqctl set_vm_memory_high_watermark
#rabbitmqctl set_vm_memory_high_watermark 0.4
rabbitmqctl set_vm_memory_high_watermark absolute
#rabbitmqctl set_vm_memory_high_watermark absolute 1GB
在 RabbitMQ 达到内存阈值并阻塞生产者之前,RabbitMQ会把部分数据转移到磁盘,以释放内存空间,这就是内存换页。那么RabbitMQ 什么时候触发数据转移呢?
有个换页参数,默认为 0.5,表示当内存使用量达到内存阈值的 50%时会进行换页,也就是当内存使用量达到 0.4*0.5=0.2时,触发内存换页。
vm_memory_high_watermark_paging_ratio=0.5
除了内存的使用量需要控制外,磁盘的使用量也需要控制。RabbitMQ 通过磁盘阈值参数控制磁盘的使用量,当磁盘剩余空间小于磁盘阈值时,RabbitMQ 同样会阻塞生产者,避免磁盘空间耗尽。
rabbitmqctl set_disk_free_limit
rabbitmqctl set_disk_free_limit mem_relative
# limit 为绝对值, KB、 MB、 GB
# fraction 为相对值,建议 1.0~2.0 之间,这里的值是相对机器内存空间设置的值。1.0表示磁盘空闲空间限制设置为内存空间 * 1的大小。
配置文件
# rabbitmq.conf
disk_free_limit.relative=1.5
# disk_free_limit.absolute=50MB
插件前面[ ] 为空说明,没有安装。有 e*说明插件是安装了的。
rabbitmq-plugins list
rabbitmq-plugins enable rabbitmq_management
rabbitmq-plugins disable rabbitmq_management
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>5.6.0version>
dependency>
通过Java API使用RabbitMQ的方式与JDBC类似,定义消费者有以下步骤:
public class XYConsumer {
private final String EXCHANGE_NAME = "XY_EXCHANGE";
private final String QUEUE_NAME = "XY_QUEUE";
public Channel create() throws IOException, TimeoutException {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 2. 设置连接IP
factory.setHost("192.168.200.16");
// 3. 设置监听端口
factory.setPort(5672);
// 4. 设置vhost虚拟机
factory.setVirtualHost("/");
// 5. 设置访问的用户名和密码
factory.setUsername("admin");
factory.setPassword("admin");
// 6. 创建连接对象
Connection conn = factory.newConnection();
// 7. 创建消息通道
Channel channel = conn.createChannel();
// 8. 声明交换机
// DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map arguments)
channel.exchangeDeclare(EXCHANGE_NAME, "direct", false, false, null);
// 9. 声明队列
// queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments)
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 10. 绑定交换机和队列
// BindOk queueBind(String queue, String exchange, String routingKey)
String routingKey = "xyRoutingKey";
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, routingKey);
// 11. 创建消费者对象,并定义接收到消息的回调
Consumer consumer = new DefaultConsumer(channel) {
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 + "'");
System.out.println("consumerTag : " + consumerTag);
System.out.println("deliveryTag : " + envelope.getDeliveryTag());
}
};
// 12. 获取消息
// String queue, boolean autoAck, Consumer callback
channel.basicConsume(QUEUE_NAME, true, consumer);
return null;
}
public static void main(String[] args) throws IOException, TimeoutException {
XYConsumer xyConsumer = new XYConsumer();
xyConsumer.create();
}
}
定义生产者有以下步骤:
package com.learn;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 1. 创建连接工厂
* 2. 设置连接IP
* 3. 设置监听端口
* 4. 设置vhost虚拟机
* 5. 设置访问的用户名和密码
* 6. 创建连接对象
* 7. 创建消息通道
* 8. 发送消息
*/
public class XYProducer {
private final String EXCHANGE_NAME = "XY_EXCHANGE";
private final String QUEUE_NAME = "XY_QUEUE";
private final String routingKey = "xyRoutingKey";
public void create() {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 2. 设置连接IP
factory.setHost("192.168.200.16");
// 3. 设置监听端口
factory.setPort(5672);
// 4. 设置vhost虚拟机
factory.setVirtualHost("/");
// 5. 设置访问的用户名和密码
factory.setUsername("admin");
factory.setPassword("admin");
// 6. 创建连接对象
Connection conn = null;
Channel channel = null;
try {
conn = factory.newConnection();
// 7. 创建消息通道
channel = conn.createChannel();
// 8. 发送消息
String msg = "hello world";
channel.basicPublish(EXCHANGE_NAME, routingKey, null, msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
if (null != channel && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
if (null != conn && conn.isOpen()) {
try {
conn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
XYProducer xyProducer = new XYProducer();
xyProducer.create();
}
}
工程名:p003002-rabbitmq-springboot
略
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
spring:
rabbitmq:
# mq的地址
host: 192.168.200.16
port: 5672
username: admin
password: admin
# vhost虚拟主机
virtual-host: /
package com.learn.rabbitmqapp.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitmqConfig {
public final static String EXCHANGE_DIRECT_QQ = "exchange_direct_qq";
public final static String EXCHANGE_TOPIC_WECHAT = "exchange_topic_wechat";
public final static String EXCHANGE_FANOUT = "exchange_fanout";
public final static String QUEUE_QQ = "queue_qq";
public final static String QUEUE_WECHAT = "queue_wechat";
public final static String ROUTINGKEY_QQ_DIRECT = "routingkey_qq_direct";
public final static String ROUTINGKEY_WECHAT_TOPIC = "routingkey.*.wechat.#.topic";
/**
* exchange,交换机
*/
@Bean("directExchange")
public Exchange directExchange() {
return ExchangeBuilder.directExchange(EXCHANGE_DIRECT_QQ).durable(true).build();
}
@Bean("topicExchange")
public Exchange topicExchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_TOPIC_WECHAT).durable(true).build();
}
@Bean("fanoutExchange")
public Exchange fanoutExchange() {
return ExchangeBuilder.fanoutExchange(EXCHANGE_FANOUT).durable(true).build();
}
/**
* queue,队列
*/
@Bean("qqQueue")
public Queue qqQueue() {
return new Queue(QUEUE_QQ, true, false, false, null);
}
@Bean("wechatQueue")
public Queue wechatQueue() {
return new Queue(QUEUE_WECHAT, true, false, false, null);
}
/**
* binding,绑定交换机和队列
*/
@Bean("bindingQQQueueToDirectExchange")
public Binding bindingQQQueueToDirectExchange(@Qualifier("directExchange") Exchange exchange, @Qualifier("qqQueue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_QQ_DIRECT).noargs();
}
@Bean("bindingWechatQueueToTopicExchange")
public Binding bindingWechatQueueToTopicExchange(@Qualifier("topicExchange") Exchange exchange, @Qualifier("wechatQueue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_WECHAT_TOPIC).noargs();
}
@Bean("bindingQQQueueToFanoutExchange")
public Binding bindingQQQueueToFanoutExchange(@Qualifier("fanoutExchange") Exchange exchange, @Qualifier("qqQueue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with(EXCHANGE_FANOUT).noargs();
}
@Bean("bindingWechatQueueToFanoutExchange")
public Binding bindingWechatQueueToFanoutExchange(@Qualifier("fanoutExchange") Exchange exchange, @Qualifier("wechatQueue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with(EXCHANGE_FANOUT).noargs();
}
}
package com.learn.rabbitmqapp.consumer;
import com.learn.rabbitmqapp.config.RabbitmqConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class MessageConsumer {
@RabbitListener(queues = RabbitmqConfig.QUEUE_QQ)
public void qqMsgHandler(Object obj, Message message, Channel channel) {
String msg = new String(message.getBody());
System.out.println("RabbitmqConfig.QUEUE_QQ msg: " + msg);
}
@RabbitListener(queues = RabbitmqConfig.QUEUE_WECHAT)
public void wechatMsgHandler(Object obj, Message message, Channel channel) {
String msg = new String(message.getBody());
System.out.println("RabbitmqConfig.QUEUE_WECHAT msg: " + msg);
}
}
package com.learn.rabbitmqapp.producer;
import com.learn.rabbitmqapp.config.RabbitmqConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MessageProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendDirect() {
/**
* 参数:
* 1、交换机名称
* 2、routingKey
* 3、消息内容
*/
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_DIRECT_QQ, RabbitmqConfig.ROUTINGKEY_QQ_DIRECT, "xy msg");
}
public void sendTopic() {
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_TOPIC_WECHAT, "routingkey.xy.wechat.info.topic", "wechat msg");
}
public void sendFanout() {
//fanout,不用写routingkey
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_FANOUT, "", "fanout msg");
}
}
《咕泡云课堂》
官网
https://www.rabbitmq.com/#getstarted
https://www.cnblogs.com/x-poior/p/6380064.html
https://www.jianshu.com/p/84b3e5d9f8f8#:~:text=RabbitMQ%E6%9C%AC%E8%BA%AB%E5%B8%A6%E6%9C%89%E6%8C%81%E4%B9%85%E5%8C%96%E6%9C%BA%E5%88%B6%EF%BC%8C%E5%8C%85%E6%8B%AC%E4%BA%A4%E6%8D%A2%E6%9C%BA%E3%80%81%E9%98%9F%E5%88%97%E4%BB%A5%E5%8F%8A%E6%B6%88%E6%81%AF%E7%9A%84%E6%8C%81%E4%B9%85%E5%8C%96%E3%80%82,%E6%8C%81%E4%B9%85%E5%8C%96%E7%9A%84%E4%B8%BB%E8%A6%81%E6%9C%BA%E5%88%B6%E5%B0%B1%E6%98%AF%E5%B0%86%E4%BF%A1%E6%81%AF%E5%86%99%E5%85%A5%E7%A3%81%E7%9B%98%EF%BC%8C%E5%BD%93RabbtiMQ%E6%9C%8D%E5%8A%A1%E5%AE%95%E6%9C%BA%E9%87%8D%E5%90%AF%E5%90%8E%EF%BC%8C%E4%BB%8E%E7%A3%81%E7%9B%98%E4%B8%AD%E8%AF%BB%E5%8F%96%E5%AD%98%E5%85%A5%E7%9A%84%E6%8C%81%E4%B9%85%E5%8C%96%E4%BF%A1%E6%81%AF%EF%BC%8C%E6%81%A2%E5%A4%8D%E6%95%B0%E6%8D%AE%E3%80%82%20%E9%BB%98%E8%AE%A4%E4%B8%8D%E6%98%AF%E6%8C%81%E4%B9%85%E5%8C%96%E7%9A%84%EF%BC%8C%E5%9C%A8%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%87%8D%E5%90%AF%E4%B9%8B%E5%90%8E%EF%BC%8C%E4%BA%A4%E6%8D%A2%E6%9C%BA%E4%BC%9A%E6%B6%88%E5%A4%B1%E3%80%82
https://blog.csdn.net/huzecom/article/details/103578310
https://blog.csdn.net/zyz511919766/article/details/42292655
https://github.com/rabbitmq/rabbitmq-server/blob/main/deps/rabbit/docs/rabbitmq.conf.example
https://www.rabbitmq.com/configure.html#config-items