首页 > 其他分享 >项目开发中如何保证接口的幂等性

项目开发中如何保证接口的幂等性

时间:2024-03-05 14:33:38浏览次数:29  
标签:请求 索引 重复 业务 接口 并发 Token 开发 保证

1. 幂等概述

1.1 什么是幂等性

在计算机领域中,幂等(Idempotence)是指任意一个操作的多次执行总是能获得相同的结果,不会对系统状态产生额外影响。在Java后端开发中,幂等性的实现通常通过确保方法或服务调用的结果具有确定性,无论调用次数如何,结果都是可预期的。

上面的定义是目前大多数文章和书籍对幂等的描述,然而,在实际的Java 项目开发中,幂等性的理论定义与业务逻辑间的冲突是常见的。

例如,考虑查询操作,当A系统调用B系统的查询接口时,如果首次调用由于B系统中的程序错误而导致业务逻辑失败,即使在程序修复后系统A重新使用相同参数进行重试,B系统可能仍然返回相同的失败响应。尽管这符合幂等性的定义,却与实际业务逻辑不符。同样,以订单支付为例,首次调用由于账户余额不足而返回“余额不足”提示,用户充值后再次使用相同参数发起支付请求,服务仍然返回“余额不足”响应,也符合幂等性的定义,但同样不符合业务逻辑。

因此,在实现幂等性方案时,应该遵循幂等性方案的目标,而不仅仅是严格遵循幂等性的定义。尤其是涉及写操作的服务,应当更关注防止重复请求带来的不良副作用,例如重复扣款或退款。

1.2 幂等性的必要性

在微服务和分布式架构中,一个请求可能需要多个服务协作才能完成。在这个过程中,网络抖动、系统运行异常等不确定因素使得请求的成功率不可能达到100%,一旦发生失败或未知异常,最常见的处理方式就是重试,而重试必然会导致重复请求问题。

幂等设计主要是为了处理重复请求而生的,好的幂等方案可以保证重复请求获得预期结果,而不产生副作用。 在实际开发中,以下场景会产生重复请求:

  • 用户不可靠:用户通过客户端发起请求,由于手抖或有意重复点击,很容易造成导致极短时间内发起多次重复请求。
  • 网络不可靠:网络抖动、网关内部抖动有可能触发重试机制,这个在使用消息队列投递消息时经常会遇到。
  • 服务不可靠:在需要保证数据一致性的场景中,如果调用下游服务超时,在无法确认执行结果的情况下,常用的处理方法是重试。
1.3 幂等与并发的关系

在具有并发写操作的场景下,通常需要考虑幂等问题。例如,当用户在极短时间内多次提交表单或者使用特殊手段同时提交多个表单时,这就是典型的并发场景,需要进行幂等性处理。为了防止重复请求被执行,服务端需要实施幂等性控制,以避免产生不符合预期的结果。

虽然并发场景大都存在幂等问题,但幂等问题却并非并发场景所特有。幂等设计是为了识别并处理重复请求,而并发仅仅是重复请求的一种特殊情况。 事实上,只要重复请求涉及写操作,无论是否并发,都需要做好幂等处理。举个例子,用户在pc端同时开了两个窗口,间隔10分钟分别提交表单,所有参数完全相同,这显然不属于并发,但仍需要进行幂等处理。

在Java项目开发过程中,并发处理与幂等性问题紧密相关,这也导致了一些人认为解决幂等性就是解决高并发的问题。

2. 幂等号的设计

幂等性设计的目的是确保即使在多次接收相同请求的情况下,也只执行一次操作,防止重复处理。要实现这一点,通常需要事先约定一个具有唯一性的标识符,如Token或业务流水号,我们称之为幂等号(Idempotency Key)。

幂等号有三个关键特性:唯一性、不变性和传递性。

唯一性确保每个请求都能被准确识别,不变性保证在请求处理期间幂等号保持不变,传递性则确保在多系统处理同一请求时,幂等号能够被传递和保持。

幂等号通常有两种设计方式:

  1. 非业务幂等号:通过唯一标识符(如UUID、时间戳或业务流水号)在调用方和被调用方之间明确实现幂等性。由于非业务幂等号难以通过业务上下文追溯,因此调用双方都必须将其持久化,从而保证请求与幂等号的关系有迹可循。

  2. 业务幂等号:由业务元素组合构成的幂等号,如“用户ID+活动ID”。使用此方法时,调用方无需单独持久化幂等号,被调用方可以根据请求参数和业务上下文直接获取并组合这些参数。例如,通过设置“用户ID”和“活动ID”的联合唯一索引来实现幂等性。

3. 幂等的实现方案

幂等性的实现关键在于确保相同的请求仅被处理一次,这通常可以通过设置唯一性约束和检查来实现。实践中有六种常见的方案:唯一索引、Token机制、悲观锁、乐观锁、分布式锁和状态机。

