Try Everything Different In My Life.

「🐞DEBUG」记录一次线上重复消费的问题-接口幂等性

2019.07.18

在一个项目中,因为硬件机器太烂,网络太烂而导致用户点击消费的时候很长时间没有收到回复,用户再次点击引发了重复消费的问题

起因

XX政府机关食堂想做一个食堂人脸消费系统,实现靠脸吃饭的目标(主要是政府里外来的务工人员太多,原来的基于卡消费系统没有做失效的功能,而且卡容易掉,当然也是我们接了人家的大项目这个小系统算是加点钱半买半送的,实现了智能化改造,毕竟对官员的升迁有用嘛😎)。

突然收到消息,一位顾客刷一次重复消费了两次。

排查

立马到现场排查,第一步定位是不是后台出错了,看了一下没有错误日志。

根据现象,再次实地现场模拟,发现问题如下:

  • 极快速的点击两次,会发送两条消费请求过来(一样的参数)
  • 现场网络很差,经常掉包(食堂,之前没有预留到机房的网线)

解决方案

APP端限制操作

APP端改成没有收到后台返回的消息,按钮处于不可点击的状态,这样就变成同步的状态

后台接口做幂等

Token机制

client端每一次调用消费接口之前要先调用token接口来获取消允许消费的token,然后再带上token的消费数据来实现消费,server端负责生成token以及负责校验token是否存在,存在则可以消费,不存在则不可以消费。这样就保证了消费接口的幂等性

如下图是实现的时序图

特点: 1.通用

「延伸」Redis实现

当一个对象(比如一个支付订单)需要且只能消费一次的时候,可以使用Redis吧这个对象的id存在redis中,如果这个对象呗消费成功,那么就将这个对象的id从redis中移除,如果没有的话则进行失败的处理逻辑

  • 异常情况

做过一个合同管理的系统,依据合同的内容生成应收,然后对着应收去收钱(即到账),但是有情况是一次只到了部分的账款,即存在一个应收对应多个到账。当然也可以判断这个账款不收全就依然可以收费,这个又得写部分的业务代码

特点: 1.具体业务具体对待对象是否可消费

「延伸」状态机制

给被消费的对象一个状态值(比如一个订单,待支付,已支付,已取消),如果这个对象被消费时候就要校验这个对象的状态值是否符合当前状态下的业务

特点: 1.和业务切合太深