老万教你最简单接口幂等性控制

简介: 老万教你最简单接口幂等性控制

什么是接口幂等性


接口幂等性,简单来说就是指一个接口调用一次和调用N次的效果是一样的,不会产生其他的副作用。

注意:

这里的效果一样和返回结果一样的区别,比如我们都知道查询接口具有天然的幂等性,但是多次调用查询接口的过程中,如果有其他操作对查询的数据进行了新增、修改、删除操作,那么查询接口的返回结果就会不一直,但是这并不能说明该查询接口不具有幂等性。


场景说明


典型场景,对指定订单发起一笔付款交易,无论交易接口调用一次还是N次,都只能扣用户账户一次钱。

一般最简单的幂等处理就是通过订单状态来进行控制,伪代码如下:

begin:
     根据订单号查询订单状态;
  if(待支付){
      扣款操作;
   更新订单状态为已支付;
  }
end;


代码实战


1、订单表说明

CREATE TABLE `tb_order` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `goods_code` varchar(255) DEFAULT NULL COMMENT '商品编码',
  `goods_num` int(10) DEFAULT NULL COMMENT '商品数量',
  `money` decimal(20,0) DEFAULT NULL COMMENT '总金额',
  `status` tinyint(1) DEFAULT NULL COMMENT '订单状态(0未支付,1支付)',
  `account` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '账户',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;


添加一条测试订单数据:订单id是1,状态0的未支付订单。


INSERT INTO `order`.`tb_order`(`id`, `goods_code`, `goods_num`, `money`, `status`, `account`) VALUES (1, 'wsj0001', 5, 3000, 0, 'laowan');


2、新建Order订单工程,实现订单支付接口

/**
 * @program: order
 * @description: 订单接口实现
 * @author: wanli
 * @create: 2020-09-21 16:11
 **/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Autowired
    OrderMapper orderMapper;
    /**
     * 订单支付接口
     * @param orderId
     * @return
     * @throws InterruptedException
     */
    @Override
    public String payOrder(String orderId) throws InterruptedException {
        //1、查询订单
        Order order = new Order();
        order.setId(Long.parseLong(orderId));
        order = orderMapper.selectByPrimaryKey(order);
        //为模拟并发,暂定100ms
        Thread.sleep(100);
        //2、根据订单状态判断,是否进行支付
        if(order.getStatus().equals(0)){
            log.info("进行支付,并更新订单状态");
            order.setStatus(1);
            orderMapper.updateByPrimaryKeySelective(order);
            return "支付完成";
        }else{
            log.info("已支付");
            return "已支付";
        }
    }
}


说明:

这个应该是很多人的常见代码,先查询订单状态,判断订单状态是否是未支付状态,是则进行支付。


3、采用JMeter模拟并发支付

38.png

37.png


模拟每秒50个并发,执行结果为:

36.png

可以发现,在高并发的情况下,出现了大量重复支付的情况。


4、优化:通过for update添加悲观锁

4.1、OrderMapper.xml中添加selectForUpdate

<select id="selectForUpdate" resultType="com.laowan.order.model.Order">
        select  * from tb_order t where t.id = #{orderId} for update
  </select>


4.2、OrderMapper.xml中添加selectForUpdate

public interface OrderMapper extends BaseMapper<Order> {
    Order selectForUpdate(Long orderId);
}

4.3、修改支付订单方法