3.1 唯一索引方案机制

唯一索引方案依赖于数据库表中不允许存在具有相同索引值的重复行。这种策略在关系型数据库中广泛支持,并且能有效利用唯一性约束来确保幂等性。在高并发场景中,唯一索引能保证当多个线程尝试同时插入相同记录时,只有一个线程能成功执行,而其他线程将会因违反唯一性约束而抛出异常。

通常,业务流水表的建立是基于以下核心字段:

  • id(bigint 类型):作为主键,唯一标识每条记录。
  • gmt_create(datetime 类型):记录的创建时间。
  • gmt_modified(datetime 类型):记录的最后修改时间。
  • user_id(varchar(32) 类型):用户ID,这个字段也可以作为分表的依据。
  • out_biz_no(varchar(64) 类型):外部业务流水号,即调用方的幂等号。
  • biz_no(varchar(64) 类型):内部业务流水号,用于系统内部追踪。
  • status(char(1) 类型):记录执行状态。

在这种设计中,user_idout_biz_no通常会组合成一个联合索引,这样做能有效避免在并发情况下的数据重复插入问题,从而保障了业务操作的幂等性。

3.2 Token机制

Token机制是用于防止客户端重复提交的一种特殊机制,特别适用于客户端创建订单等提交表单场景。

其执行流程如下:

  1. 当用户访问表单页面时,客户端请求服务端接口以获取唯一的Token(可以是UUID或全局ID),服务端生成的Token会被存储在Redis或数据库中。
  2. 用户首次提交表单时,将Token与表单一起发送至服务端,服务端会验证Token的存在性,如果Token存在,则执行业务逻辑,并在完成后销毁Token。
  3. 用户再次提交表单时,同样携带Token一起发送至服务端。但由于Token已被销毁,服务端无法找到对应的Token,从而拒绝重复提交请求。
3.3 悲观锁机制

悲观锁依赖数据库提供的锁机制来实现,整个数据处理过程中,数据处于锁定状态,并与事务机制配合,能够有效实现业务幂等性。操作示例如下:

// 1. 开启事务
begin;

// 2. 基于幂等号查询
record = select * from tbl_xxx where out_biz_no = 'xxx' for update;

// 3. 根据状态进行决策
if(record.getStatus() != 预期状态){
   return;
}

// 4. 更新记录
update tbl_xxx set status = '目标状态' where out_biz_no = 'xxx';

// 5. 提交事务
commit;

悲观锁主要适用于更新场景,通过串行化请求处理来确保幂等性,但需要小心使用,因为在并发场景下,重复请求可能会导致线程长时间处于等待状态,浪费资源且降低性能。

3.4 乐观锁机制

乐观锁主要依靠"带条件更新"(update with condition)来确保多次外部请求的一致性。在系统设计中,可以在数据表中添加版本号字段,用于标识当前数据的版本。每次对该数据表的记录进行更新时,都需要提供上一次更新的版本号,示例操作如下:

//1. 取出要更新的对象,带有版本versoin
select * from tablename where id = xxx

//2. 更新数据
update tableName set sq = sq-#{quantity},version = #{version}+1 where id = xxx and version=#{version}

乐观锁主要适用于更新场景,确保多次更新不会影响结果的一致性。

3.5 分布式锁机制

分布式锁与悲观锁本质上相似,都通过串行化请求处理来实现幂等性。与悲观锁不同的是,分布式锁更轻量。在系统接收请求后,首先尝试获取分布式锁。如果成功获取锁,则执行业务逻辑;如果获取失败,则立即拒绝请求。

分布式锁的核心是识别重复请求,实现串行化处理。但要注意,获取锁成功后,业务逻辑的执行并没有可靠保证。因此,在实际应用中,分布式锁需要结合事务机制和重试机制,以形成完整的幂等性解决方案。

3.6 状态机机制

在许多业务单据中,存在有限数量的状态,并且这些状态之间的流转顺序是固定的。如果状态已经处于下一个状态,那么再次应用上一个状态的变更逻辑是不会产生任何效果的,这就确保了有限状态机的幂等性。

例如,库存状态通常包括"预扣中"、"扣减中"、"占用中"和"已释放"等状态。如果系统重复调用扣减接口,而库存状态已经是"扣减中",则可以直接返回结果。

状态机可以与乐观锁机制结合使用,示例操作如下:

update tableName set sq=sq-#{quantity},status=#{udpate_status} where id =#{id} and status=#{status}

总结

以上介绍了六种实现幂等性的方式,并简要介绍了每种方式适用的场景和关键信息。这些方式可以总结为三个技术路线:唯一索引、唯一数据和状态机约束

  • 唯一索引:指的是数据库的唯一索引,通常基于业务流水表创建,也可以单独创建表来实现。
  • 唯一数据:包括悲观锁、乐观锁、分布式锁等机制。
  • 状态机约束:适用于具有状态流转的业务,通过状态机的流转约束,可以实现有限状态机的幂等性。

