前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分布式服务接口的幂等性

分布式服务接口的幂等性

原创
作者头像
JavaEdge
修改2020-09-11 12:55:53
2.1K0
修改2020-09-11 12:55:53
举报
文章被收录于专栏:JavaEdgeJavaEdge

文章收录在我的 GitHub 仓库,欢迎Star/fork: Java-Interview-Tutorial https://github.com/Wasabi1234/Java-Interview-Tutorial

1 问题背景

可能你最先想到的就是使用数据库的事务保证。比如创建订单时,要同时往订单表和订单商品表中插入数据,那这些插入数据的INSERT必须在一个数据库事务中执行,数据库的事务可以确保:执行这些INSERT语句,共赴生死!

假如你有个服务部署在5台机器上,有个付款接口。然后用户在前端操作时,一份订单不小心发起了两次支付请求,然后这俩请求分散在了这个服务部署的不同的机器上,结果一个订单扣款扣两次,gg!

订单系统调用支付系统进行支付,结果不小心网络超时,然后订单系统走了前面我们看到的那个重试机制,给你重试了一把,好,支付系统收到一个支付请求两次,而且因为负载均衡算法落在了不同的机器了!

但这还是有很多大坑存在。一个分布式系统中的某个接口,要保证幂等性,如何保证?

2 如何避免重复下单?

评论里有同学说,前端页面直接防止用户重复提交表单。没啥毛病,但网络错误会导致重传,很多RPC框架、网关都有自动重试机制,所以重复请求无法避免。

所以问题归结于如何保证服务接口的幂等性。

2.1 怎么判断请求是否重复

  1. 插入订单数据前,先查一下订单表里面有没有重复订单? 这可不好啊,因为你很难用SQL的条件来定义“重复的订单”
  2. 订单用户一样、商品一样、价格一样,就是重复订单? 万一这搞笑用户就是连续下了俩一模一样订单呢

2.2 最佳实践

保证幂等性主要有如下几点

每个请求须有唯一标识

比如订单支付请求,得包含订单id,一个订单id最多支付一次

每次处理完请求后,须有记录标识该请求已被处理

比如说常见的方案是在MySQL中记录一个状态字段。比如支付之前记录一条这个订单的支付流水

每次接收请求判断之前是否处理过

若有一个订单已支付,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId已存在,唯一键约束生效,报错插入不进去。就不会再重复扣款。

在往db插条记录时,一般不提供主键,而由数据库在插入时自动生成一个主键。这样重复的请求就会导致插入重复数据。

MySQL的主键自带唯一性约束,若在一条INSERT语句提供主键,且该主键值在表中已存在,则该条INSERT会执行失败。因此可利用db的“主键唯一约束”,在插数据时带上主键,以此实现创建订单接口的幂等性。

给订单服务添加一个“orderId生成”的接口,无参,返回值就是一个全局唯一订单号。在用户进入创建订单页面时,前端页面先调用该orderId生成接口得到一个订单号,在用户提交订单的时候,在创建订单的请求中携带该订单号。

该订单号其实就是订单表的主键,如此一来,重复请求中带的都是同一订单号。订单服务在订单表中插入数据的时候,执行的这些重复INSERT语句中的主键,也都是同一个订单号。而数据库的唯一约束可保证,只有一次INSERT执行成功。

实际要结合业务,比如使用Redis,用orderId作为唯一键。只有成功插入这个支付流水,才可执行扣款。

要求是支付一个订单,必须插入一条支付流水,order_id建立一个唯一键unique key

你在支付一个订单前,先插入一条支付流水,order_id就已经传过去了

你就可以写一个标识到Redis中,set order_id payed,当重复请求过来时,先查Redis的order_id对应的value,若为payed说明已支付,就别重复支付了!

然后再重复支付订单时,写尝试插入一条支付流水,db会报错unique key冲突,整个事务回滚即可。

保存一个是否处理过的标识也可以,服务的不同实例可以一起操作Redis。

  • 幂等创建订单的时序图

如果因为重复订单导致插入订单表失败,订单服务不要把这个错误返回给前端页面.

否则,就可能出现用户点击创建订单按钮后,页面提示创建订单失败,而实际上订单却创建成功了.

正确的做法是,遇到这种情况,订单服务直接返回订单创建成功即可.

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 问题背景
  • 2 如何避免重复下单?
    • 2.1 怎么判断请求是否重复
      • 2.2 最佳实践
        • 每个请求须有唯一标识
        • 每次处理完请求后,须有记录标识该请求已被处理
        • 每次接收请求判断之前是否处理过
    相关产品与服务
    云数据库 Redis
    腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档