/**
     * 订单支付接口
     * @param orderId
     * @return
     * @throws InterruptedException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String payOrder(String orderId) throws InterruptedException {
        //1、查询订单
        Order order = new Order();
        order.setId(Long.parseLong(orderId));
        //通过
        order = orderMapper.selectForUpdate(Long.parseLong(orderId));
        //为模拟并发,暂定100ms
        Thread.sleep(100);
        //2、根据订单状态判断,是否进行支付
        if(order.getStatus().equals(0)){
            log.info("进行支付,并更新订单状态");
            order.setStatus(1);
            orderMapper.updateByPrimaryKeySelective(order);
            return "支付完成";
        }else{
            log.info("已支付");
            return "已支付";
        }
    }



说明:

for update一定要配合事务使用,不然执行完查询语句后,会自动释放锁。

给方法添加 @Transactional事务注解后,只有方法执行完毕才会自动释放锁。


4.4、再次使用JMeter压测

35.png

订单支付接口在高并发多次调用的情况下,仍然只支付了一次,接口的幂等性得到保证。


总结:


1、不能简单的通过订单状态来控制接口的幂等性,高并发情况下,多个线程同时查到未支付状态的订单,就容易出现重复支付的情况。

2、通过for update对订单记录加上排他锁,使的该订单记录不能被其他线程查询到,也不能进行其他修改操作。只有当事务提交,释放锁后,其他操作该订单的接口才能执行。

3、一定要给订单号加上索引,避免锁表。(Innodb引擎中,查询和更新操作如果不走索引,就会进行全表锁定。本例中由于订单号是主键,所以不用加)。

4、@Transactional注解不但能控制一个方法中的多个数据修改操作的原子性,也能控制锁的释放。比如本例中,尽管只有一个修改操作,但是一定要添加@Transactional注解让方法执行完后自动释放锁。


当然实现接口幂等性还有很多其他的方法,这里只是说明一个最典型的错误场景,并通过简单的添加悲观锁,实现接口的幂等性控制。

目录
相关文章
|
编解码 测试技术 索引
性能工具之 Jmeter 使用 HTTP 请求编写 HLS 脚本
在我们简要介绍了 HLS 协议的基础知识,接下来我们详细介绍一种使用 Jmeter 编写压测 HLS 协议脚本的方法。
582 1
性能工具之 Jmeter 使用 HTTP 请求编写 HLS 脚本
|
关系型数据库 MySQL Java
【JMeter】(3)---MySQL压测
【JMeter】(3)---MySQL压测
721 0
|
Java 应用服务中间件 API
Flowable UI Web应用搭建教程(图解)
Flowable UI Web应用搭建教程(图解)
1916 0
|
7月前
|
人工智能 安全 Linux
养虾日记:养出“能干活”的OpenClaw!阿里云部署+百炼API配置+必备Skill+人格塑造+多Agent协作保姆级教程
很多新手成功部署OpenClaw(昵称“龙虾”)后,都会陷入一种落差:别人的OpenClaw能主动推进任务、持续迭代优化、越用越聪明,而自己的却频繁报错、缺乏主动性、需反复确认指令。问题的核心并非模型能力,而是配置缺失——OpenClaw本质是可持续运行的AI Agent,而非单纯的聊天机器人,只有做好“人格塑造、记忆管理、Skill筛选、多Agent协作”四大配置,才能从“会聊天的壳”升级为“能干活的助手”。
2369 4
|
4月前
|
SQL 运维 关系型数据库
AnalyticDB MySQL vs ClickHouse:OLAP 数据库选型深度对比——谁更适合企业级分析
AnalyticDB MySQL(阿里云PB级全托管实时数仓)与ClickHouse深度对比:在多表JOIN、高并发、实时更新、全托管运维及企业生态集成上全面领先,TPC-DS测试性能优3–5倍,成本可降30%–60%,是企业级复杂分析首选。
336 7
|
9月前
|
存储 弹性计算 人工智能
阿里云服务器ECS全解析:2026赋能企业数字化的强劲算力
阿里云ECS,历经十五年技术沉淀,以自研CIPU芯片驱动,提供高达400G网络带宽、超低时延与99.975%高可用性,支持多元计费与全球29个地域布局,助力企业高效降本、灵活扩展。广泛适配AI、HPC、互联网等百行千业,一站式满足安全、稳定、弹性的云计算需求,赋能数字化转型。
|
9月前
|
存储 人工智能 运维
AR眼镜+AI:重构工业运维场景的智能核心载体
AR眼镜融合AI、语音交互与影像技术,实现仪器仪表自动识别、分时巡检提示、全程录像追溯与远程协作指导,提升巡检效率60%以上,误判率低于0.5%,推动工业运维向智能化、标准化、可追溯转型,成为数字化转型关键终端。
|
4月前
|
算法 Java API
🔐 1688开放平台API Sign签名算法详解(Java / Python / PHP 实现)
本文详解1688开放平台API签名算法(MD5方式),涵盖Java/Python/PHP三语言实现。核心四步:过滤空值→参数名ASCII升序→拼接key+value→AppSecret首尾包裹后MD5大写。重点纠正常见错误:sign不参与签名、时间戳须毫秒、空值剔除、Base64原串处理。助你一次打通商品、订单、物流等全接口调用。(239字)
|
7月前
|
自然语言处理 数据挖掘 数据库
数据智能引擎:从精准问数到深度分析的完整解决方案
数据智能引擎基于本体论,首创“精准问数+深度分析”双模式:技术专家可自然语言查数据,高管提方向性问题获自动洞察。多智能体协同、95%准确率、低门槛业务知识管理,赋能企业AI原生数据转型。(239字)
|
7月前
|
人工智能 运维 安全
企业如何降低 Claude 的迁移与接入门槛
企业AI落地面临多模型并行、接口杂乱、跨境支付与合规难、治理缺失等痛点。通过统一OpenAI标准接口、接入聚合平台及LLM网关,可实现模型热切换、人民币结算、网络加速与精细化管控,降本增效又保障稳定安全。
288 0

热门文章

最新文章