RabbitMQ使用场景
broker
英
/ˈbrəʊkə(r)
n.
经纪人,中间人
v.
协调,安排
- 队列在 消费者声明是最好的。
- 因为:发送方,没有队列不会报错。
- 消费者:没有队列,项目会启动失败。
01、解耦、削峰、异步
01-1、同步异步的问题(串行)
串行方式:将订单信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端
代码
public void makeOrder(){
// 1 :保存订单
orderService.saveOrder();
// 2: 发送短信服务
messageService.sendSMS("order");//1-2 s
// 3: 发送email服务
emailService.sendEmail("order");//1-2 s
// 4: 发送APP服务
appService.sendApp("order");
}
01-2、并行方式 异步线程池
并行方式:将订单信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
代码
public void makeOrder(){
// 1 :保存订单
orderService.saveOrder();
// 相关发送
relationMessage();
}
public void relationMessage(){
// 异步
theadpool.submit(new Callable<Object>{
public Object call(){
// 2: 发送短信服务
messageService.sendSMS("order");
}
})
// 异步
theadpool.submit(new Callable<Object>{
public Object call(){
// 3: 发送email服务
emailService.sendEmail("order");
}
})
}
存在问题:
1:耦合度高
2:需要自己写线程池自己维护成本太高
3:出现了消息可能会丢失,需要你自己做消息补偿
4:如何保证消息的可靠性你自己写
5:如果服务器承载不了,你需要自己去写高可用
01-2、异步消息队列的方式
好处
1:完全解耦,用MQ建立桥接
2:有独立的线程池和运行模型
3:出现了消息可能会丢失,MQ有持久化功能
4:如何保证消息的可靠性,死信队列和消息转移的等
5:如果服务器承载不了,你需要自己去写高可用,HA镜像模型高可用。
按照以上约定,用户的响应时间相当于是订单信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍
代码
public void makeOrder(){
// 1 :保存订单
orderService.saveOrder();
rabbitTemplate.convertSend("ex","2","消息内容");
}
02、高内聚,低耦合
03、流量的削峰
04、分布式事务的可靠消费和可靠生产
05、索引、缓存、静态化处理的数据同步
06、流量监控
07、日志监控(ELK)
08、下单、订单分发、抢票
RabbitMQ-SpringBoot案例 -fanout模式
整体核心
01、目标
使用springboot完成rabbitmq的消费模式-Fanout
02、实现步骤
1:创建生产者工程:sspringboot-rabbitmq-fanout-producer
2:创建消费者工程:springboot-rabbitmq-fanout-consumer
3:引入spring-boot-rabbitmq的依赖
4:进行消息的分发和测试
5:查看和观察web控制台的状况
具体实现
03、生产者
1、创建生产者工程:
sspringboot-rabbitmq-fanout-producer
spring-boot-starter-parent 3.0.1 使用的是:
spring-rabbit 3.0.0
spring-amqp 3.0.0
amqp-client 5.16.9
boot 2.4.3
spring-rabbit 2.3.5
spring-amqp 2.3.5
amqp-client 5.10.0
2、在pom.xml中引入依赖
<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>
3、在application.yml进行配置
# 服务端口
server:
port: 8080
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 47.104.141.27
port: 5672
4:定义订单的生产者
RabbitTemplate //注入
//转换 和 发送
rabbitTemplate.convertAndSend(exchangeName, routeKey, orderNumer);
@Service
public class MyTestOderService {
@Resource
private RabbitTemplate rabbitTemplate;
public void createOrder() {
String content = UUID.randomUUID().toString();
rabbitTemplate.convertAndSend("fanout_order_exchange","",content);
}
}
package com.xuexiangban.rabbitmq.springbootrabbitmqfanoutproducer.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.UUID;
/**
* @author: 学相伴-飞哥
* @description: OrderService
* @Date : 2021/3/4
*/
@Component
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
// 1: 定义交换机
private String exchangeName = "fanout_order_exchange";
// 2: 路由key
private String routeKey = "";
public void makeOrder(Long userId, Long productId, int num) {
// 1: 模拟用户下单
String orderNumer = UUID.randomUUID().toString();
// 2: 根据商品id productId 去查询商品的库存
// int numstore = productSerivce.getProductNum(productId);
// 3:判断库存是否充足
// if(num > numstore ){ return "商品库存不足..."; }
// 4: 下单逻辑
// orderService.saveOrder(order);
// 5: 下单成功要扣减库存
// 6: 下单完成以后
System.out.println("用户 " + userId + ",订单编号是:" + orderNumer);
// 发送订单信息给RabbitMQ fanout。交换机,路由key,内容。
rabbitTemplate.convertAndSend(exchangeName, routeKey, orderNumer);
}
}
5、绑定关系
@Configuration
public class RabbitConfig {
@Bean
public Queue emailQuequ() {
//true 持久化
return new Queue("email.fanout.queue", true);
}
//群发 订阅者发布者模式为:Fanout
@Bean
public FanoutExchange fanoutExchange() {
//持久化,不自动删除
return new FanoutExchange("email.fanout.queue", true, false);
}
//第一个绑定。参数为:队列 to 交换机
@Bean
public Binding binding1() {
return BindingBuilder.bind(emailQuequ()).to(fanoutExchange());
}
}
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;
/**
* @Author : JCccc
* @CreateTime : 2019/9/3
* @Description :
**/
@Configuration
public class DirectRabbitConfig {
//队列 起名:TestDirectQueue
@Bean
public Queue emailQueue() {
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
// return new Queue("TestDirectQueue",true,true,false);
//一般设置一下队列的持久化就好,其余两个就是默认false
return new Queue("email.fanout.queue", true);
}
@Bean
public Queue smsQueue() {
return new Queue("sms.fanout.queue", true);
}
@Bean
public Queue weixinQueue() {
return new Queue("weixin.fanout.queue", true);
}
//Direct交换机 起名:TestDirectExchange
/*@Bean
public DirectExchange fanoutOrderExchange() {
// return new DirectExchange("TestDirectExchange",true,true);
return new DirectExchange("fanout_order_exchange", true, false);
}*/
@Bean
public FanoutExchange fanoutOrderExchange() {
// return new DirectExchange("TestDirectExchange",true,true);
return new FanoutExchange("fanout_order_exchange", true, false);
}
//绑定 将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting
@Bean
public Binding bindingDirect1() {
return BindingBuilder.bind(weixinQueue()).to(fanoutOrderExchange());//.with("") 使用Fanout模式(发布订阅)没用routing key
}
@Bean
public Binding bindingDirect2() {
return BindingBuilder.bind(smsQueue()).to(fanoutOrderExchange());
}
@Bean
public Binding bindingDirect3() {
return BindingBuilder.bind(emailQueue()).to(fanoutOrderExchange());
}
}
6、进行测试
- 我的使用 boot3.0,报错,改为了2.4.3,可以用了。
@SpringBootTest
class SpringbootRabbitmqFanoutProducerApplicationTests {
@Autowired
OrderService orderService;
@Test
public void contextLoads() throws Exception {
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
Long userId = 100L + i;
Long productId = 10001L + i;
int num = 10;
orderService.makeOrder(userId, productId, num);
}
}
}
04、定义消费者
1、创建消费者工程:springboot-rabbitmq-fanout-consumer
2、引入依赖pom.xml
- 一样
3、在application.yml进行配置
- 一样
4、消费者 - 邮件服务
@Component
@RabbitListener(queues = {"email.fanout.queue"})
public class FanoutEmailConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("email收到了消息" + message);
}
}
package com.xuexiangban.rabbitmq.springbootrabbitmqfanoutconsumer.consumer;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;
// bindings其实就是用来确定队列和交换机绑定关系
@RabbitListener(bindings =@QueueBinding(
// email.fanout.queue 是队列名字,这个名字你可以自定随便定义。
value = @Queue(value = "email.fanout.queue",autoDelete = "false"),
// order.fanout 交换机的名字 必须和生产者保持一致
exchange = @Exchange(value = "fanout_order_exchange",
// 这里是确定的rabbitmq模式是:fanout 是以广播模式 、 发布订阅模式
type = ExchangeTypes.FANOUT)
))
@Component
public class EmailService {
// @RabbitHandler 代表此方法是一个消息接收的方法。该不要有返回值
@RabbitHandler
public void messagerevice(String message){
// 此处省略发邮件的逻辑
System.out.println("email-------------->" + message);
}
}
@RabbitListener(bindings =
@QueueBinding(
// email.fanout.queue 是队列名字,这个名字你可以自定随便定义。
value = @Queue(value = "email.fanout.queue",autoDelete = "false"),
// order.fanout 交换机的名字 必须和生产者保持一致
exchange = @Exchange(value = "fanout_order_exchange",
// 这里是确定的rabbitmq模式是:fanout 是以广播模式 、 发布订阅模式
type = ExchangeTypes.FANOUT)
)
)
RabbitMQ-SpringBoot案例 -direct模式
@Bean
public Queue emailDirectQuequ() {
//true 持久化
return new Queue("email.direct.queue", true);
}
//Direct交换机 起名:TestDirectExchange
@Bean
public DirectExchange directOrderExchange() {
//持久化,非自动删除
return new DirectExchange("direct_order_exchange", true, false);
}
@Bean
public Binding binding2() {
return BindingBuilder.bind(emailDirectQuequ()).to(directOrderExchange()).with("email");
}
@Service
public class MyTestOderService {
@Resource
private RabbitTemplate rabbitTemplate;
public void createOrder() {
String content = UUID.randomUUID().toString();
//再用 同样的代码,发送 短信即可。
rabbitTemplate.convertAndSend("direct_order_exchange","email",content);
}
}
@Component
@RabbitListener(queues = {"email.direct.queue"})
public class FanoutEmailConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("email direct模式收到了消息" + message);
}
}
RabbitMQ-SpringBoot案例 -topic模式
配置 和 发送
@Component
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "sms.topic.queue", durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
key = "com.#"
)) //email的key为:*.email.#。 duanxin为:#.duanxin.#
public class TopicSmsConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("sms topic 接收到了消息" + message);
}
}
//*.email.#
//#.duanxin.#
//com.# 是sms
//com.duanxin 就是给 sms 和 短信 同时发消息。
//com.email.duanxin.test 同时满足3个。
rabbitTemplate.convertAndSend("topic_order_exchange","com.12313213",content);
RabbitMQ高级-过期时间TTL
-
Message TTL
- TTL是 Time To Live 生存时间值
- 超时时间,超过这个时间,默认删除 或 到死信队列里。
- 比如超过20分钟没支付的,进行转移。
-
Auto expire
-
Overflow behaviour
-
溢出行为
-
Single active consumer
-
Dead letter exchange
-
Dead letter routing key
-
死信队列
-
Max length
-
Max length bytes
-
Maximum priority
-
Lazy mode
-
Master locator
过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。RabbitMQ可以对消息和队列设置TTL。目前有两种方法可以设置。
- 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。
- 第二种方法是对消息进行单独设置,每条消息TTL可以不同。
如果上述两种方法同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就称为dead message被投递到死信队列, 消费者将无法再收到该消息。
1-1、 设置队列TTL
1、概述
过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。RabbitMQ可以对消息和队列设置TTL。目前有两种方法可以设置。
- 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。
- 第二种方法是对消息进行单独设置,每条消息TTL可以不同。
- 这种 是真的 过期就消失了,不会写到 死信队列
如果上述两种方法同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就称为dead message被投递到死信队列, 消费者将无法再收到该消息。
代码设置
-
就是存储在 该队列的消息,X毫秒后,会被移除。(一般移除到 死信队列)
-
参数在页面创建队列的时候,点击 add Message TTL参数即可。
-
注意了,如果第一次没创建对,要删除原来的队列,重新进行测试。
Map<String,Object> args2 = new HashMap<>();//使用非Spring boot
args2.put("x-message-ttl",5000);
channel.queueDeclare("ttl.queue", true, false, false, args2);
@Configuration
public class DirectRabbitConfig {
@Bean
public Queue smsQueue() {
Map<String, Object> args = new HashMap<>();
//10秒后自动消失
//这里一定是int类型,
args.put("x-message-ttl", 10000);
return new Queue("sms.direct.queue", true,false,false, args);
}
//Direct交换机 起名:TestDirectExchange
@Bean
public DirectExchange directOrderExchange() {
return new DirectExchange("direct_order_exchange", true, false);
}
@Bean
public Binding bindingDirect2() {
return BindingBuilder.bind(smsQueue()).to(directOrderExchange()).with("sms");
}
}
//进行发送
rabbitTemplate.convertAndSend("direct_order_exchange", "sms", content);
参数 x-message-ttl 的值 必须是非负 32 位整数 (0 <= n <= 2^32-1) ,以毫秒为单位表示 TTL 的值。这样,值 6000 表示存在于 队列 中的当前 消息 将最多只存活 6秒 钟。
1-2、 设置消息TTL
- 则该消息不会被路由到任何队列,而是被直接丢弃。
消息的过期时间;只需要在发送消息(可以发送到任何队列,不管该队列是否属于某个交换机)的时候设置过期时间即可。在测试类中编写如下方法发送消息并设置过期时间到队列:
- 单个消息设置过期时间:expiration,单位:ms(毫秒)
- 下面 微秒是错的。
expiration 字段以微秒为单位表示 TTL 值。且与 x-message-ttl 具有相同的约束条件。因为 expiration 字段必须为字符串类型,broker 将只会接受以字符串形式表达的数字。
当同时指定了 queue 和 message 的 TTL 值,则两者中较小的那个才会起作用。
//非Spring Boot 方式,无用
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("x", "1");
headers.put("y", "1");
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
.deliveryMode(2) // 传送方式
.priority(1)
.contentEncoding("UTF-8") // 编码方式
.expiration("5000") // 过期时间
.headers(headers).build(); //自定义属性
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routing
// @params3: 属性配置
// @params4: 发送消息的内容
for (int i = 0; i <10 ; i++) {
channel.basicPublish("", "ttl.queue2", basicProperties, message.getBytes());
System.out.println("消息发送成功!");
}
MessagePostProcessor p = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
message.getMessageProperties().setContentType("UTF-8");
return message;
}
};
rabbitTemplate.convertAndSend("direct_order_exchange", "sms", content, p);
RabbitMQ高级-死信队列
概述
DLX,全称为Dead-Letter-Exchange , 可以称之为死信交换机,也有人称之为死信邮箱。当消息在一个队列中变成死信(dead message)之后,它能被重新发送到另一个交换机中,这个交换机就是DLX ,绑定DLX的队列就称之为死信队列。
消息变成死信,可能是由于以下的原因:
- 消息被拒绝
- 消息过期
- 队列达到最大长度
- args.put(“x-max-length”,5;);队列对多接收5个。
DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性。当这个队列中存在死信时,Rabbitmq就会自动地将这个消息重新发布到设置的DLX上去,进而被路由到另一个队列,即死信队列。
要想使用死信队列,只需要在定义队列的时候设置队列参数 x-dead-letter-exchange
指定交换机即可。
在rabbitMQ管理界面中结果
未过期:
过期后:
流程
代码
- 参数还是在 Queues 页面,点击后,可以看到。
- 必须配置在 有过期时间的队列里。
- 如果已经有队列了,记得删除 ,重新测试。
- 已经有队列了,又修改了参数,会报错。
@Bean
public Queue smsQueue() {
Map<String, Object> args = new HashMap<>();
//5秒后自动消失
args.put("x-message-ttl", 10000);
args.put("x-dead-letter-exchange","dead_direct_exchange");
//fanout不需要配置
args.put("x-dead-letter-routing-key","dead");
return new Queue("sms.direct.queue", true,false,false, args);
}
@Configuration
public class DeadRabbitConfig {
@Bean
public Queue deadQueue() {
Map<String, Object> args = new HashMap<>();
return new Queue("dead.direct.queue", true, false, false, null);
}
@Bean
public DirectExchange deadDirectExchange() {
return new DirectExchange("dead_direct_exchange", true, false);
}
@Bean
public Binding deadBinding() {
return BindingBuilder.bind(deadQueue()).to(deadDirectExchange()).with("dead");
}
}
rabbitTemplate.convertAndSend("direct_order_exchange", "sms", content);
- 在页面 可看到 sms.direct.queue 的配置:有这个两个参数。
- DLX dead-letter-exchange
- DLK dead-letter-routing-key:
- 如上配置,队列的数据10秒后,没被消费,打入死信队列。
x-dead-letter-exchange: | dead_direct_exchange |
---|---|
x-dead-letter-routing-key: | dead |
队列达到最大长度
- 队列的TTL,也可以不设置。
- 改了参数,要删除原来队列。
@Bean
public Queue smsQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-max-length", 5);//队列对多接收5个。
args.put("x-dead-letter-exchange", "dead_direct_exchange");
args.put("x-dead-letter-routing-key", "dead");
return new Queue("sms.direct.queue", true, false, false, args);
}
RabbitMQ运维-持久化机制和内存磁盘的监控
01、RibbitMQ持久化
持久化就把信息写入到磁盘的过程。
- 内存里的数据 写入 磁盘
- 内存的极限大小为 0.4。8G的 0.4 就是3.2G
- 达到 这个内存,进入 blocking 和 blocked
- RabbitMQ会暂时阻塞客户端的连接,并且停止接收从客户端发来的消息
02、RabbitMQ持久化消息
把消息默认放在内存中是为了加快传输和消费的速度,存入磁盘是保证消息数据的持久化。
03、RabbitMQ非持久化消息
非持久消息:是指当内存不够用的时候,会把消息和数据转移到磁盘,但是重启以后非持久化队列消息就丢失。
04、RabbitMQ持久化分类
RabbitMQ的持久化队列分为:
1:队列持久化
2:消息持久化
3:交换机持久化
不论是持久化的消息还是非持久化的消息都可以写入到磁盘中,只不过非持久的是等内存不足的情况下才会被写入到磁盘中。
05、RabbitMQ队列持久化的代码实现
队列的持久化是定义队列时的durable参数来实现的,Durable为true时,队列才会持久化。
// 参数1:名字
// 参数2:是否持久化,
// 参数3:独du占的queue,
// 参数4:不使用时是否自动删除,
// 参数5:其他参数
channel.queueDeclare(queueName,true,false,false,null);
其中参数2:设置为true,就代表的是持久化的含义。即durable=true。持久化的队列在web控制台中有一个D
的标记
测试步骤
1:可以建立一个临时队列
2:然后重启rabbit-server服务,会发现持久化队列依然在,而非持久队列会丢失。
systecmctl restart rabbitmq-server或者docker restart myrabbit
06、RabbitMQ消息持久化
消息持久化是通过消息的属性deliveryMode来设置是否持久化,在发送消息时通过basicPublish的参数传入。
/ 参数1:交换机的名字
// 参数2:队列或者路由key
// 参数3:是否进行消息持久化
// 参数4:发送消息的内容
channel.basicPublish(exchangeName, routingKey1, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
07、RabbitMQ交换机持久化
和队列一样,交换机也需要在定义的时候设置持久化的标识,否则在rabbit-server服务重启以后将丢失。
// 参数1:交换机的名字
// 参数2:交换机的类型,topic/direct/fanout/headers
// 参数3:是否持久化
channel.exchangeDeclare(exchangeName,exchangeType,true);
RabbitMQ运维-内存磁盘的监控
01、RabbitMQ的内存警告
当内存使用超过配置的阈值或者磁盘空间剩余空间对于配置的阈值时,RabbitMQ会暂时阻塞客户端的连接,并且停止接收从客户端发来的消息,以此避免服务器的崩溃,客户端与服务端的心态检测机制也会失效。
如下图:
当出现blocking或blocked话说明到达了阈值和以及高负荷运行了。
02、RabbitMQ的内存控制
参考帮助文档:https://www.rabbitmq.com/configure.html
当出现警告的时候,可以通过配置去修改和调整
docker 进入 容器
docker exec -it 3a3afa942911 bash
02-1、命令的方式
- 选择其一,第一个是 百分比
rabbitmqctl set_vm_memory_high_watermark <fraction>
rabbitmqctl set_vm_memory_high_watermark absolute 50MB
fraction/value 为内存阈值。默认情况是:0.4/2GB,代表的含义是:当RabbitMQ的内存超过40%时,就会产生警告并且阻塞所有生产者的连接。通过此命令修改阈值在Broker重启以后将会失效,通过修改配置文件方式设置的阈值则不会随着重启而消失,但修改了配置文件一样要重启broker才会生效。
分析:
rabbitmqctl set_vm_memory_high_watermark absolute 50MB
02-2、配置文件方式 rabbitmq.conf
当前配置文件:/etc/rabbitmq/rabbitmq.conf
#默认
#vm_memory_high_watermark.relative = 0.4
# 使用relative相对值进行设置fraction,建议取值在04~0.7之间,不建议超过0.7.
vm_memory_high_watermark.relative = 0.6
# 使用absolute的绝对值的方式,但是是KB,MB,GB对应的命令如下
vm_memory_high_watermark.absolute = 2GB
03、RabbitMQ的内存换页
在某个Broker节点及内存阻塞生产者之前,它会尝试将队列中的消息换页到磁盘以释放内存空间,持久化和非持久化的消息都会写入磁盘中,其中持久化的消息本身就在磁盘中有一个副本,所以在转移的过程中持久化的消息会先从内存中清除掉。
- 1000M,达到 400M时。
- 配置的是0.5。400*0.5=200M,就换页200M到 磁盘。
默认情况下,内存到达的阈值是50%时就会换页处理。
也就是说,在默认情况下该内存的阈值是0.4的情况下,当内存超过0.4*0.5=0.2时,会进行换页动作。
比如有1000MB内存,当内存的使用率达到了400MB,已经达到了极限,但是因为配置的换页内存0.5,这个时候会在达到极限400mb之前,会把内存中的200MB进行转移到磁盘中。从而达到稳健的运行。
可以通过设置 vm_memory_high_watermark_paging_ratio
来进行调整
vm_memory_high_watermark.relative = 0.4
vm_memory_high_watermark_paging_ratio = 0.7(设置小于1的值)
什么设置小于1,以为你如果你设置为1的阈值。内存都已经达到了极限了。你在去换页意义不是很大了。
04、RabbitMQ的磁盘预警
当磁盘的剩余空间低于确定的阈值时,RabbitMQ同样会阻塞生产者,这样可以避免因非持久化的消息持续换页而耗尽磁盘空间导致服务器崩溃。
默认情况下:磁盘预警为50MB的时候会进行预警。表示当前磁盘空间第50MB的时候会阻塞生产者并且停止内存消息换页到磁盘的过程。
这个阈值可以减小,但是不能完全的消除因磁盘耗尽而导致崩溃的可能性。比如在两次磁盘空间的检查空隙内,第一次检查是:60MB ,第二检查可能就是1MB,就会出现警告。
通过命令方式修改如下:
- set_disk_free_limit 100GB
- 就是剩余空间 <100GB,就报红。默认是 <50MB挂起。
- 一但暴红,一样会被挂起。不在接收 发送者
rabbitmqctl set_disk_free_limit <disk_limit>
rabbitmqctl set_disk_free_limit memory_limit <fraction>
disk_limit:固定单位 KB MB GB
fraction :是相对阈值,建议范围在:1.0~2.0之间。(相对于内存)
通过配置文件配置如下:
disk_free_limit.relative = 3.0
disk_free_limit.absolute = 50mb
RabbitMQ高级-消息确认机制的配置
- 这是Work模式 公平分发,手动确认 Ack的代码
finalChannel.basicQos(1);
finalChannel.basicConsume("queue1", false, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(2000); //这里睡了2秒。下一个消费者睡200毫秒即可。
//第二个参数:false,就是单条消费
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
- NONE值是禁用发布确认模式,是默认值
- CORRELATED值是发布消息成功到交换器后会触发回调方法,如1示例
- SIMPLE值经测试有两种效果,
- 其一效果和CORRELATED值一样会触发回调方法,
- 其二在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker;
# 服务端口
server:
port: 8080
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 47.104.141.27
port: 5672
publisher-confirm-type: correlated
package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.callback;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
/**
* @description:
* @author: xuke
* @time: 2021/3/5 23:25
*/
public class MessageConfirmCallback implements RabbitTemplate.ConfirmCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack){
System.out.println("消息确认成功!!!!");
}else{
System.out.println("消息确认失败!!!!");
}
}
}
/**
* @Author xuke
* @Description 模拟用户购买商品下单的业务
* @Date 22:26 2021/3/5
* @Param [userId, productId, num]
* @return void
**/
public void makeOrderTopic(String userId,String productId,int num){
// 1: 根据商品id查询库存是否充足
// 2: 保存订单
String orderId = UUID.randomUUID().toString();
System.out.println("保存订单成功:id是:" + orderId);
// 3: 发送消息
//com.# duanxin
//#.email.* email
//#.sms.# sms
// 设置消息确认机制
rabbitTemplate.setConfirmCallback(new MessageConfirmCallback());
rabbitTemplate.convertAndSend("topic_order_ex","com.email.sms.xxx",orderId);
}