前言

首先说一点,企业中最常用的实际上既不是RocketMQ,也不是Kafka,而是RabbitMQ。

RocketMQ很强大,但主要是阿里推广自己的云产品而开源出来的一款消息队列,其实中小企业用RocketMQ的没有想象中那么多。

深层次的原因在于兔宝在中小企业普及更早,经受的考验也更久,很容易产生「回头客」,当初随RabbitMQ成长的一批人才如今大部分都已成为企业中的中坚骨干,技术选型亲睐RabbitMQ的几率就更高。

至于Kafka,主要还是用在大数据和日志采集方面,除了一些公司有特定的需求会使用外,对消息收发准确率要求较高的公司依然是以RabbitMQ作为企业级消息队列的首选。

工作这么多年我自身的感受是,RabbitMQ经久不衰,除非后续其他消息中间件有与众不同的使用体验,否则依然是RabbitMQ的占有率更高。

所以准备进入软件行业的小伙伴,我建议有必要系统的先把RabbitMQ学好,然后再学习其他消息中间件扩展视野,他们的原理大同小异,是可以触类旁通的。

两个概念

RabbitMQ避免消息丢失的方法主要是利用消息确认机制和手动签收机制,所以有必要把这两个概念搞清楚。

1、消息确认机制

主要是生产者使用的机制,用来确认消息是否被成功消费。

配置如下:

spring:
rabbitmq:
address: 192.168.x.x:xxxx
virtual-host: /
username: guest
password: guest
connection-timeout: 5000
publisher-confirms: true # 消息成功确认
publisher-returns: true # 消息失败确认
template:
mandatory: true # 手动签收机制

这样,当你实现RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback这两个接口的方法后,就可以针对性地进行消息确认的日志记录,之后做进一步的消息发送补偿,以达到接近100%投递的目的。

伪代码如下:

