当前位置: 首页 > news >正文

【Java系统接口幂等性解决实操】

Java系统接口幂等性解决实操

  • 背景:
  • 1.幂等性
  • 2.实现方法
  • 3.确定方案
  • 4.幂等性核心
  • 5.实例代码

背景:

今天在之前的系统中碰到一个以往见过的面试题的实际使用情况,背景是这样子:

我们之前做的电商小程序,最近用户下单的时候出现了一个问题,从商城选了一款iPhone 16 Pro下单,创建订单支付金额后发现自己的订单列表的订单还是待支付(订单列表默认显示待支付状态的),上午的时候就来向客户反馈了,客户下午来找到我,说了情况。

我看下了后台发现这个用户订单的已经支付过了,没看到待支付的那个单子(因为系统有调度任务定时清理未支付的订单,用户上午的问题客户下午才来找,后台看不到要去数据库或者日志看),上k8s找了下当时的日志,发现确实同时创建了1、2两个订单,服务器时间只相差零点几秒左右,是前端同时调用了两次创建订单的接口,一开始我想着可能是前端页面没做控制,同一时间多次点击下单按钮就会多次调用接口。

于是我就给前端同事打电话说了一下情况,调整了一下前端的代码,完事之后测试了一下还是会出现这种情况。我们就商讨了一下,是否需要后端也加一下校验,防止订单重复提交,但是一时间又不知道从何下手,用户的参数相同时那就是买了一个iPhone 16 Pro然后又想再买一个iPhone 16 Pro的情况也不是没有,如何在参数上做校验。恍然间我想起来了以前刷面试题的时候有遇到一个幂等性的问题,好像就是防止接口重复调用的吧,但是具体怎么搞就不知道了,然后去刷DS。

1.幂等性

幂等性:
理论:

是数学和计算机科学中的一个重要概念,指同一操作多次执行与单次执行的效果一致。

实际:

在高并发的电商场景下,用户可能会因为网络延迟或其他原因多次点击提交按钮,导致生成重复订单(携带相同参数短时间内重复调用接口)。

2.实现方法

  1. 唯一标识‌:客户端生成唯一请求 ID(如 UUID),后端校验该 ID 是否已处理过。若已存在,则拒绝请求;若不存在,则处理请求并存储 ID。

  2. 状态机‌:确保业务操作仅在特定状态下执行,如订单状态从“待支付”到“已支付”仅允许一次变更。‌‌

  3. Token机制‌:客户端预获取Token,服务端验证后立即失效,避免重复提交。‌‌

  4. 分布式锁:通过 Redis 的 SETNX(SET if Not eXists)命令,为每个订单生成一个分布式锁。处理订单时,只有成功获取锁的请求才能继续执行,防止重复提交。

  5. 数据库唯一约束:在数据库订单表中设置唯一索引(如 用户ID + 商品ID + 订单号),当重复提交订单时,数据库会抛出唯一约束异常,后端捕获异常并拒绝重复请求。

3.确定方案

最后确定了下方案,因为我们的电商小程序只是分布式部署,QPS极低(低的可怜,理论上讲我们的所有用户同时去下单都不会达到高并发的标准,QPS达到1000以上,没错,基本没什么用户),所以使用唯一标识方法就足够了。

  1. 前端在进入创建订单页面时生成一个唯一的 uniqueId,用于标识本次订单创建请求;

  2. 第一次请求到达后端时,后端将 uniqueId 存入 Redis,并设置一定有效期(如5分钟);

  3. 后续的请求中,后端先验证 uniqueId 是否已存在于 Redis 中,存在:表示是重复请求,直接拒绝;不存在:表示是新请求,继续处理订单业务逻辑,并将 uniqueId 存入 Redis。

由此结合了幂等性校验和缓存验证,在代码中需要先进行幂等性处理,再去进行业务校验。为什么,因为幂等性校验的核心是确保同一请求不会重复执行业务逻辑,避免重复提交、重复支付的问题。

4.幂等性核心

其核心思想是:

提前拦截重复请求:如果请求是重复的,直接返回结果(如“请勿重复操作”),就不用进入后续的业务流程了。
节省资源:避免重复的数据库操作、业务逻辑处理或第三方接口调用,减少系统消耗。

5.实例代码

我项目的真实代码,已部署!!!

		//校验创建订单请求的唯一id 接口幂等性处理String uniqueId = bo.getUniqueId();if (StringUtils.isNotBlank(uniqueId)) {if (redisService.hasKey(RedisKeyConstants.SHOP_ORDER_UNIQUEIDKEY_PREFIX + uniqueId)){throw new BizException(BizErrorCodeEnum.ORDER_UNIQUEID_EXIST);}String uniqueIdKey = RedisKeyConstants.SHOP_ORDER_UNIQUEIDKEY_PREFIX + uniqueId;redisService.set(uniqueIdKey, uniqueId, loginTimeOut);}//业务校验//业务校验代码

不过这里建议是优化一下redis操作,由于Redis 的 hasKey() + set() 是两个独立操作,存在并发请求时可能都会通过验证的问题,使用 Lua 脚本可以确保校验和写入的原子性。

		// Lua 脚本:如果 key 不存在,则设置值并返回 true,否则返回 falseString script = "if redis.call('exists', KEYS[1]) == 0 then " +"redis.call('setex', KEYS[1], ARGV[2], ARGV[1]); " +"return 1; else return 0 end";// 执行 Lua 脚本String uniqueId = bo.getUniqueId();Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),Collections.singletonList(RedisKeyConstants.SHOP_ORDER_UNIQUEIDKEY_PREFIX + uniqueId),"1", 300 // 300 秒 = 5 分钟);if (result == 0L) {throw new BusinessException("请勿重复提交订单");}

那为什么我项目的代码没有这样写呢 ,xianzaidouxiewanleyihouganshenme (拼音不是乱码)?

果然经验不是靠八股文背出来的,还得亲身从项目中体会、实践。

http://www.dtcms.com/a/301691.html

相关文章:

  • java--WebSocket简单介绍
  • 2.安装CUDA详细步骤(含安装截图)
  • Dataloader的使用
  • 对抗攻击-知识点
  • HCIE学习之路:MSTP实现负载均衡实验
  • 全方位评测:11款主流指标平台优劣分析
  • [BSidesCF 2019]Kookie
  • 【测试报告】玄机抽奖系统(Java+Selenium+Jmeter自动化测试)
  • MyBatis-Plus 通用 Service(IService)详解与实战
  • Mybatis Plus 多数据源
  • 【LeetCode 热题 100】51. N 皇后——回溯
  • WiFi Mouse PC端 v1.7.2 官方中文版
  • GIF图像格式
  • 【RAG技术权威指南】从原理到企业级应用实践
  • Git Commit 生成与合入 Patch 指南
  • 《关于matplot中绘制图像中文字体乱码问题》
  • AWS免费套餐全面升级:企业降本增效与技术创新解决方案
  • 物联网发展:从概念到应用的演变历程
  • vue3报错:this.$refs.** undefined
  • 【INT范围提取字符串数字为正数】2022-8-29
  • Linux文件系统(三)
  • Java常用日志框架介绍
  • 【笔记】菲克定律与连续性方程详述
  • 【测试报告】博客系统(Java+Selenium+Jmeter自动化测试)
  • 【Milvus合集】1.Milvus 的核心概念(collection、field、index、partition、segment)
  • 【lucene】向量搜索底层文件关系梳理
  • springboot实现打印每个接口请求的出参和入参
  • SpringBoot配置文件详解
  • jangow-01-1.0.1靶机
  • Java 后端 Cookie Session Token会话跟踪技术