RocketMQ应用篇

生产问题

RocketMQ生产环境主题扩分片后遇到的坑

问题复盘

潜在原因:DefaultCluster 集群进行过一次集群扩容,从原来的一台消息服务器( broker-a )额外增加一台broker服务器( broker-b ),但扩容的时候并没有把原先的存在于 broker-a 上的主题、消费组扩容到 broker-b 服务器。

触发原因:接到项目组的扩容需求,将集群队列数从4个扩容到8个,这样该topic就在集群的a、b都会存在8个队列,但Broker不允许自动创建消费组(订阅关系),消费者无法从broker-b上队列上拉取消息,导致在broker-b队列上的消息堆积,无法被消费。

解决办法:运维通过命令,在broker-b上创建对应的订阅消息,问题解决。

经验教训:集群扩容时,需要同步在集群上的topic.json、subscriptionGroup.json文件。

RocketMQ 理论基础,消费者向 Broker 发起消息拉取请求时,如果broker上并没有存在该消费组的订阅消息时,如果不允许自动创建(autoCreateSubscriptionGroup 设置为 false),默认为true,则不会返回消息给客户端,其代码如下:

RocketMQ生产环境主题扩分片后遇到的坑

RocketMQ一行代码造成大量消息丢失

由于项目组并没有对消息发送失败做任何补偿,因为Broker端的发送快速失败机制(TIMEOUT_CLEAN_QUEUE),导致丢失消息发送失败,故需要对这个问题进行深层次的探讨,并加以解决

RocketMQ 消息发送高可用设计一个非常关键的点,重试机制,其实现是在 for 循环中使用 try catch 将 sendKernelImpl 方法包裹,就可以保证该方法抛出异常后能继续重试。从上文可知,如果 SYSTEM_BUSY 会抛出 MQBrokerException,但发现只有上述几个错误码才会重试,因为如果不是上述错误码,会继续向外抛出异常,此时 for 循环会被中断,即不会重试。

这里非常令人意外的是连 SYSTEM_ERROR 都会重试,却没有包含 SYSTEM_BUSY,显然违背了快速失败的设计初衷,故笔者断定,这是 RocketMQ 的一个BUG,将 SYSTEM_BUSY 遗漏了,后续会提一个 PR,增加一行代码,将 SYSTEM_BUSY 加上即可

RocketMQ 一行代码造成大量消息丢失

RocketMQ消息发送常见错误与解决方案

最终一致性的解决方案

本地事务表重试直至成功型

去哪儿网使用了这种分布式事务解决方案

我们知道,使用异步消息 Consumer 端需要实现幂等

幂等有两种方式,

  • 一种方式是业务逻辑保证幂等。比如接到支付成功的消息订单状态变成支付完成,如果当前状态是支付完成,则再收到一个支付成功的消息则说明消息重复了,直接作为消息成功处理(也就是状态机幂等的方案)
  • 另外一种方式如果业务逻辑无法保证幂等,则要增加一个去重表或者类似的实现(去重表)

异步消息

对于 producer 端在业务数据库的同实例上放一个消息库(消息发送的管理表,每条消息给一个status状态字段,发送成功则修改为success,发送失败则定时任务一直扫描它直到发送成功),让发消息和业务操作在同一个本地事务里(也就是让业务操作和往消息表中插入一条状态为init状态的消息,这两组动作在一个事务中)

发消息的时候消息并不立即发出,而是向消息库插入一条消息记录,然后在事务提交的时候再异步将消息发出(一般是通过定时任务扫描实现异步发送),发送消息如果成功则将消息库里的消息删除,如果遇到消息队列服务异常或网络问题,消息没有成功发出那么消息就留在这里了,会有另外一个服务不断地将这些消息扫出重新发送

总结起来,很多最终一致性解决方案的根本原理是类似的,也就是将分布式事务转换为多个本地事务,然后依靠重试/补偿等方式达到最终一致性

废单MQ回滚型

蘑菇街交易创建过程中的分布式一致性方案

交易创建的一般性流程

我们把交易创建流程抽象出一系列可扩展的功能点,每个功能点都可以有多个实现(具体的实现之间有组合/互斥关系)。把各个功能点按照一定流程串起来,就完成了交易创建的过程。

面临的问题

每个功能点的实现都可能会依赖外部服务。那么如何保证各个服务之间的数据是一致的呢?比如锁定优惠券服务调用超时了,不能确定到底有没有锁券成功,该如何处理?再比如锁券成功了,但是扣减库存失败了,该如何处理?

