mq要如何处理消息丢失、重复消费?

如果要你实现一个支付宝向余额宝转账的功能,比如:账户a从支付宝转出5000余额宝转入5000,该怎么做呢?

可能有些人会说,这还不简单,直接上图

支付宝先给账户a减5000,调用余额宝的接口给余额宝的账号b加5000。

用这种方式正常情况下是可以的,如果出现以下问题该怎么办呢?

  1. 调用余额宝api时网络失败了

  2. 调用余额宝api时网络超时了

  3. 如果余额宝api业务逻辑比较复杂,耗时比较长,用户需要长时间的等待才有结果,用户体验不好

有人说:如果调用余额宝api时网络失败了,对接口进行重试不就可以解决问题了。

:你是用同步重试,还是异步重试呢?

       如果用同步重试,即在调用余额宝api时获取返回值,如果发现失败立刻重试3次。调用一次余额宝api的耗时为n秒,重试3次的耗时则为3n秒,接口响应时间增加了两倍,增加了接口超时的风险。如果重试3次之后,还是失败该怎么处理?

        如果用异步重试,第一次调用余额宝api时,不管是成功还是失败,都直接给用户返回成功。如果是失败,后台开启一个线程,不断重试一直到成功为止。如果在不断重试的过程中服务器重启了,该怎么办?

又有人说:如果调用余额宝api时网络超时了,不知道上次请求是成功还是失败,再重试一下不行吗?

:不是不,第一.余额宝必须做幂等性设计,不然余额宝这边多转入5000怎么办?余额宝肯定不会犯这种错误。第二.同样会面临如果调用余额宝api时网络失败了的问题。

再有人说:**如果余额宝api业务逻辑比较复杂,耗时比较长,用户需要长时间的等待才有结果,用户体验不好。**改成异步就可以解决这个问题了。

**答:**改成异步可以提前告知用户结果,然后在后台通过补偿机制不断的重试,让数据达成最终一致性,这种方式对用户体验可能确实要好一些。异步处理又分为:开启线程  和 使用mq。线程处理有比较致命的弊端,如果服务器重启,线程里的数据会丢失。

接下来,我们的重点放在mq上。

余额宝给账户a减了5000之后,给指定topic1发一条消息,然后余额宝从topic1消费这条消息,给账户b加5000。

对于问题1,如果余额宝处理失败了,比如像rocketmq这类消息处理框架会把消息放入重试队列重试16次,不需要业务代码做额外的工作。

对于问题2,如果服务器重启了,由于消息保存在服务端的磁盘上,不会丢失,客户端可以通过offset从服务端重新获取消息,它能够保证消息至少被余额宝消费一次。

对于问题3,支付宝给账户a减了5000发送完消息之后,可以直接返回成功,然后余额宝作为消费者在后台默默执行,一直到成功为止。

那么问题又来了:

如果余额宝消费了消息,业务处理失败了怎么办?这个就是所谓的消息丢失。

要解决消息丢失就需要建一张消息发送表,如图:

       支付宝从账户a减5000,接着往本地消息表中写入一条消息记录,confirm_status为待确认,然后发送mq消息。注意,支付宝这边的扣款和写本地消息表要在同一事务中。

       余额宝消费消息给账户b加5000之后,调用支付宝消息确认api,修改confirm_status为已确认。

        如果余额宝这边消息丢失了,支付宝有个job会每个5分钟扫描一次本地消息表中confirm_status为待确认状态的记录,重新发送一次消息,这样余额宝又可以重新处理了。

那么还有个问题:

余额宝这边处理成功,但是由于调用 支付宝消息确认api失败,导致支付宝的job重新发送消息,余额宝重复消费了。这个就是所谓的重复消息。

重复消费要如何解决呢?

        余额宝也增加一个本地消息表,记录业务处理成功的消息。当然余额宝的账号操作和本地消息表也要在同一个事务中。    

        余额宝消费消息之后,先从余额宝的本地消息表中查一下,该消息有没有消费过,如果已经消费过了,则直接调用支付宝消息确认api,修改confirm_status为已确认,避免下次支付宝的job重复发消息。如果从余额宝的本地消息表中查到没有消费,则给账户b增加5000,同时往本地消息表写一条记录,然后调用支付宝消息确认api。

       总结:通过在mq的生产者和消费者两端分别增加本地消息表,并且在生成者端增加定时job扫描待确认状态的记录,重新发送消息,可以解决:消息丢失重复消费 问题。当然实际的支付宝向余额宝转账的场景更复杂,在高并发的情况下,可能需要用分布式锁,防止金额异常。

本文分享自微信公众号 - 苏三说技术(gh_9f551dfec941)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

KaliTools说明书+BurpSuit实战指南+SQL注入知识库+国外渗透报告

!(https://oscimg.oschina.net/oscnet/d1c876a571bb41a7942dd9752f68632e.gif"15254461546.gif")0X00KaliLinux Tools中文说明书!(https://oscimg.oschina.net/oscnet/