一、一些概念
1. 消息队列的作用
- 解耦
- 异步
- 削峰
2.使用了什么协议
- 常见的消息中间件协议:OpenWire、AMQP、MQTT、Kafka、OpenMessage协议
- 基于TCP/IP协议之上的协议
- 为什么不用HTTP协议:
- 因为http的请求报文头和响应报文头是比较复杂的,包含了cookie、数据加解密、状态码、响应码等附加功能,对于一个消息而言并不需要这么复杂,它就只是负责数据的传递、存储、分发,追求的是高性能,应尽量简洁和快速
- 大部分情况下http都是短链接,在实际的交互过程重,一个请求到响应的过程很有可能中断,中断以后就不会进行持久化,就会造成请求的丢失,这就不利于消息中间件的应用场景,因为消息中间件肯是一个长期的获取消息的过程,出现故障和问题要对消息进行持久化等,目的是为了消息和数据的高可靠和稳健运行
3. 消息持久化
ActiveMQ | RabbitMQ | Kafka | RockMQ | |
---|---|---|---|---|
文件存储 | 支持 | 支持 | 支持 | 支持 |
数据库 | 支持 | / | / | / |
4. 分发策略
- 发布订阅:有订阅的都能收到消息
- 轮询分发:平均分发
- 公平分发:能者多劳,谁先干完先给谁发
- 重发:消息无应答后重发给其他消费者
二、RabbitMQ入门及安装
1. rpm安装
- 下载安装erlang环境
- yum install -y socat
- 下载安装RabbitMQ
- rabbitmq-plugins enable management 开启前端管理页面默认端口15672,账号密码 guest guest 只能在本机访问
- 新增用户命令: rabbitmqctl add_user admin admin
- 分配权限:rabbitmqctl set_user_tags admin administrator
- administrator:最高级权限可以登录控制台,查看所有信息,管理RabbitMQ
- monitoring:监控者,可以登录查看所有信息
- policymaker:策略制定者,登录控制台配置策略
- management:普通管理员,登录控制台
- 资源权限授权:rabbitmqctl set_permission -p admin “." ".” “.*”
2. Docker安装
docker run -di --name myrabbit -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=user123 -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management
3. Demo
- 创建一个Maven项目
- 引入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.10.0</version>
</dependency>
</dependencies>
- Producer示例代码
package com.ambition.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("你的IP");
connectionFactory.setPort(5672);
connectionFactory.setUsername("你设置的用户");
connectionFactory.setPassword("你设置的密码");
Connection connection = null;
Channel channel = null;
try {
// 2. 创建连接
connection = connectionFactory.newConnection("生产者");
// 3. 通过连接获取channel
channel = connection.createChannel();
// 4. 声明队列
String queueName = "queue3";
/*
* @params1 队列名称
* @params2 是否持久化,随着服务器重启,队列是否还存在,非持久化队列里的消息依旧会存盘,但是随着服务重启消息也会丢失
* @params3 排他性,是否独占队列
* @params4 是否自动删除,当队列里最后一个消息被消费后是否删除队列
* @params5 携带参数
* */
channel.queueDeclare(queueName,false,false,false,null);
// 5. 准备消息
String msg = "Hello Wrold!";
// 6. 发送消息
channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 7. 关闭通道
if (null != channel && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
if (null != connection && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- Consumer示例代码
package com.ambition.rabbitmq.simple;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("你的IP");
connectionFactory.setPort(5672);
connectionFactory.setUsername("你设置的用户");
connectionFactory.setPassword("你设置的密码");
Connection connection = null;
Channel channel = null;
try {
// 2. 创建连接
connection = connectionFactory.newConnection("消费者");
// 3. 通过连接获取channel
channel = connection.createChannel();
// 4. 声明队列
String queueName = "queue3";
channel.basicConsume(queueName, true, new DeliverCallback() {
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("收到的消息是:" + new String(delivery.getBody(), "UTF-8"));
}
}, new CancelCallback() {
public void handle(String s) throws IOException {
System.out.println("接收消息失败了……");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 7. 关闭通道
if (null != channel && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
if (null != connection && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 消息一定是先到exchange(交换机),exchange再根据路由键投递给对应的queue,而不是直接塞到queue,queue没有指定exchange就会绑定到默认的那一个
Default exchange
4. RabbitMQ 支持消息的模式
- Publish/Subscribe:消息发送绑定本exchange的所有queue
package com.ambition.rabbitmq.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("你的IP");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("你设置的用户");
connectionFactory.setPassword("你设置的密码");
Connection connection = null;
Channel channel = null;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4. 通过连接获取channel
channel = connection.createChannel();
// 5. 准备消息
String msg = "Hello Wrold!";
// 6. 准备交换机
String exchangeName = "fanout-exchange";
// 7. 定义routing key
String routingKey = "";
// 8. 指定交换机类型
String type = "fanout";
// 6. 发送消息
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 7. 关闭通道
if (null != channel && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
if (null != connection && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- Routing:消息发送绑定本exchange的并且指定的RoutingKey的queue
package com.ambition.rabbitmq.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("你的IP");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("你设置的用户");
connectionFactory.setPassword("你设置的密码");
Connection connection = null;
Channel channel = null;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4. 通过连接获取channel
channel = connection.createChannel();
// 5. 准备消息
String msg = "Hello Direct!";
// 6. 准备交换机
String exchangeName = "direct-exchange";
// 7. 定义routing key
String routingKey = "email";
// 8. 指定交换机类型
String type = "direct";
// 6. 发送消息
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 7. 关闭通道
if (null != channel && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
if (null != connection && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- Topics:模糊匹配
- #.:可以没有也可以有一个或多个
- *.:只能有且必须有一个
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4. 通过连接获取channel
channel = connection.createChannel();
// 5. 准备消息
String msg = "Hello Topic!";
// 6. 准备交换机
String exchangeName = "topics-exchange";
// 7. 定义routing key
String routingKey = "com.course.order";
// 8. 指定交换机类型
String type = "topic";
// 6. 发送消息
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
- 代码创建交换机、队列,并绑定关系
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4. 通过连接获取channel
channel = connection.createChannel();
// 5. 准备消息
String msg = "Hello direct!";
// 6. 准备交换机
String exchangeName = "direct_message_exchange";
String exchangeType = "direct";
// 交换机可以在管理页面进行创建和绑定,也可以通过代码创建
// param3是否持久化,指的是交换机会不会随着服务器重启而丢失
channel.exchangeDeclare(exchangeName,exchangeType,true);
// 7. 声明队列
channel.queueDeclare("queue5",true,false,false,null);
channel.queueDeclare("queue6",true,false,false,null);
channel.queueDeclare("queue7",true,false,false,null);
// 8. 绑定交换机和队列
channel.queueBind("queue5",exchangeName,"order");
channel.queueBind("queue6",exchangeName,"order");
channel.queueBind("queue7",exchangeName,"course");
// 9. 发送消息
channel.basicPublish(exchangeName, "order", null, msg.getBytes());
- Work queues
- 默认轮询:例如代码Producer向一个队列发送20条消息,同时有Work1和Work2两个消费者,默认情况下,MQ会平均地向Work1和Work2推送消息,Work1发一条->Work2发一条 如此轮询
- 公平分发:在代码设置手动应答的情况下,假如设置Qos=1,那么就是MQ推送给Work1一条消息,Work1消费完应答后立马获取下一条消息,Work2同理,但是当Work1处理消息速递比Work2快时,此时就不再是轮询地把1357给Work1而2468给Work2,而是Work1处理完立马推送下一条消息就可能是134567
//生产者发送20条消息
// 5. 准备消息
for (int i = 0; i < 20; i++) {
String msg = "Hello ," + i;
// 6. 发送消息
channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
}
// 默认情况下就是轮询分发
// 4. 声明队列
String queueName = "queue";
// Qos ,每次取多少条消息
finalChannel.basicQos(1);
// param2 是否自动应答
finalChannel.basicConsume(queueName, false, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("Work1收到的消息是:" + new String(delivery.getBody(), "UTF-8"));
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}
开始接收消息
Worker1收到的消息是:Hello ,0
Worker1收到的消息是:Hello ,2
Worker1收到的消息是:Hello ,4
Worker1收到的消息是:Hello ,6
Worker1收到的消息是:Hello ,8
Worker1收到的消息是:Hello ,10
Worker1收到的消息是:Hello ,12
Worker1收到的消息是:Hello ,14
Worker1收到的消息是:Hello ,16
Worker1收到的消息是:Hello ,18
开始接收消息
Worker2收到的消息是:Hello ,1
Worker2收到的消息是:Hello ,3
Worker2收到的消息是:Hello ,5
Worker2收到的消息是:Hello ,7
Worker2收到的消息是:Hello ,9
Worker2收到的消息是:Hello ,11
Worker2收到的消息是:Hello ,13
Worker2收到的消息是:Hello ,15
Worker2收到的消息是:Hello ,17
Worker2收到的消息是:Hello ,19
// 使用公平调度,手动应答,设置basicQos mq每次分配消息的个数,可以根据服务器性能情况等因素灵活设置
// 4. 声明队列
String queueName = "queue";
finalChannel.basicQos(1);
// param2 是否自动应答
finalChannel.basicConsume(queueName, false, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("Work1收到的消息是:" + new String(delivery.getBody(), "UTF-8"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}
开始接收消息
Work1收到的消息是:Hello ,0
Work1收到的消息是:Hello ,6
Work1收到的消息是:Hello ,11
Work1收到的消息是:Hello ,17
开始接收消息
Work2收到的消息是:Hello ,1
Work2收到的消息是:Hello ,2
Work2收到的消息是:Hello ,3
Work2收到的消息是:Hello ,4
Work2收到的消息是:Hello ,5
Work2收到的消息是:Hello ,7
Work2收到的消息是:Hello ,8
Work2收到的消息是:Hello ,9
Work2收到的消息是:Hello ,10
Work2收到的消息是:Hello ,12
Work2收到的消息是:Hello ,13
Work2收到的消息是:Hello ,14
Work2收到的消息是:Hello ,15
Work2收到的消息是:Hello ,16
Work2收到的消息是:Hello ,18
Work2收到的消息是:Hello ,19
三、Springboot 整合RabbitMQ
1. 创建一个spring boot工程,包依赖引入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
2. 配置rabbitmq连接信息
server:
port: 8081
spring:
rabbitmq:
username: 你的用户名
password: 你的密码
host: 你的服务器地址
port: 5672
virtual-host: /
3. fanout类型
(1)配置交换机、队列、绑定关系
package cn.ambition.rabbbitmq.producer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfiguration {
/**
* 创建交换机,通过使用对应的类 指定交换机类型
* @return
*/
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("fanout_order_exchange",true,false);
}
/**
* 创建队列
* @return
*/
@Bean
public Queue smsQueue() {
return new Queue("sms.fanout.queue",true);
}
@Bean
public Queue emailQueue() {
return new Queue("email.fanout.queue",true);
}
@Bean
public Queue wechatQueue() {
return new Queue("wechat.fanout.queue",true);
}
/**
* 创建绑定关系
* @param fanoutExchange
* @param smsQueue
* @return
*/
@Bean
public Binding smsBinding(FanoutExchange fanoutExchange, Queue smsQueue) {
return BindingBuilder.bind(smsQueue).to(fanoutExchange);
}
@Bean
public Binding emailBinding(FanoutExchange fanoutExchange, Queue emailQueue) {
return BindingBuilder.bind(emailQueue).to(fanoutExchange);
}
@Bean
public Binding wechatBinding(FanoutExchange fanoutExchange, Queue wechatQueue) {
return BindingBuilder.bind(wechatQueue).to(fanoutExchange);
}
}
(2)发送消息
package cn.ambition.rabbbitmq.producer.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void makeOrder(String userId, String productId, Integer num) {
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功:" + orderId);
String exchangeName = "fanout_order_exchange";
String routingKey = "";
rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
}
}
(3)消费消息
- 使用
@RabbitListener(queues = {""})
指定要消费的队列 - 使用
@RabbitHandler
注解指定处理消息的方法
package cn.ambition.rabbbitmq.consumer.service.fanout;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = {"email.fanout.queue"})
public class FanoutEmailConsumer {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("FanoutEmailConsumer 收到消息:" + msg);
}
}
3. direct类型
(1)配置交换机、队列、绑定关系
package cn.ambition.rabbbitmq.producer.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DirectRabbitMQConfiguration {
/**
* 创建交换机,通过使用对应的类 指定交换机类型
* @return
*/
@Bean
public DirectExchange directExchange() {
return new DirectExchange("direct_order_exchange",true,false);
}
/**
* 创建队列
* @return
*/
@Bean
public Queue directSmsQueue() {
return new Queue("sms.direct.queue",true);
}
@Bean
public Queue directEmailQueue() {
return new Queue("email.direct.queue",true);
}
@Bean
public Queue directWechatQueue() {
return new Queue("wechat.direct.queue",true);
}
/**
* 创建绑定关系
* @param directExchange
* @param directSmsQueue
* @return
*/
@Bean
public Binding directSmsBinding(DirectExchange directExchange, Queue directSmsQueue) {
return BindingBuilder.bind(directSmsQueue).to(directExchange).with("sms");
}
@Bean
public Binding directEmailBinding(DirectExchange directExchange, Queue directEmailQueue) {
return BindingBuilder.bind(directEmailQueue).to(directExchange).with("email");
}
@Bean
public Binding directWechatBinding(DirectExchange directExchange, Queue directWechatQueue) {
return BindingBuilder.bind(directWechatQueue).to(directExchange).with("wechat");
}
}
(2)发送消息
public void makeOrderDirect(String userId, String productId, Integer num) {
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功:" + orderId);
String exchangeName = "direct_order_exchange";
String routingKey = "";
rabbitTemplate.convertAndSend(exchangeName,"sms",orderId);
rabbitTemplate.convertAndSend(exchangeName,"wechat",orderId);
}
(3)消费消息
package cn.ambition.rabbbitmq.consumer.service.direct;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = {"email.direct.queue"})
public class DirectEmailConsumer {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("DirectEmailConsumer 收到消息:" + msg);
}
}
4. topic类型,使用注解创建绑定交换机队列
package cn.ambition.rabbbitmq.consumer.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "email.topic.queue",durable = "true",autoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange",type = ExchangeTypes.TOPIC),
key = "#.email.#"))
public class TopicEmailConsumer {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("TopicEmailConsumer 收到消息:" + msg);
}
}
四、RabbitMQ高级
1. 设置消息过期时间
(1)在queue的参数列表中设置,针对所有进队列的消息
@Bean
public Queue ttlQueue() {
Map<String,Object> params = new HashMap<>();
params.put("x-message-ttl",5000);//单位是毫秒
return new Queue("ttl.direct.queue",true,false,false,params);
}
(2)在消息发送时设置过期时间
public void makeTtlMsg(String userId, String productId, Integer num) {
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功:" + orderId);
String exchangeName = "ttl_direct_exchange";
String routingKey = "ttl-msg";
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("5000");
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId,messagePostProcessor);
}
当上述两种方法同时存在时,即同时设置了队列的过期时间又设置了消息的过期时间,此时以最短的时间为准
2. 死信队列
DLX,全程dead-letter-exchange,可以称之为死信交换机,当消息在一个队列中变成死信之后,他能被重新发送到另一个交换机中,这个交换机就是DLX,绑定DLX的队列就是死信队列。
消息变成死信,可能有一下几个原因:
- 消息被拒绝
- 消息过期
- 队列达到最大长度
- 创建一个死信队列
package cn.ambition.rabbbitmq.producer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DeadRabbitMqConfiguration {
@Bean
public DirectExchange deadExchange() {
return new DirectExchange("dead_direct_exchange",true,false);
}
@Bean
public Queue deadQueue() {
return new Queue("dead.direct.queue",true);
}
@Bean
public Binding deadBinding(DirectExchange deadExchange, Queue deadQueue) {
return BindingBuilder.bind(deadQueue).to(deadExchange).with("dead");
}
}
- 常规队列绑定死信交换机
@Bean
public Queue ttlQueue() {
Map<String,Object> params = new HashMap<>();
params.put("x-message-ttl",5000);
// 绑定死信交换机
params.put("x-dead-letter-exchange","dead_direct_exchange");
//fanout不用配
params.put("x-dead-letter-routing-key","dead");
return new Queue("ttl.direct.queue",true,false,false,params);
}
发现消息进入队列 ttl.direct.queue
5s后消息过期,被重新发送到了 dead.direct.queue
3. RabbitMQ的内存控制
当内存使用率达到一定的阈值时报警,并且不再接收新的消息
3.1 内存预警(二者选其一,命令的方式:重启后失效,配置文件:一直有效)
- 设置相对值:默认值是 0.4,建议一般配置为0.6,可以0.4-0.7之间
rabbitmqctl set_vm_memory_high_watermark <fraction>
- 设置绝对值
rabbitmqctl set_vm_memory_high_watermark absolute 2GB
3.2 磁盘预警:当服务器磁盘空间小于指定数值时磁盘预警
rabbitmqctl set_disk_free_limit 50MB
3.3 内存换页
在某个broker节点内存阻塞生产者之前,他会尝试将内存中的消息换页到磁盘中以释放内存空间,持久化和非持久化的消息都会写入磁盘中,其中持久化的消息在磁盘中就有一个副本,所以在转移的过程中持久化的消息会优先从内存中清除掉。
例如:服务器内存1000MB,当内存使用了400MB达到了预警线,但是因为配置了内存换页 0.5,这时会在达到400MB极限之前,把内存中的200MB内容转移到磁盘中,从而达到稳健运行的目的
vm_memory_high_watermark_paging_ratio = 0.5 # 设置值肯定小于1
五、RabbitMQ集群
1. Docker搭建RabbitMQ集群
默认模式下主节点挂掉,所有节点都会强制下线无法使用
- 创建节点rabbit-1,因为在一台服务器启动了两个节点,因此使用
--net mynet
加入自定义的docker网络
docker run -d --name rabbit-1 -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=user123 -e RABBITMQ_ERLANG_COOKIE='rabbit_mq' -p 15672:15672 -p 5672:5672 --net mynet rabbitmq:management
- 创建节点rabbit-2
docker run -d --name rabbit-2 -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=user123 -e RABBITMQ_ERLANG_COOKIE='rabbit_mq' -p 15673:15672 -p 5673:5672 --net mynet rabbitmq:management
- 进入rabbit-1
docker exec -it rabbit-1 /bin/bash
# 停止服务
rabbitmqctl stop_app
# 清除历史数据,如果不清楚可能会无法加入集群
rabbitmqctl reset
# 重新启动
rabbitmqctl start_app
- 进入rabbit-2
docker exec -it rabbit-2 /bin/bash
# 停止服务
rabbitmqctl stop_app
# 清除历史数据,如果不清楚可能会无法加入集群
rabbitmqctl reset
# 将rabbit-2 加入 rabbit-1(主节点)的集群中,-n 指定节点 节点名称@主机名称
rabbitmqctl -n rabbit@3d7211e507ee join_cluster rabbit@4907b0882674
# 启动服务
rabbitmqctl start_app
# 查看集群节点信息
rabbitmqctl cluster_status -n rabbit