面试官:写接口时有考虑过接口幂等性问题吗?

在日常开发接口的过程中,接口的幂等性问题是我们必须要考虑的,否则会带来很严重的后果。比如在支付场景中,用户不小心点了两次,然后就发现被扣了两次钱,这显然是很严重的问题。因此考虑接口的幂等性是很重要的。

1. 什么是接口幂等性

维基百科解释:

  幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于`抽象代数`中。

  在编程中一个幂等操作的特点是其任意多次执行所产生的影响钧与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成高边。例如,"setTrue()"函数就是一个幂等函数,无论多次执行,其结果都是一样的。

通俗解释:
接口幂等性就是用户对同一接口发起了一次或多次请求之后,对数据的影响是不变的,不会因为多次请求而产生不同的结果。

2.什么情况下要考虑接口幂等性问题

  • select查询操作具有天然幂等性

    • 每次查询不会对数据产生影响。
  • insert插入操作时

    • 自增主键,没有幂等性

      • 重复插入会有多条记录被插入
    • 业务主键,具有幂等性

      • 业务主键具有唯一性,因此重复插入也只会有一条数据成功。
  • delete删除操作时

    • 绝对删除,具有幂等性

      # 不管执行多少次,都是删除这一条
      delete from user where id = 1
      
    • 相对删除,不具有幂等性

      # 这个带范围的删除,每次操作,对结果产生的影响都可能不一样,所以不具有幂等性
      delete from user where id > 5
      
  • update更新操作时,和删除操作同理

    • 绝对更新,具有幂等性

      # 这条sql不管执行多少次,对结果的影响都是一样的。
      update user set username='尚硅谷' where id = 1
      
    • 相对更新,不具有幂等性

      # 这条sql每次执行,对结果的影响可能都不相同,所以不具有幂等性
      update user set username='尚硅谷' where id > 5
      

3. 幂等的解决方案

对于和Web段交互的接口,我们可以在前端拦截一部分,例如防止表单重复提交,按钮置灰,隐藏,不可点击等。但是稍微懂点技术的人都知道,可以直接去调你的后台接口,所以前端的这种方式只能过滤掉一些普通用户的幂等操作。

那么后端应该怎么处理呢?主要可以通过以下几个方面进行考虑:

3.1 Redis 实现幂等

用户下单支付时,不小心点了两次,这时如果没有做幂等性处理,后台就会收到多次请求,从而会产生两次扣款。


  • 用户发送请求到服务端
  • 尝试用SETNX 指令设置key 和 value,因为SETNX只能放置成功不存在的 key
    • 放置成功,设置Key的过期时间,执行业务逻辑,返回结果
    • 放置失败,返回失败结果

3.2 状态机实现幂等

比如电商的订单,订单支付状态 0-待支付 1-支付中 2-支付成功 3-支付失败

设计的时候,状态一般都是单向改变的,也就是一个订单的状态变化状态是 0 --> 1 --> 2 或者 0--> 1 --> 3

也就是说1-支付中的前置状态必须是0-待支付2-支付成功的前置状态必须是1-支付中,这样我们在更新的时候就可以加上比较条件,判断当前要更新的订单前置状态是不是我们规定的前置状态,如果不是,就不会更新了,这样多次调用也只会执行一次。

-- 支付订单时,加上判断条件,判断当前这个订单的状态是不是支付中的前置状态 0-待支付
update order set status = 1 where status= 0 and order_id='88888888'

状态枚举

public enum OrderStatusEnum {

    UN_PAYMENT(0,0,"待支付"),
    PAYING(0,1,"支付中"),
    PAY_SUCCESS(1,2,"支付成功"),
    PAY_FAIL(0,3,"支付失败"),
    ;

    //前置状态
    private int preStatus;

    //当前状态值
    private int status;

    //状态描述
    private String desc;


    OrderStatusEnum(int preStatus, int status, String desc) {
        this.preStatus = preStatus;
        this.status = status;
        this.desc = desc;
    }
}

3.3 数据库去重表

这种方式是利用mysql唯一索引的特性。

处理流程为:

  1. 建立一张去重表,为其中一个或多个字段建立唯一索引
  2. 客户端请求服务端,服务端将请求中的一些信息插入去重表
  3. 因为表中建立了唯一索引,
    1. 如果插入成功,表示是第一次请求,则可以继续执行后面的业务逻辑。
    2. 如果插入失败,表示已经执行过当前请求,直接返回。

流程图如下

一般在实际使用过程中,插入操作需要加上事务,如果业务逻辑出现错误执行失败,防重表中数据需要回滚。

3.4 防重Token令牌

对于客户端的重复点击,或者超时重试的情况,也可以用 Token机制实现防止重复提交。简单来说,就是调用方在调用接口之前,先向后端发请求获取一个全局唯一的ID(Token),请求的时候要携带这个Token(一般是放在Headers中),后端要对这个Token进行校验,以Token作为Key,用户信息作为Value到Redis中进行键值对内容比较,如果Key存在且Value匹配就执行删除命令,然后正常执行后面的业务逻辑。如果不存在对应的Key或Value不匹配,就返回重复执行的信息,这样就可以保证幂等性操作。

整体的处理流程:

  1. 客户端先向服务端请求获取token,服务端返回token,并将token作为Key,用户信息作为Value存入Redis中(注意设置过期时间)
  2. 客户端请求业务接口,并携带token
  3. 服务端接收到请求后对token进行校验,token作为Key,在Redis中查询信息,并进行校验
    1. 如果Redis中存在Key,对比Value,如果匹配,就删除这个Key,并执行业务逻辑
    2. 如果查出来的Value值不匹配,或者不存在这个Key,则直接返回重复消费的提示信息

流程图:

注意,在并发情况下,执行 Redis 查找数据与删除需要保证原子性,否则很可能在并发下无法保证幂等性。其实现方法可以使用分布式锁或者使用 Lua 脚本来注销查询与删除操作。

4.总结

幂等性是开发中一个很常见,也是很重要的问题,尤其是在支付这种与钱相关的业务,保证幂等性是非常重要的,实现幂等性首先要理解业务需求,根据业务需求来确定用哪种方式实现幂等性才比较合理。

本文主要介绍面试时回答该问题的一些思路,后续会出用代码实现的具体解决方案。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容