然而,需要注意的是,在实际开发中,单独使用这些方法往往效果有限。 例如,悲观锁和分布式锁只是将请求串行处理,对于异常情况的重试并没有足够的防御能力,因此需要结合唯一索引来实现完整的幂等性解决方案。同样,唯一索引方案也需要与事务机制结合使用。因此,在实际应用中,需要根据具体的业务场景灵活选择、合理的运用上述实现方法。

标签:请求,索引,重复,业务,接口,并发,Token,开发,保证
From: https://www.cnblogs.com/binbingg/p/18027445

相关文章

  • 使用go写的一个api接口
    记录一下使用go写的一些脚本packagemainimport( "encoding/json" "fmt" "log" "net/http" "os" "os/exec" "strconv" "strings" "sync" "time")var( requestCo......
  • JAVA项目 贪吃蛇游戏二次开发
    基于java实现贪吃蛇小游戏,主要通过绘制不同的图片并以一定速度一帧一帧地在窗体上进行展示。原代码地址:https://gitee.com/jay_musu/games-and-tools.gitpackagecom.snake.view;importjava.awt.Color;importjava.awt.EventQueue;importjava.awt.Font;importjava.awt......
  • 阿里巴巴/1688 api接口 获取商品详情 数据采集
    iDataRiver平台https://www.idatariver.com/zh-cn/提供开箱即用的阿里巴巴1688电商数据采集API,供用户按需调用。接口使用详情请参考阿里巴巴1688接口文档接口列表1.获取商品详情参数类型是否必填默认值示例值描述apikeystring是idr_***从控制台里复制api......
  • 报名开启!第3期自动驾驶OS开发初阶营
    第三期国家智能网联汽车创新中心自动驾驶OS开发初阶训练营将于3月10日开营,现已开启报名,诚邀对自动驾驶底层开发感兴趣的学员加入,一起驶入自动驾驶的快车道,共同探索未来的无限可能。关于训练营发起人训练营项目是由清华大学操作系统课题组陈渝老师和向勇老师发起,清华大学的操作......
  • 2024年,提升Windows开发和使用体验实践 - 终端&命令行篇
    前言经过前面的铺垫,终于继续更新了,这个大概率是本系列近期的最后一篇了。同时之前有些内容更新,我也补充到这一篇里面。关于scoop管理器的补充scoop常用命令scoophelp#查看帮助scoophelp<某个命令>#具体查看某个命令的帮助scoopinfo<app>#查看APP信......
  • 小程序开发:定时删除上传的阿里云oss文件
    上文说到:我们接入了阿里云的oss,以及阿里云的人像动漫化接口。本文要完成的功能:用户上传到阿里云oss的图片需要在一天后定时删除,否则占用了存储空间。大致流程:用户上传阿里云oss成功后,将上传的文件名记录到一张表,记录有一个字段日期为过期时间,定时任务检测到过期后,先删除oss文件,......
  • 13_.NET 中 IDisposable 接口的作用和使用
    .NET中IDisposable接口的作用和使用IDisposable接口是.NET框架中定义的一个接口,用于释放非托管资源。非托管资源是指由操作系统或其他外部库管理的资源,例如文件句柄、数据库连接和网络套接字。IDisposable接口的作用是提供一种标准的方式来释放非托管资源。这有助......
  • C++游戏飞翔的小鸟软件二次开发
    引言:在快节奏的现代生活中,人们总是在寻找一种方式来放松自己,释放内心的压力。游戏作为一种娱乐方式,早已深入人心。今天,我要向大家介绍的是一款简单而又充满挑战的小游戏——飞翔的小鸟。这款游戏的核心玩法是控制一只小鸟在无尽的天空中飞翔,通过点击屏幕使小鸟上升,避开各种障碍......
  • 鸿蒙开发游戏(四)---大鱼吃小鱼(互吃升级)
    鸿蒙开发游戏(一)---大鱼吃小鱼(界面部署)鸿蒙开发游戏(二)---大鱼吃小鱼(摇杆控制)鸿蒙开发游戏(三)---大鱼吃小鱼(放置NPC)鸿蒙开发游戏(四)---大鱼吃小鱼(互吃升级)鸿蒙开发游戏(五)---大鱼吃小鱼(添加音效)鸿蒙开发游戏(六)---大鱼吃小鱼(称霸海洋) 前言:该篇对NPC进行了升级,这里可以投入多个......
  • C++网上购书系统项目的二次开发 2252416 hzx
    1、来源:同学大二下的期末大作业:网上购书系统项目。2、运行环境:VisualStudio2019代码:点击查看代码#include"StdAfx.h"#include<iostream>#include"person.h"#include<string.h>#include"globalfunction.h"#include"book.h"#include"adm.h&......