使用消息中间件产生的问题和解决方案

消息中间件,本身存在高可用的保障,目前发生的消息丢失情况主要是通过业务稽核的方式,发现发送端和消费端消息量不对等,存在两种场景的异常:

消息事物和数据库事务的一致性问题:数据库操作全部成功,事物进行提交,而消息事物提交失败。
消息中间件磁盘损坏问题: 消息中间件坏区,导致消息丢失。
问题总览:

  1. 消息可靠性,幂等性,可维护性。消息异常处理机制,消息积压,

问题一:消息的可靠性

1、生产者弄丢了数据

消息发送时,由于网络以及中间件,异常导致重试。
消息中间件不稳定,在发送时,发生链路被服务端中断,导致提交失败。
解决方案
(1) RabbitMQ 提供的事务功能,就是生产者发送数据之前开启 RabbitMQ 事务channel.txSelect,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重试发送消息;如果收到了消息,那么可以提交事务channel.txCommit。

// 开启事务
channel.txSelect
try {
    // 这里发送消息
} catch (Exception e) {
    channel.txRollback
    // 这里再次重发这条消息
}
// 提交事务
channel.txCommit

(2)confirm 模式:在生产者那里设置开启 confirm 模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 ack 消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你的一个 nack 接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
事务机制和 confirm 机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是 confirm 机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息 RabbitMQ 接收了之后会异步回调你的一个接口通知你这个消息接收到了。

所以一般在生产者这块避免数据丢失,都是用 confirm 机制的

2、mq弄丢了数据

mq挂了,消息中间件发生存储坏块,并进行重置后,导致消息丢失。
解决方案: 开启了持久化机制以及备份
kafka解决方案:

  • 给 topic 设置 replication.factor 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。
  • 在 Kafka 服务端设置 min.insync.replicas 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧。
  • 在 producer 端设置 acks=all:这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了
  • 在 producer 端设置 retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了。
3、消费端弄丢了数据

(1)RabbitMQ 如果丢失了数据,主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,RabbitMQ 认为你都消费了,这数据就丢了。
这个时候得用 RabbitMQ 提供的 ack 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 ack,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 ack 一把。这样的话,如果你还没处理完,不就没有 ack 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。
(2)Kafka 会自动提交 offset,那么只要关闭自动提交 offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。

问题二:消费幂等性:重复消费

消费集群在接收消息后,进行业务处理,当最后确认消费完成时,发生消息断连的情况,导致消息再次被发送给其他消费者。
方案:采用redis存储msgId 消费之前判断msgId是否存在,如果存在则消费过。消费完同时写入数据库msgId作为唯一主键。数据量大还可以采用BloomFilter算法过滤。

重复消费的几种场景:
1.生产者重复发送
生产者支持最大超时重发机制,生产者可能在超时消息中间件未应答的情况下,重发消息,导致生产消息重复。
2.消息中间自身机制
1)ActiveMq:网络闪断情况下,可能会导致broker端消息重复投递;
2)RocketMq:偏移量异步刷新机制,为了吞吐量,新增或者停止消费者都可能会导致偏移量刷新中断,或者刷新不够及时,导致消费重复。
消息去重建议:
消息去重的核心思路在消费者场景中,确保业务的幂等性或者消息消费时的去重判断是确保消息重复情况下不被重复消费的理想方式。
1.业务层面解决
业务层面可以各自采用消息处理去重方式,基于物理DB等。
2.消息读写框架上帮助统一实现方式
消息消费者框架统一支持基于Redis的去重插件功能。
主要采用BloomFilter算法+redis来实现消息ID的统一去重。

问题三:消费顺序性

一个queue只能有一个consumer。嫌效率问题的话:写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。
RabbitMQ
拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
Kafka
写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。

消息积压的解决方案

几千万条数据在 MQ 里积压。
1、可能是消费者故障了,如有故障先排查,确保其恢复消费速度
2、临时搞个消息分发程序,停掉现有的consumer,紧急扩容consumer。这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的n个queue(ps:如果对消费顺序性不考虑的话)。当然如果要考虑消费顺序的话,在分发程序中要保证消息的顺序性的话,就需要自定义规则对消息的分发到同一个queue中。比如,订单系统中,一般场景来说只需要同一笔订单编号的消息需要保证顺序性,可以对订单编号进行hash,保证相同的订单编号分发到同一个queue。

总之:要保证顺序性,一个queue只能有一个consumer。嫌效率问题的话:写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。

当然,这个功能也是可以在消息中间件解决方案中作为一个单独的功能,自动一键部署对应queue的消费者,自动分发,维护一键解决。