@Component
@Slf4j
public class RabbitMQSender implements RabbitTemplate.ConfirmCallback,
RabbitTemplate.ReturnCallback { /**
* 发送消息
*/
public void sendOrder(Order order) {
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this); // 发送消息
rabbitTemplate.convertAndSend(xx, xx, order, xx);
} /**
* 成功接收后的回调
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String s) { // 如果成功接收了,这里可以对日志表的消息收发状态做更新。
// .... } /**
* 失败后的回调
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) { // 如果失败了,这里可以对日志表的消息收发状态做更新,之后通过任务调度去补偿发送。
// .... }
}

2、消息签收机制

RabbitMQ的消息是自动签收的,你可以理解为快递签收了,那么这个快递的状态就从发送变为已签收,唯一的区别是快递公司会对物流轨迹有记录,而MQ签收后就从队列中删除了。

企业级开发中,RabbitMQ我们基本都开启手动签收方式,这样可以有效避免消息的丢失。

前文中已经在生产者开启了手动签收机制,那么作为消费方,也要设置手动签收。

配置如下:

spring:
rabbitmq:
address: 192.168.x.x:xxxx
virtual-host: /
username: guest
password: guest
connection-timeout: 5000
listener:
simple:
concurrency: 5 # 并发数量
max-concurrency: 10 # 最大并发数量
acknowledge-mode: manual # 开启手动签收
prefetch: 1 # 限制每次只消费一个(一个线程),上面配置5,也就是能一次接收5个

消费监听时,手动签收就一行代码,伪代码如下:

@RabbitListener(xxx)
public void onOrderMessage(@Payload Order order, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception { // .... // 手动签收
channel.basicAck(tag, false); }

消息丢失

两个概念搞清楚后,就可以来学习消息丢失的问题和处理方案了。

1、出现原因

消息丢失的原因无非有三种:

1)、消息发出后,中途网络故障,服务器没收到;

2)、消息发出后,服务器收到了,还没持久化,服务器宕机;

3)、消息发出后,服务器收到了,消费方还未处理业务逻辑,服务却挂掉了,而消息也自动签收,等于啥也没干。

这三种情况,(1) 和 (2)是由于生产方未开启消息确认机制导致,(3)是由于消费方未开启手动签收机制导致。

2、解决方案

1)、生产方发送消息时,要try...catch,在catch中捕获异常,并将MQ发送的关键内容记录到日志表中,日志表中要有消息发送状态,若发送失败,由定时任务定期扫描重发并更新状态;

2)、生产方publisher必须要加入确认回调机制,确认成功发送并签收的消息,如果进入失败回调方法,就修改数据库消息的状态,等待定时任务重发;

3)、消费方要开启手动签收ACK机制,消费成功才将消息移除,失败或因异常情况而尚未处理,就重新入队。

其实这就是前面阐述两个概念时已经讲过的内容,也是接近100%消息投递的企业级方案之一,主要目的就是为了解决消息丢失的问题。

消息重复

1、出现原因

消息重复大体上有两种情况会出现:

1)、消息消费成功,事务已提交,签收时结果服务器宕机或网络原因导致签收失败,消息状态会由unack转变为ready,重新发送给其他消费方;

2)、消息消费失败,由于retry重试机制,重新入队又将消息发送出去。

2、解决方案

网上大体上能搜罗到的方法有三种:

1)、消费方业务接口做好幂等;

2)、消息日志表保存MQ发送时的唯一消息ID,消费方可以根据这个唯一ID进行判断避免消息重复;

3)、消费方的Message对象有个getRedelivered()方法返回Boolean,为TRUE就表示重复发送过来的。

我这里只推荐第一种,业务方法幂等这是最直接有效的方式,(2)还要和数据库产生交互,(3)有可能导致第一次消费失败但第二次消费成功的情况被砍掉。

消息积压

1、出现原因

消息积压出现的场景一般有两种:

1)、消费方的服务挂掉,导致一直无法消费消息;

2)、消费方的服务节点太少,导致消费能力不足,从而出现积压,这种情况极可能就是生产方的流量过大导致。

2、解决方案

1)、既然消费能力不足,那就扩展更多消费节点,提升消费能力;

2)、建立专门的队列消费服务,将消息批量取出并持久化,之后再慢慢消费。

(1)就是最直接的方式,也是消息积压最常用的解决方案,但有些企业考虑到服务器成本压力,会选择第(2)种方案进行迂回,先通过一个独立服务把要消费的消息存起来,比如存到数据库,之后再慢慢处理这些消息即可。

使用心得

这里单独讲一下本人在工作中使用RabbitMQ的一些心得,希望能有所帮助。

1)、消息丢失、消息重复、消息积压三个问题中,实际上主要解决的还是消息丢失,因为大部分公司遇不到消息积压的场景,而稍微有水准的公司核心业务都会解决幂等问题,所以几乎不存在消息重复的可能;

2)、消息丢失的最常见企业级方案之一就是定时任务补偿,因为不论是SOA还是微服务的架构,必然会有分布式任务调度的存在,自然也就成为MQ最直接的补偿方式,如果MQ一定要实现100%投递,这种是最普遍的方案。但我实际上不推荐中小企业使用该方案,因为凭空增加维护成本,而且没有一定规模的项目完全没必要,大家都小看了RabbitMQ本身的性能,比如我们公司,支撑一个三甲医院,也就是三台8核16G服务器的集群,上线至今3年毫无压力;

3)、不要迷信网上和培训机构讲解的生产者消息确认机制,也就是前面两个概念中讲到的ConfirmCallback和ReturnCallback,这种机制十分降低MQ性能,我们团队曾遇到过一次流量高峰期带来的MQ传输及消费性能大幅降低的情况,后来发现是消息确认机制导致,关闭后立马恢复正常,从此以后都不再使用这种机制,MQ运行十分顺畅。同时我们会建立后台管理实现人工补偿,通过识别业务状态判断消费方是否处理了业务逻辑,毕竟这种情况都是少数,性能和运维成本,在这一块我们选择了性能;

4)、我工作这些年使用RabbitMQ没见过自动签收方式,一定是开启手动签收;

5)、手动签收方式你在网上看到的教程几乎都是处理完业务逻辑之后再手动签收,但实际上这种用法是不科学的,在分布式的架构中,MQ用来解耦和转发是非常常见的,如果是支付业务,往往在回调通知中通过MQ转发到其他服务,其他服务如果业务处理不成功,那么手动签收也不执行,这个消息又会入队发给其他消费者,这样就可能在流量洪峰阶段因为偶然的业务处理失败造成堵塞,甚至标题所讲的三种问题同时出现,这样就会得不偿失。

不科学的用法:在处理完业务逻辑后再手动签收,否则不签收,就好比客人进店了你得买东西,否则不让走。

@RabbitListener(xxx)
public void onOrderMessage(@Payload Order order, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception { // 处理业务
doBusiness(order); // 手动签收
channel.basicAck(tag, false); }

科学的用法:不论业务逻辑是否处理成功,最终都要将消息手动签收,MQ的使命不是保证客人进店了必须消费,不消费就不让走,而是客人能进来就行,哪怕是随便看看也算任务完成。

@RabbitListener(xxx)
public void onOrderMessage(@Payload Order order, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception { try {
// 处理业务
doBusiness(order);
} catch(Exception ex) {
// 记录日志,通过后台管理或其他方式人工处理失败的业务。
} finally {
// 手动签收
channel.basicAck(tag, false);
} }

可能有人会问你这样不是和自动签收没区别吗,NO,你要知道如果自动签收,出现消息丢失你连记录日志的可能都没有。

另外,为什么一定要这么做,因为MQ是中间件,本身就是辅助工具,就是一个滴滴司机,保证给你送到顺便说个再见就行,没必要还下车给你搬东西。

如果强加给MQ过多压力,只会造成本身业务的畸形。我们使用MQ的目的就是解耦和转发,不再做多余的事情,保证MQ本身是流畅的、职责单一的即可。

总结

本篇主要讲了RabbitMQ的三种常见问题及解决方案,同时分享了一些作者本人工作中使用的心得,我想网上是很难找到的,如果哪一天用到了,不妨再打开看看,也许能避免一些生产环境可能出现的问题。

我总结下来就是三点:

1)、消息100%投递会增加运维成本,中小企业视情况使用,非必要不使用;

2)、消息确认机制影响性能,非必要不使用;

3)、消费者先保证消息能签收,业务处理失败可以人工补偿。

工作中怕的永远不是一个技术不会使用,而是遇到问题不知道有什么解决思路。

分享

多年工作及学习过程中在云笔记中记录了很多内容,我闲暇之余都做了下整理,本篇也是其中之一,有感兴趣的朋友可以私信我获取,什么时候用到了翻开说不定就能节省很多时间。


原创文章纯手打,觉得有一滴滴帮助就请举手之劳点个推荐吧~

持续分享工作中的真实经验和心得体会,喜欢的话就点个关注吧~

RabbitMQ:消息丢失 | 消息重复 | 消息积压的原因+解决方案+网上学不到的使用心得的更多相关文章

  1. 实际业务处理 Kafka 消息丢失、重复消费和顺序消费的问题

    关于 Kafka 消息丢失.重复消费和顺序消费的问题 消息丢失,消息重复消费,消息顺序消费等问题是我们使用 MQ 时不得不考虑的一个问题,下面我结合实际的业务来和你分享一下解决方案. 消息丢失问题 比 ...

  2. Kafka leader副本选举与消息丢失场景讨论

    如果某个broker挂了,leader副本在该broker上的分区就要重新进行leader选举.来简要描述下leader选举的过程 1.4.1 KafkaController会监听ZooKeeper的 ...

  3. MQ的消息丢失/重复/积压的问题解决

    在我们实际的开发过程中,我们肯定会用到MQ中间件,常见的MQ中间件有kafka,RabbitMQ,RocketMQ.在使用的过程中,我们必须要考虑这样一个问题,在使用MQ的时候,我们怎么确保消息100 ...

  4. rabbitmq 重复ACK导致消息丢失

    rabbitmq 重复确认导致消息丢失 背景 rabbitmq 在应用场景中,大多采用工作队列 work-queue的模式. 在一个常见的工作队列模式中,消费者 worker 将不断的轮询从队列中拉取 ...

  5. RabbitMQ,RocketMQ,Kafka 事务性,消息丢失和消息重复发送的处理策略

    消息队列常见问题处理 分布式事务 什么是分布式事务 常见的分布式事务解决方案 基于 MQ 实现的分布式事务 本地消息表-最终一致性 MQ事务-最终一致性 RocketMQ中如何处理事务 Kafka中如 ...

  6. RabbitMQ消息丢失问题和保证消息可靠性-消费端不丢消息和HA(二)

    继续上篇文章解决RabbitMQ消息丢失问题和保证消息可靠性(一) 未完成部分,我们聊聊MQ Server端的高可用和消费端如何保证消息不丢的问题? 回归上篇的内容,我们知道消息从生产端到服务端,为了 ...

  7. 解决RabbitMQ消息丢失问题和保证消息可靠性(一)

    原文链接(作者一个人):https://juejin.im/post/5d468591f265da03b810427e 工作中经常用到消息中间件来解决系统间的解耦问题或者高并发消峰问题,但是消息的可靠 ...

  8. RabbitMQ 消息顺序、消息幂等、消息重复、消息事务、集群

    1. 消息顺序 场景:比如下单操作,下单成功之后,会发布创建订单和扣减库存消息,但扣减库存消息执行会先于创建订单消息,也就说前者执行成功之后,才能执行后者. 不保证完全按照顺序消费,在 MQ 层面支持 ...

  9. RabbitMQ,为应对消息从发送到消费,各个环节消息丢失的解决方案

      1.发送方   为保证消息到达exchange,在这个过程中不丢失.  用事务或者发送方确认机制  见<RabbitMQ实战指南>4.8节 2.为保证消息不会因为到达exchange后 ...

随机推荐

  1. 半导体行业如何保持高效远程办公?因果集群(Causal Clustering)了解一下!

    什么是因果集群?因果集群是下一代多站点复制技术.它支持数据中心的分布式系统集群模型.借助于因果集群技术,可以让远程工作团队成员体验到更卓越的性能和更健壮的复制功能,确保您的团队始终以高效状态工作. 因 ...

  2. python中的sort用法

    内置的列表类型提供sort的方法 可以根据多项指标给list实例中的元素排序.在默认情况下,sort方法总是按照自然升序排列列表内的元素 #升序排列 list1=[2,3,1,2,5] list1.s ...

  3. 为什么 Nginx 比 Apache 更厉害?

    关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 为什么Nginx在处理高并发方面要优于httpd,我们先从两种web服务器的工作原理以及工作模 ...

  4. kubevirt在360的探索之路(k8s接管虚拟化)

    来源:360云计算 KubeVirt是一个Kubernetes插件,在调度容器之余也可以调度传统的虚拟机.它通过使用自定义资源(CRD)和其它 Kubernetes 功能来无缝扩展现有的集群,以提供一 ...

  5. linux网络配置,查看IP地址

    linux等等学习参考博客:https://www.cnblogs.com/pyyu/p/9276851.html 1.在安装好的linux上面输入cd /etc/sysconfig/network- ...

  6. Java学习笔记-基础语法Ⅹ-进程线程

    学习快一个月了,现在学到了黑马Java教程的300集 打印流的特点: 只负责输出数据,不负责读取数据 有自己的特有方法 字节打印流:PrintStream,使用指定的文件名创建新的打印流 import ...

  7. Redis设计与实现3.3:集群

    集群 这是<Redis设计与实现>系列的文章,系列导航:Redis设计与实现笔记 集群中的节点 创建集群 通过 CLUSTER NODE 命令可以查看当前集群中的节点.刚启动时,默认每一台 ...

  8. MySQL8小时问题

    一.问题 获取MySQL连接,8小时内无请求自动断开连接. 二.解决 2.1 分析 MySQL服务器默认的"wait_timeout"是28800秒即8小时,意味着如果一个连接的空 ...

  9. 使用ROOT用户运行Jenkins

    !本教程仅适用于以YUM.APT等包管理器安装的Jenkins. 使用ROOT用户运行Jenkins,以保证Jenkins以最高权限执行任务. 注:会存在安全风险! 1.找到Jenkins启动脚本 通 ...

  10. el-form 中的数组表单验证(数组可动态添加删除)

    除了一些简单的表单验证之外,我们还会有一些稍微复杂点的多层级表单的验证,如下图所示可点击添加,删除对数组进行操作,当点击确定时需要验证每一条form-item不能为空 其tempalte部分主要代码如 ...