方案选型

服务依赖过多,会带来管理复杂性增加和稳定性风险增大的问题。试想如果我们强依赖 10 个服务,9 个都执行成功了,最后一个执行失败了,那么是不是前面 9 个都要回滚掉?这个成本还是非常高的。

所以在拆分大的流程为多个小的本地事务的前提下,对于非实时、非强一致性的关联业务写入,在本地事务执行成功后,我们选择发消息通知、关联事务异步化执行的方案。

消息通知往往不能保证 100% 成功;且消息通知后,接收方业务是否能执行成功还是未知数。前者问题可以通过重试解决;后者可以选用事务消息来保证。

但是事务消息框架本身会给业务代码带来侵入性和复杂性,所以我们选择基于 DB 事件变化通知到 MQ 的方式做系统间解耦,通过订阅方消费 MQ 消息时的 ACK 机制,保证消息一定消费成功,达到最终一致性。由于消息可能会被重发,消息订阅方业务逻辑处理要做好幂等保证。

所以目前只剩下需要实时同步做、有强一致性要求的业务场景了。在交易创建过程中,锁券和扣减库存是这样的两个典型场景。

要保证多个系统间数据一致,乍一看,必须要引入分布式事务框架才能解决。但引入非常重的类似二阶段提交分布式事务框架会带来复杂性的急剧上升;

解决方案为:

电商领域,绝对的强一致是过于理想化的,我们可以选择准实时的最终一致性。

  1. 我们在交易创建流程中,首先创建一个不可见订单,然后在同步调用锁券和扣减库存时,针对调用异常(失败或者超时),发出废单消息到MQ
  2. 如果废单消息发送失败,本地会做时间阶梯式的异步重试;优惠券系统和库存系统收到消息后,会进行判断是否需要做业务回滚,这样就准实时地保证了多个本地事务的最终一致性

这里就是MQ的一个应用场景,当然都需要配合幂等消费来记性

参考链接(最终一致性的6种实现方案):https://www.cnblogs.com/Vincent-yuan/p/16074577.html

总结起来,很多最终一致性解决方案的根本原理是类似的,也就是将分布式事务转换为多个本地事务,然后依靠重试等方式达到最终一致性

TCC两阶段提交

支付宝及蚂蚁金融云的分布式服务 DTS 方案

充分保障分布式环境下高可用性、高可靠性的同时兼顾数据一致性的要求,其最大的特点是保证数据最终一致 (Eventually consistent)

异步回调+主动回查

具体方式为:

主流程线程,将笨重的操作甩给后续的异步流程,后续异步流程处理结束后主动回调通知结果,并且还有一个兜底策略,主流程周期性的轮询

应用场景:

这种多用于异步流程中的业务逻辑比较重的场景

从整体角度考虑,对于这种重操作做成异步通知结果是比较合理的,不然就有可能造成接口超时等问题

支付回调的逻辑,除了等第三方回调,还会提供主动查询结果的功能,这种是经典的分布式最终一致性解决方案

场景一

支付回调的逻辑,除了等第三方回调,还会提供主动查询结果的功能

场景二

好医生商城交易

背景:平安好医生有自己的商城,在平安好医生商城购物,可以使用平安消金的消费额度

实现:整个实现采用流行的异步化的实现

好医生商城下订单的信息,是给到了APS保存,所以APS维护了订单的状态信息。

下单成功后,好医生商城侧,发生支付请求,支付请求经TC转入AMS/TDC。AMS/TDC接收到支付交易请求时,立马先完整的保存一遍交易信息,并在交易主表中记录支付状态为初始化状态

然后AMS/TDC用异步线程开启内部的后续支付处理逻辑,同时同步流程直接给好医生返回了

3秒后,消金自己的前端会发生轮训,通过支付单号来AMS/TDC查询该笔交易的支付情况

常见问题处理

几百万消息积压

首先,是要解决积压的原因,一般是消费端线程阻塞报错了

其次,处理积压的消息,比如原来只有3台broker,6个消费者,当3台broker积压了900w条消息,那么这是可以借调10台broker,30台消费者。然后另起10个消费者把通过批量拉取的方式,快速的将3台broker中的900w消息分发到借调的10台broker中,然后在启动另外30台去快速读取10台broker中的数据

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值