mq 中的消息过期失效解决方案

假设你用的是 RabbitMQ,RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是大量的数据会直接搞丢。
这个情况下,就只能对消息进行批量重导,这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去。

维护问题:消息跟踪、查看困难

  1. 消息轨迹跟踪困难: 消息的状态(已发送,已消费,等待中),收发时间,收发地址缺乏有效的跟踪。
  2. 消息内容查看困难: 由于消息存在于中间件中,而且broker众多,和数据库相比,无法有效的查看消息内容。
  3. 处理能力度量困难: 每笔消息的处理时长,消费者的处理能力,缺乏有效的度量指标,对消息队列的健康情况无法做出有效的判断。
  4. 异常场景维护困难: 在异常场景下,相比传统的数据库,处理能力有待完善, 数据可操作性较强,而消息中间件可操作行弱。比如:异常重处理: 当消费方,出现消费异常(例如业务逻辑错误,导致部分消息消费报错),需要进行快速重处理时;或者消息中间件存储损坏情况下(消息出现丢失)的异常重试能力
  5. 积压情况下的快速转移:当消息出现大量积压的情况下,如何做快速的转移和分发,
  6. 方便的统计检索能力: 能够支持多种索引条件下的检索,统计能力

问题:消息幂等性,稳定性,可维护性

消息幂等:提供统一的去重解决方案,消息消费者框架支持基于Redis的去重插件功能
消息轨迹监控:支持消息在生产者、消息中间件、消费者落地轨迹查询,方便定位分析消息重复、丢失出现的故障点
消息持久化:消息使用客户端统一支持消息持久化功能
统一提供消息持异常处理机制:消息使用客户端统一提供异常控制机制功能
消息内容查询:支持将统一持久化的内容,通过统一转码方式解析提供内容查询,方便业务数据存在异常的定位分析
消息监控:提供消息消费轨迹、消息集群性能监控的功能

总体解决方案

1.框架统一生产者异常处理和重试机制
(1)生产者业务处理发送消息到消息中间件
(2)消息发送存在IO、超时等异常情况,框架捕获该异常,重试发送该笔消息
(3)重试发送机制,默认尝试3次在持续异常的情况下,同时支持可配置
(4)对于超出重试机制的情况,框架提供统一的异常处理接口,该接口包括记录“主题、消息体、异常堆栈”,其中具体记录处理异常类由业务配置
2.框架统一消费者异常处理和重试机制
(5)消费者框架从消息中间件连接中获取并消费消息
(6)消费者客户端会判断业务处理返回结果,捕获异常信息
(7)对于异常情况,框架客户端支持重试投递消息继续消费,异常重试次数默认为3次,支持可配置
(8)如果重试次数超出了重试配置数量,则将该消息写入死信队列,供后续处理
(9)异常重试失败之后,框架层面统一捕获该异常,通过统一的异常接口,捕获并记录异常信息,具体记录处理类由业务配置
3.对于标准化异常进入死信队列的数据

4.消息积压场景自动分发和扩容
提前写好自动分发程序,维护自动扩容,自动分发。

消息中间件平台包括:
1.消息控制台
消息中间件配置管理
可视化方式支持不同的开源消息中间件broker资源、主题和相应监控配置管理。
消息中间件监控管理
可视化方式支持对底层消息中间件监控,其中包括集群资源、集群的处理性能、客户端接入量、客户端处理性能、消息的流经轨迹和消息内容的监控和查询。
系统管理
支持消息中间件平台用户、权限、租户和认证管理。
2.消息客户端
1)消息生产者
封装统一的消息生产者框架,支持对消息中间件接入的认证、发送消息、消息统一序列化、切面日志、异常处理的能力。
2)消息消费者
封装统一的消息消费者框架,支持对消息中间件接入的认证、消费消息、消息统一反序列化、切面日志、异常处理和去重处理的能力。
3.消息注册中心
注册中心主要实现消息中间件的核心路由、集中管控的能力,不同的消息中间件注册中心实现机制不同,Msgframe主要基于不同的中间件适配访问注册中心的服务,统一屏蔽底层中间件差异性,为上层控制台提供统一的配置管理和客户端访问能力。
4.消息broker集群
消息中间件的核心主要实现broker集群能力,这部分不同的消息中间件有不同的实现集群方式,ActiveMq、kafka、RocketMq等。
5.消息持久化和搜索
支持消息从消息中间件的生产、消费、broker三个关键点采集消息内容数据,通过消息采集模块统一同步至指定存储,基于存储之上提供消息内容ES搜索能力,支持消息轨迹、消息内容、消息监控的分析查询。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值