RabbitMQ | 柏竹
(102条消息) RabbitMQ官方文档知识点总结合集+代码注释(中文+Java版)_rabbitmq官方中文文档_嘤桃子的博客-CSDN博客
关于 RabbitMQ,应该没有比这更详细的教程了! - 掘金
在 操作系统与应用软件之间搭建的桥梁。负责两者之间的通讯。(跨平台)
背景:微服务架构,若微服务之间需要通讯,此时就需要中间件为中介,并且保证 持久性、高可用(防止服务挂掉)容错性,稳定性
分布式架构优劣:
存在问题
- 学习成本高,技术栈过多
- 运维成本和服务器成本增高、人员成本
- 项目的负载度也会上升
- 面临的错误和容错性也会成倍增加
- 占用的服务器端口和通讯的选择的成本高
- 安全性的考虑和因素逼迫可能选择RMI/MQ相关的服务器端通讯。
好处
- 服务系统的独立,占用的服务器资源减少和占用的硬件成本减少,
确切的说是:可以合理的分配服务资源,不造成服务器资源的浪费- 系统的独立维护和部署,耦合度降低,可插拔性。
- 系统的架构和技术栈的选择可以变的灵活(而不是单纯的选择java)
- 弹性的部署,不会造成平台因部署造成的瘫痪和停服的状态。
选择一个优秀的消息中间件:
所以,消息队列是啥:
在跨服务、系统之间传递消息。并提供 消息排队、消息传递机制
应用场景:
消息队列主要任务: 接收、分发、存储消息。
数据存入磁盘,可永久保存
ActiveMQ | RabbitMQ | Kafka | RocketMQ | |
---|---|---|---|---|
文件存储 | 支持 | 支持 | 支持 | 支持 |
数据库 | 支持 | / | / | / |
发布订阅、轮询分发、公平分发、重发、消息拉取(类似于Git pull、push)
ActiveMQ | RabbitMQ | Kafka | RocketMQ | |
---|---|---|---|---|
发布订阅 | ✅ | ✅ | ✅ | ✅ |
轮询分发 | ✅ | ✅ | ✅ | ❌ |
公平分发 | ❌ | ✅ | ✅ | ❌ |
重发 | ✅ | ✅ | ❌ | ✅ |
消息拉取 | ❌ | ✅ | ✅ | ✅ |
RabbitMQ 消息的分发策略_duyinboo的博客-CSDN博客
发布订阅: 生产者将消息发到消息队列,消费者订阅了后, 就会收到消息队列的消息,<推>
轮询分发:
不会因为服务器性能的不同而产生的数据倾斜。 每个服务收到的消息平均分-- 公平
公平分发:
重发: 消息队列发送消息后,存在应答机制:若没有收到消费者的成功反馈,则重新发送给其他消费者(服务器) 消费,防止消息堆积。保证消息可靠性。
消息拉取: <拉>
(71条消息) windows环境下安装RabbitMQ(超详细)_luckySnow-julyo的博客-CSDN博客
Windows 10 下 RabbitMQ的安装 - 知乎 (zhihu.com)
RabbitMQ6中模型:
广播:交换机将消息发送给绑定了交换机的所有队列。
路由:交换机通过不同的路由key 将消息发送到不同的队列。
Topic 主题: 在路由的基础上, 队列接收 符合通配符的消息。路由通过模糊匹配,将消息发送到匹配的队列。
优点:
劣势:
Broker: 接收、分发消息
Virtual Host: 当多个用户使用同一个MQ时,会分出多个Virtual Host ;每个 VH 包含各自的 交换机和队列;
Exchange: 交换机,通过匹配路由(routing key) 不同类型的分发规则 将消息传给不同的队列。
Queue:接收来自交换机的消息。传给消费者消费。
Binding:Exchange与 Queue的连接。包括 routing key,Binding 被保存在 交换机的 查询表中
Exchange 的查询表,其中记录了 Exchange 的名称、类型、是否持久化等属性,以及与之绑定的队列和 RoutingKey 等信息。具体操作步骤如下:
Connection:客户端(生产、消费者)与 Broker 建立TCP连接;
channel:轻量级的Connection,避免频繁建立TCP连接,
Web管理页:
RabbitMQ 管理页面该如何使用 - 掘金
(101条消息) 消息中间件RabbitMQ需要知道的6个端口的作用_rabbitmq 4369_像夏天一样热的博客-CSDN博客
管理界面的登陆页
http://127.0.0.1:15672
端口 | 作用 |
---|---|
15672 | 管理界面ui使用的端口 |
15671 | 管理监听端口 |
5672,5671 | AMQP 0-9-1 without and with TLSclient端通信口 |
4369 | (epmd)epmd代表 Erlang端口映射守护进程,erlang发现口 |
25672 | ( Erlang distribution) server间内部通信口 |
rabbitmq 常用命令
启动监控管理器:rabbitmq-plugins enable rabbitmq_management
关闭监控管理器:rabbitmq-plugins disable rabbitmq_management
启动rabbitmq:rabbitmq-service start
关闭rabbitmq:rabbitmq-service stop
查看所有的队列:rabbitmqctl list_queues
清除所有的队列:rabbitmqctl reset
关闭应用:rabbitmqctl stop_app
启动应用:rabbitmqctl start_app
用户和权限设置(后面用处)
添加用户:rabbitmqctl add_user username password
分配角色:rabbitmqctl set_user_tags username administrator
新增虚拟主机:rabbitmqctl add_vhost vhost_name
将新虚拟主机授权给新用户:rabbitmqctl set_permissions -p vhost_name username '.*' '.*' '.*'
角色说明
none 最小权限角色
management 管理员角色
policymaker 决策者
monitoring 监控
administrator 超级管理员
-----------------------------------------
1. 关闭与启动
① 到指定目录:cd/etc/init.d
② 停止:rabbitmq-server stop
③ 启动:rabbitmq-server start
④ 查看是否停止/启动成功:ps -ef |grep rabbitmq
2.开启RabbitMQ Managerment管理界面
① 到指定目录:cd /usr/lib/rabbitmq/lib/rabbitmq_server-3.1.5/plugins
② 开启管理界面:./rabbitmq-plugins enable rabbitmq_management
启动 RabbitMQ 服务:
sudo systemctl start rabbitmq-server
停止 RabbitMQ 服务:
sudo systemctl stop rabbitmq-server
重启 RabbitMQ 服务:
sudo systemctl restart rabbitmq-server
查看 RabbitMQ 服务状态:
sudo systemctl status rabbitmq-server
启用 RabbitMQ 管理界面:
sudo rabbitmq-plugins enable rabbitmq_management
查看 RabbitMQ 管理界面地址:
sudo rabbitmqctl status | grep "listener.*{tcp.*http"
查看 RabbitMQ 队列列表:
sudo rabbitmqctl list_queues
删除 RabbitMQ 队列:
sudo rabbitmqctl delete_queue [QUEUE_NAME]
查看 RabbitMQ 交换机列表:
sudo rabbitmqctl list_exchanges
删除 RabbitMQ 交换机:
sudo rabbitmqctl delete_exchange [EXCHANGE_NAME]
以下是 RabbitMQ 在 Windows 环境下的常用命令:
注意,在 Windows 环境下,RabbitMQ 相关命令需要在 RabbitMQ 安装目录下的
sbin
文件夹下执行。
启动 RabbitMQ 服务:
net start RabbitMQ
停止 RabbitMQ 服务:
net stop RabbitMQ
重启 RabbitMQ 服务:
net stop RabbitMQ
net start RabbitMQ
查看 RabbitMQ 服务状态:
sc query RabbitMQ
启用 RabbitMQ 管理界面:
rabbitmq-plugins enable rabbitmq_management
查看 RabbitMQ 管理界面地址:
rabbitmqctl status | findstr "http:"
查看 RabbitMQ 队列列表:
rabbitmqctl.bat list_queues
删除 RabbitMQ 队列:
rabbitmqctl.bat delete_queue [QUEUE_NAME]
查看 RabbitMQ 交换机列表:
rabbitmqctl.bat list_exchanges
删除 RabbitMQ 交换机:
rabbitmqctl.bat delete_exchange [EXCHANGE_NAME]
可视化界面管理:
启动
rabbitmq-plugins enable rabbitmq_management
关闭
rabbitmq-plugins disable rabbitmq_management
RabbitMQ 消息投递以及ACK机制 - Mr*宇晨 - 博客园
消息只能被消费一次。 可能会重复放入队列。
消费者c1,c2 轮询或非公平消费 队列中的消息。(不会重复消费)
高并发下可能会重复放入队列:
在rabbitmq的工作队列模式下,在高并发情况下,某一条消息有可能会被重复放入队列。
当且仅当
(消费者没及时确认消息,此时该消费者挂掉,会导致消息被重新放入队列,以至于重新消费该消息。)
要避免这种情况,可以考虑使用“任务分配模式”,让每个消费者通过channel.basicqos(1)设定取值比例,使得每个消费者只能顺序地获取一个消息来处理,这样就可以避免竞争和重复处理
RabbitMQ之Work Queues模式_
默认 是轮询消费消息。
消息应答: 默认情况下,手动消息确认是打开的。在前面的例子中,我们通过auto_ack=True
标志显式地关闭了它们。
自动应答(消息发给消费者后自动删除消息。)
带来的问题:若消费者此时挂掉会造成消息丢失
解决方法:手动确认消息。
让消费者消费消息完成后向队列发送 消息确认,随后队列删除消息。
若消费者挂掉,没有发送 消息确认。则该消息重新被放入队列,可让其他消费者消费该消息。
RabbitMQ不会为未确认的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很久很久。仅当消费者挂掉才会将消息回退到队列中。
rabbitmq.conf
中的参数consumer_timeout
自定义超时时间
手动确认:看实现资料 01.pdf
消费者
重写 defaultConsumer
的 handleDelivery()
方法:
channel.basicConsume(将autoAck设为false。)
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body);
int i = 5/0;
System.out.println("received:"+msg);
// 手动进行ACK
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 监听队列,第二个参数false,手动进行ACK
channel.basicConsume(QUEUE_NAME,false,consumer);
应答方法
deliveryTag: 发送消息的标签; multiple: 是否批量应答
multiple
返回 | 方法 | 说明 |
---|---|---|
void | basicAck(long deliveryTag, boolean multiple) |
确认收到 |
void | basicReject(long deliveryTag, boolean requeue) |
拒绝消息,并丢弃 |
void | basicNack(long deliveryTag, boolean multiple, boolean requeue) |
拒绝收到 |
ACK确认与消息接收必须使用相同的channel信道
- 若 消费者未确认消息,便将消费者关闭,则队列会回收消息导致重复消费。
- 消费者没关闭,会一直占用CPU内存。
队列持久化:不会将队列中的消息持久化!!!
channel.queueDeclare();
将durable 设为 true。
若原先队列 已设为 false,则需要 删除原队列, 重新queueDeclare
消息持久化:
channel.basicPublish()
将 props 设为 MessageProperties.PERSISTENT_TEXT_PLAIN
当然,也不是完全持久化,队列不一定就立刻刷入磁盘。
非公平分发:
存在多个消费者的效率不同,若采用轮询策略易导致 消息堆积,消费者有的很清闲,有的很忙。此时需要进行公平分发(能者多劳。)
channel.basicQos(1)
消费者每次只能处理 不能超过一个消息。
- 若消费者还没将上次的消息进行ACK,则不会将新的消息派给该消费者。
- 直到消费者ACK后,进行继续分发消息。
预期值:允许消费者未确认消息的最大数量。
channel.basicQos(6);
producer -> exchange -> queue -> consumer
broker
此部分是 当消息被 queue
收到后,broker
向生产者确认消息 ( 生产者丢失数据 )
发布确认高级是确认 producer
到 broker
(交换机确认)、exchange
到 queue
的消息 (queue确认)。
发布确认:生产者通过channel
发布的消息, 当消息 被对应的队列收到后,broker server会给生产者发送ACK确认包(消息唯一ID),确保消息已经正确到达目标队列。
若MQ队列持久化、消息持久化。
channel
将消息发给队列,当消息和MQ刷入磁盘后,broker 会告知生产者消息确认。开启确认:
channel.confirmSelect();
代码详见 idea:rabbitmq-1-producter
项目下: work_producter_confirm.java
单个确认发布: 当上一个消息 broker 返回确认后,消费者再发送下一个消息。
boolean wait = channel.waitForConfirms();
缺点:逐个发布确认,可保证消息是否被 mq成功收到。 但效率太慢
批量发布确认: 每发送 指定数量后同一确认。
if (i%confirmCount==0&&i>0){
boolean wait = channel.waitForConfirms();
if (wait){
System.out.println("消息发送成功"+i);
}
}
缺点:若出现问题,无法精确到是哪个消息没有被mq收到。
异步确认: 利用回调函数
确定 哪些消息 成功接收、未成功接收。
//支持高并发,且用跳表提高查询效率。
ConcurrentSkipListMap<Long, String> skipListMap = new ConcurrentSkipListMap<>();
// 异步消息正常的确认
ConfirmCallback ackCallback = (long deliveryTag, boolean multiple)->{
// todo 批量删除 包括当前消息之前的所有消息 都是已确认的,需要删除
// 剩下的就是 未正常确认的消息。
... 略
// 不是批量 则单独删除
... 略
};
// 异步消息异常的确认
ConfirmCallback nackCallback = (long deliveryTag, boolean multiple)->{... };
// 消息监听器,用于监听消息 接收是否成功
channel.addConfirmListener(ackCallback,nackCallback);
// 发布消息时:
skipListMap.put(channel.getNextPublishSeqNo(),message);
// 去掉 消息持久化 ackCallback就能打印出多个消息
channel.basicPublish("",queueName, null,message.getBytes());
ConcurrentSkipListMap
J.U.C 之 ConcurrentSkipListMap - 掘金
(102条消息) ConcurrentSkipListMap 常用的方法_
headMap()
方法:对 返回的部分视图 map 的操作会映射到原Map上。
也就是说,这里获取headMap并clear,是清除消息队列Map中在该delivery Tag之前的所有消息,也就是批量确认
在 simple和work模式中消息只能被消费一次;
若想将一个消息发给多个消费者:由交换机 routingKey
绑定 多个队列,
默认交换机:(AMQP default)
channel.basicPublish("","queueName",null,message.getBytes());
交换机 名称默认为 空字符串, 将消息传给 队列名称为 queueName
的队列。
默认交换机 默认绑定到每个队列,路由键等于队列名称。
无法显式绑定到默认交换机或从默认交换机取消绑定。也无法删除。
临时队列:
bind:交换机与队列绑定
/**
* @param queue the name of the queue
* @param exchange the name of the exchange
* @param routingKey the routing key to use for the binding
*/
channel.queueBind(queue1,exchangeName,routingKey);
fanout
广播
在 fanout 模式下 只要与声明的交换机绑定,不论 多个队列routingKey
是否一样,都会将消息传给 绑定了交换机的队列
消费者收到消息不会受到 RoutingKey
的影响 , 只需绑定就可以收到通知
fanout类型的交换机试忽略routingkey的,只要绑定了队列都会收到消息
direct
不同的 routingKey
绑定不同的队列。
topics
"*"
:代替一个词"#"
:代替零个或多个词如果只有一个
#
那么将会接收通道的所有数据 (fanout
)如果没有
#
/*
出现 , 默认采用direct
例子:
RoutingKey 通配值 说明
com.sans.color *.sans.*
匹配3个单词中的中间单词 sans
com.sans.color.red #.red
匹配最后为 red
com.sans.color.blue con.#
匹配开头为 com
通配符规则:
RabbitMQ - Springboot 整合 死信队列(Dead-Letter-Exchange ) - 知乎
当消息消费发生异常时,将消息投入死信队列中。保证订单业务的消息数据不丢失
eg: 用户在商城下单成功并点击去支付后在指定时间未支付时自动失效
在 RabbitMQ 中使用 Confirm 模式时,异步未确认消息的处理方式取决于你的代码实现。一般来说,有两种处理方式:
即: 无法被消费的消息。某种原因导致消息无法被消费,若也没做后续处理,就会放入死信队列
导致的原因:
生产者发送10条消息, 若此时C1 断链,且消息过期( 10s
),消息就认为无法正常消费,便转到死信队列 dead-queue
,(若消息发太快 即使未过期,但C1断链无法消费,10s内 也会转到 dead-queue
)。
普通队列与 死信交换机 绑定:
Map<String, Object> params = new HashMap<>();
// 指定死信交换机
params.put("x-dead-letter-exchange",deadExchange);
// 指定死信 routingKey
params.put("x-dead-letter-routing-key","dead");
// -- 设置 正常队列接收消息长度限制:
// params.put("x-max-length",6);
// 由于正常的队列 需要与 死信交换机绑定:
channel.queueDeclare(normalQueue,false,false,false,params);
设置 队列 中所有消息的过期时长:
// 生产者发送消息 设置消息过期时长:10s
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
// 发布者发布消息
channel.basicPublish(normalExchange,"normal",properties,message.getBytes());
设置正常队列长度限制
// -- 设置 正常队列接收消息长度限制:
params.put("x-max-length",6);
设置 拒绝消息:
// 设置消费者 拒绝消息
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
// 取消手动接收
channel.basicConsume(dead_producter.normalQueue,
false, deliverCallback, cancelCallback);
一起来学SpringBoot | 第十三篇:RabbitMQ延迟队列-腾讯云开发者社区-腾讯云
使用场景:需要在某个事件发生之后或者之前的指定时间点完成某一项任务
1.订单在十分钟之内未支付则自动取消
2.新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
3.用户注册成功后,如果三天内没有登陆则进行短信提醒。
4.用户发起退款,如果三天内没有得到处理则通知相关运营人员。
5.预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议
对每条消息设置过期时长:
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
---------------------------------------------------
@GetMapping("/hello")
public void hello() {
Message message = MessageBuilder.withBody("hello javaboy".getBytes())
.setExpiration("10000")
.build();
rabbitTemplate.convertAndSend(QueueConfig.JAVABOY_QUEUE_DEMO, message);
}
作者:江南一点雨
链接:https://juejin.cn/post/7051469607806173221
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在队列中统一设置消息时长: x-message-ttl
属性
@Bean("queueB")
public Queue queueB(){
// 设置死信队列 和TTL
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange",exchangeY);
arguments.put("x-dead-letter-routing-key","YD");
arguments.put("x-message-ttl",30*1000);
return QueueBuilder.durable(queueB).withArguments(arguments).build();
}
对于第一种方式(每个消息设置自己的过期时间),当消息过期后并不会立马被删除,而是当消息要投递给消费者的时候才会去删除,因为第二种方式,每条消息的过期时间都不一样,想要知道哪条消息过期,必须要遍历队列中的所有消息才能实现,当消息比较多时这样就比较耗费性能,因此对于第二种方式,当消息要投递给消费者的时候才去删除。
对于第二种方式(在队列中设置统一的消息过期时间),当消息队列设置过期时间的时候,那么消息过期了就会被删除,因为消息进入 RabbitMQ 后是存在一个消息队列中,队列的头部是最早要过期的消息,所以 RabbitMQ 只需要一个定时任务,从头部开始扫描是否有过期消息,有的话就直接删除。
关于 RabbitMQ,应该没有比这更详细的教程了! - 掘金
问题:queue只会计算头部消息时间,如果前面的消息ttl过长,后面就起不到准确的延时效果;易引起消息堆积。
解决方法:rabbitmq的插件:rabbitmq_delayed_message_exchange
原理:当消息发布时,先存入
Mnesia
分布式数据库管理系统 中 ,当消息过期时,再开始发送消息到指定队列。
直接将交换机设为延时;支持延时投递消息的机制。
(107条消息) @Component和@Configuration的差别_@configuration和@component区别_墨子白的博客-CSDN博客
总结来说,延时队列用于实现延时任务的功能,而死信队列用于处理无法被正确处理的消息。它们在消息队列的应用中有不同的用途。
RabbitMQ(4):消息确认机制详解 - 掘金
producer -> broker -> exchange -> queue -> consumer 。
在编码时我们可以用两个选项用来控制消息投递的可靠性:
producer
到 RabbitMQ broker cluster
成功,则会返回一个 confirmCallback
;exchange
到 queue
投递失败,则会返回一个 returnCallback
我们可以利用这两个 callback 接口来控制消息的一致性和处理一部分的异常情况。
作用:判断 交换机是否接收到 消息且对 生产者做出反馈。
配置文件:
spring:
rabbitmq:
publisher-confirm-type: correlated
它有三个值:
config
交换机、队列、 routingkey
@Configuration
@Slf4j
public class SeniorConfirmConfig {
private final String queue = "SeniorConfirmQueue";
private final String exchange = "SeniorConfirmExchange";
private final String routingKey = "key1";
@Bean
public Queue SeniorQueue(){
return QueueBuilder.durable(queue).build();
}
@Bean
public DirectExchange SeniorExchange(){
return ExchangeBuilder.directExchange(exchange).build();
}
@Bean
public Binding bindingQE(@Qualifier("SeniorQueue") Queue queue, @Qualifier("SeniorExchange") DirectExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with(routingKey);
}
}
实现 RabbitTemplate
的接口: confirmCallback
以此 交换机响应生产者消息。
创建rabbitTemplate 的实例Bean 后,init 实现的实例。
@PostConstruct
: 先执行完构造方法,再注入依赖,最后执行初始化操作
Spring框架@PostConstruct注解详解 - 掘金
@Component
@Slf4j
public class MyConfirmCallback implements RabbitTemplate.ConfirmCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
// 创建rabbitTemplate 的实例Bean 后,init 实现的实例。
public void init(){
rabbitTemplate.setConfirmCallback(this);
}
@Override
/**
* 交换机 回调方法
* correlationData 消息的信息、ID
* ack 交换机收到消息是否成功
* cause 交换机未收到消息的原因 (当 ack=false)
*/
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack){
log.warn("交换机 已接收到 ID 为 :{} 的消息",correlationData.getId());
}else {
log.error("交换机 未接收到 ID 为 :{} 的消息,原因是:{}",correlationData.getId(),cause);
}
}
}
生产者、消费者
CorrelationData
:消息的额外消息(唯一标识)。 (id,Message)
ConfirmCallback
和ReturnCallback
的回调函数 设置 消息的唯一标识。
CorrelationData
的ID
找到对应消息;随后可判断消息是否被 交换机接收@GetMapping("sendSeniorConfirmMsg/{message}")
public void sendSeniorConfirmMsg(@PathVariable String message){
log.info("时间:{};为队列SeniorConfirmQueue发送消息:{} ",new Date(),message);
CorrelationData correlationData = new CorrelationData("1");
correlationData.setReturnedMessage(new Message("returnedMessage".getBytes()));
rabbitTemplate.convertAndSend("SeniorConfirmExchange","key1",message,correlationData);
}
--------------------
//消费者:
@RabbitListener(queues = "SeniorConfirmQueue")
public void SeniorConfirmQueueMessage(Message message, Channel channel){
System.out.println(message.getMessageProperties());
System.out.println(channel);
log.info("时间:{},接收到来自{}的消息: {}",new Date(),
message.getMessageProperties().getConsumerQueue(),
new String(message.getBody()));
}
作用:当消息路由 不可达(没有该
routingkey
) 时,将消息 告知生产者。
配置文件:
spring:
rabbitmq:
host: 127.0.0.1
username: heima
password: heima
virtual-host: testheima
port: 5672
publisher-confirm-type: correlated
publisher-returns: true
returnedMessage
当消息路由 不可达(没有该
routingkey
) 时,将消息 告知生产者。
@Override
/**
* Returned message callback.
* 当消息路由 不可达(不存在该 routingkey ) 时,告知生产者。
* @param message the returned message.
* @param replyCode the reply code.
* @param replyText the reply text.
* @param exchange the exchange.
* @param routingKey the routing key.
*/
public void returnedMessage(Message message, int replyCode,
String replyText, String exchange,
String routingKey) {
log.error("消息 {} ,响应码 {} , 回退原因 {} , 交换机 {} ,routingKey {}",message,replyCode,replyText,exchange,routingKey);
}
当某消息无法路由时,若通过 returnedMessage
告知生产者 ,通常为打印日志的方式;其实也可将 消息存在 另一个交换机上。由备份交换机处理
SeniorExchange
绑定 备份交换机:backUpExchange
@Bean
public DirectExchange SeniorExchange(){
return ExchangeBuilder.directExchange(seniorConfirmExchange)
// 设置备份交换机
.alternate(backUpExchange).build();
}
当 returnedMessage
与 备份交换机同时开启,则优先进入 备份的交换机。
定义:用户对于同一操作 发起一次或多次请求,返回的结果是一致的
RabbitMQ幂等性的主流解决方案 - 简书
RabbitMQ 消息幂等性&顺序性&消息积压&面试问题 - Mr*宇晨 - 博客园
RabbitMQ可能遇到的问题:
背景:
消费者正常接收到 MQ的消息,消费完后 向MQ确认时(返回ACK)网络中断,导致MQ认为 消费者未成功消费消息。MQ会把该消息 发给其他消费者消费。从而导致重复消费。
解决方法:
唯一ID+ 指纹码 判断是否被消费; 使用数据库去重
insert
插入 代表已消费过。指纹码:通过规则或者时间戳加别的服务给到的唯一信息码,以确保 消息唯一性。
缺点:高并发情况 数据库读写压力大,可根据ID分库分表,采用路由算法分压分流。
使用Redis setnx
原子命令判断
setnx
成功则说明没消费过,
使用 redis 的原子性去实现主要需要考虑两个点
databus
同步。背景: 重要客户的消息需要优先消费。
在队列中 对消息进行排序,(设置优先级) 再进行发给消费者。
官方的优先级范围: 0-255
0-10
Map<String, Object> params = new HashMap();
params.put("x-max-priority", 10);
channel.queueDeclare("hello", true, false, false, params);
Boot版本:定义优先级队列 0-100
· /**
* 定义优先级队列
*/
Queue queue() {
Map<String, Object> args= new HashMap<>();
args.put("x-max-priority", 100);
return new Queue(QUEUE, false, false, false, args);
}
消息代码 设置优先级:
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().priority(5).build();
Boot版本: 设置消息优先级
rabbitTemplate.convertAndSend("exchange","routringKey","message",
(Message message2) ->{
message2.getMessageProperties().setPriority(4);
return message2;
}, correlationData1);
惰性队列会尽可能将 消息存入磁盘,当消费者需要消费消息时,再将消息加载至内存。
解决的问题:消费者宕机 导致消息堆积
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-queue-mode", "lazy");
channel.queueDeclare("myqueue", false, false, false, args);
Mirror Queue
RabbitMQ 镜像队列 使用和原理详解 - 掘金
可在多个节点之间复制队列中的消息。
镜像队列包括一个主队列和多个镜像队列。 主与镜像之间的消息是异步方式
消息发送给主队列,随后会复制到所有的镜像队列;当主队列所在的节点发生故障,镜像可以接管主队列
在 某一节点的 Admin-policies
中配置镜像策略,结果如下:
参数配置:
其中,Name为 策略名称;
Pattern为 通过正则将部分队列设为镜像策略的队列。
^
是将所有的队列设为镜像队列。Definition 镜像定义。 包括 ha-mode,ha-params,ha-sync-mode
ha-mode
镜像队列模式:
all
在集群的所有代理(节点)上进行镜像。exactly
指定个数的节点上进行镜像。个数由 ha-params
指定。建议设置的副本值为大多数节点N / 2 + 1
ha-params = 1
表示只有一个主节点ha-params = 3
需要2个节点作为镜像节点(一主二从)nodes
在指定节点进行镜像。 节点名称通过 ha-params
指定。ha-params
ha-mode
需要的参数ha-sync-mode
镜像队列间的同步方式。
automatic
自动将主节点消息同步到镜像节点。 – 新加入group的节点 会自动同步消息manually
手动操作完成消息同步。镜像队列只接收新消息,已被发送的消息无法备份,。如果主队列在所有未同步的消息耗尽之前宕机,则这些消息将丢失。Priority Priority: 可选参数, policy的优先级。
显示的 mirror-policy
为该队列应用的镜像策略。+n 表示 同步副本为n个
ha-sync-mode | 说明 |
---|---|
manual | 这是默认模式。新队列镜像将不接收现有消息,它只接收新消息。一旦使用者耗尽了仅存在于主服务器上的消息,新的队列镜像将随着时间的推移成为主服务器的精确副本。如果主队列在所有未同步的消息耗尽之前失败,则这些消息将丢失。您可以手动完全同步队列,详情请参阅未同步的镜像部分。 |
automatic | 当新镜像加入时,队列将自动同步。值得重申的是,队列同步是一个阻塞操作。如果队列很小,或者您在RabbitMQ节点和ha-sync-batch-size之间有一个快速的网络,那么这是一个很好的选择。 |
当主节点出现故障,最老的从节点会提升为新的主节点。
参数:
ha-promote-on-shutdown,
ha-promote-on-failure
ha-promote-on-shutdown/ha-promote-on-failure | 说明 |
---|---|
when-synced | 从节点与主节点完成数据同步,才会被提升为主节点 |
always | 无论什么情况下从节点都将被提升为主节点 |
(67条消息) 学会Haproxy+Keepalived实现高可用负载均衡项目搭建一片就够了!!!_仇亚峰的博客-CSDN博客
负载均衡工具。
不同的适用场景:
Web前端采用Nginx/Haproxy + Keepalived 负载均衡; 后端 MySQL数据库一主多从和读写分离。采用LVS+Keepalived 架构。
优点:
LVS 的缺点
Nginx 的优点
Nginx 的缺点
ip_hash
来解决。HAProxy 支持两种代理模式 TCP(四层)和HTTP(七层),也是支持虚拟主机的。
HAProxy 的优点能够补充 Nginx 的一些缺点,比如支持 Session 的保持,Cookie 的引导;同时支持通过获取指定的 url 来检测后端服务器的状态。
HAProxy 跟 LVS 类似,本身就只是一款负载均衡软件;单纯从效率上来讲 HAProxy 会比 Nginx 有更出色的负载均衡速度,在并发处理上也是优于 Nginx 的。
HAProxy 支持 TCP 协议的负载均衡转发,可以对 MySQL 读进行负载均衡,对后端的 MySQL 节点进行检测和负载均衡,大家可以用 LVS+Keepalived 对 MySQL 主从做负载均衡。
HAProxy 负载均衡策略非常多:Round-robin(轮循)、Weight-round-robin(带权轮循)、source(原地址保持)、RI(请求URL)、rdp-cookie(根据cookie)。
HAPorxy缺点:
(1)不支持POP/SMTP协议
(2) 不支持SPDY协议
(3) 不支持HTTP cache功能。现在不少开源的lb项目,都或多或少具备HTTP cache功能。
(4)重载配置的功能需要重启进程,虽然也是soft restart,但没有Nginx的reaload更为平滑和友好。
(5)多进程模式支持不够好
高可用——Keepalived安装部署使用详解 - 掘金
Nginx 的优点
Nginx 的缺点
ip_hash
来解决。HAProxy 支持两种代理模式 TCP(四层)和HTTP(七层),也是支持虚拟主机的。
HAProxy 的优点能够补充 Nginx 的一些缺点,比如支持 Session 的保持,Cookie 的引导;同时支持通过获取指定的 url 来检测后端服务器的状态。
HAProxy 跟 LVS 类似,本身就只是一款负载均衡软件;单纯从效率上来讲 HAProxy 会比 Nginx 有更出色的负载均衡速度,在并发处理上也是优于 Nginx 的。
HAProxy 支持 TCP 协议的负载均衡转发,可以对 MySQL 读进行负载均衡,对后端的 MySQL 节点进行检测和负载均衡,大家可以用 LVS+Keepalived 对 MySQL 主从做负载均衡。
HAProxy 负载均衡策略非常多:Round-robin(轮循)、Weight-round-robin(带权轮循)、source(原地址保持)、RI(请求URL)、rdp-cookie(根据cookie)。
HAPorxy缺点:
(1)不支持POP/SMTP协议
(2) 不支持SPDY协议
(3) 不支持HTTP cache功能。现在不少开源的lb项目,都或多或少具备HTTP cache功能。
(4)重载配置的功能需要重启进程,虽然也是soft restart,但没有Nginx的reaload更为平滑和友好。
(5)多进程模式支持不够好
高可用——Keepalived安装部署使用详解 - 掘金