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

外卖CPS场景下Java后端接口幂等性设计的多种实现技巧

外卖CPS场景下Java后端接口幂等性设计的多种实现技巧

在外卖CPS(Cost Per Sale)系统中,用户下单、佣金结算等关键接口常因网络超时、客户端重试等原因被重复调用。若未做幂等处理,将导致重复创建订单、多发奖励等严重问题。本文结合实际业务场景,介绍基于Token、数据库唯一约束、分布式锁等多种幂等实现方式,并提供可落地的Java代码示例。

1. 基于请求唯一ID的数据库幂等表

最通用的方式是使用request_id作为幂等键,在业务执行前校验是否已处理:

packagebaodanbao.com.cn.cps.idempotent;importorg.springframework.jdbc.core.JdbcTemplate;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;@ServicepublicclassIdempotentService{privatefinalJdbcTemplatejdbcTemplate;publicIdempotentService(JdbcTemplatejdbcTemplate){this.jdbcTemplate=jdbcTemplate;}@TransactionalpublicbooleantryAcquire(StringrequestId){StringcheckSql="SELECT 1 FROM idempotent_record WHERE request_id = ?";Integerexists=jdbcTemplate.queryForObject(checkSql,Integer.class,requestId);if(exists!=null){returnfalse;// 已处理}StringinsertSql="INSERT INTO idempotent_record(request_id, create_time) VALUES (?, NOW())";jdbcTemplate.update(insertSql,requestId);returntrue;}}

对应建表语句:

CREATETABLEidempotent_record(request_idVARCHAR(64)NOTNULLPRIMARYKEY,create_timeDATETIMENOTNULL);

在CPS下单接口中使用:

packagebaodanbao.com.cn.cps.order;importbaodanbao.com.cn.cps.idempotent.IdempotentService;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework.web.bind.annotation.RequestHeader;importorg.springframework.web.bind.annotation.RestController;@RestControllerpublicclassCpsOrderController{privatefinalIdempotentServiceidempotentService;privatefinalOrderServiceorderService;publicCpsOrderController(IdempotentServiceidempotentService,OrderServiceorderService){this.idempotentService=idempotentService;this.orderService=orderService;}@PostMapping("/cps/order/create")publicOrderResultcreateOrder(@RequestBodyCreateOrderRequestreq,@RequestHeader("X-Request-ID")StringrequestId){if(!idempotentService.tryAcquire(requestId)){returnorderService.queryOrderByRequestId(requestId);// 返回已有结果}returnorderService.createOrder(req);}}

2. 基于Redis的幂等令牌机制

适用于高并发场景,利用Redis的原子性操作实现快速校验:

packagebaodanbao.com.cn.cps.idempotent.redis;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Component;importjava.util.concurrent.TimeUnit;@ComponentpublicclassRedisIdempotentToken{privatefinalStringRedisTemplateredisTemplate;publicRedisIdempotentToken(StringRedisTemplateredisTemplate){this.redisTemplate=redisTemplate;}publicbooleanacquire(Stringkey,longexpireSeconds){Booleanresult=redisTemplate.opsForValue().setIfAbsent(key,"1",expireSeconds,TimeUnit.SECONDS);returnBoolean.TRUE.equals(result);}}

配合前端预获取Token:

@PostMapping("/cps/order/token")publicStringgenerateToken(){Stringtoken="token_"+System.currentTimeMillis()+"_"+Thread.currentThread().getId();// 默认5分钟有效redisIdempotentToken.acquire(token,300);returntoken;}@PostMapping("/cps/order/create-with-token")publicOrderResultcreateOrderWithToken(@RequestBodyCreateOrderRequestreq,@RequestHeader("X-Idempotent-Token")Stringtoken){// 注意:此处不再主动acquire,而是检查是否存在Booleanexists=redisTemplate.hasKey(token);if(Boolean.FALSE.equals(exists)){thrownewIllegalArgumentException("Invalid or expired token");}// 执行业务并删除token(防止重用)OrderResultresult=orderService.createOrder(req);redisTemplate.delete(token);returnresult;}

3. 利用数据库唯一索引实现天然幂等

对于“用户+活动”维度的奖励发放,可直接在业务表上建立联合唯一索引:

CREATETABLEuser_reward(idBIGINTPRIMARYKEYAUTO_INCREMENT,user_idVARCHAR(32)NOTNULL,activity_idVARCHAR(32)NOTNULL,reward_amountDECIMAL(10,2)NOTNULL,UNIQUEKEYuk_user_activity(user_id,activity_id));

Java层捕获异常即可:

packagebaodanbao.com.cn.cps.reward;importorg.springframework.dao.DuplicateKeyException;publicclassRewardService{publicvoidgrantReward(StringuserId,StringactivityId,doubleamount){try{rewardMapper.insert(userId,activityId,amount);}catch(DuplicateKeyExceptione){// 幂等:已发放过,可查询并返回原记录return;}}}

4. 分布式锁兜底方案

当上述方法不适用时(如无明确唯一键),可采用Redisson分布式锁:

packagebaodanbao.com.cn.cps.idempotent.lock;importorg.redisson.api.RLock;importorg.redisson.api.RedissonClient;importorg.springframework.stereotype.Component;@ComponentpublicclassDistributedIdempotentLock{privatefinalRedissonClientredissonClient;publicDistributedIdempotentLock(RedissonClientredissonClient){this.redissonClient=redissonClient;}public<T>TexecuteWithLock(StringlockKey,java.util.concurrent.Callable<T>task){RLocklock=redissonClient.getLock(lockKey);try{if(lock.tryLock(1,10,TimeUnit.SECONDS)){returntask.call();}else{thrownewRuntimeException("Failed to acquire idempotent lock");}}catch(Exceptione){thrownewRuntimeException(e);}finally{if(lock.isHeldByCurrentThread()){lock.unlock();}}}}

调用示例:

StringlockKey="idempotent:order:"+req.getUserId()+":"+req.getActivityId();returndistributedIdempotentLock.executeWithLock(lockKey,()->{// 查询是否已存在订单Orderexisting=orderMapper.selectByUserAndActivity(...);if(existing!=null)returnexisting;returnorderMapper.insertNewOrder(req);});

本文著作权归 俱美开放平台 ,转载请注明出处!

http://www.cnnetsun.cn/news/3362209.html

相关文章:

  • WandEnhancer技术深度解析:Electron应用增强框架的架构与实现原理
  • TMC7300与PIC18F47K42组合的直流电机驱动方案
  • 美团LongCat-2.0:1.6万亿参数MoE大模型的完整指南
  • 终极指南:如何用llama.cpp快速部署LLaMA-Mesh-Q4_K_M-GGUF模型(附完整命令)
  • Windows Subsystem for Android终极实战:在Windows 11中构建原生Android环境
  • mlx-community/Z-Image-bf16核心架构揭秘:S3-DiT与Qwen3-4B的完美融合
  • 怎样高效使用Windows Subsystem for Android:5个实战技巧与完整方案
  • 7个Bitters自定义技巧:让你的样式代码更简洁、更易维护
  • Calories Burned Predictor与Hugging Face集成:模型发布、版本管理和社区协作的最佳实践
  • SmolLM-135M-Instruct_rai_1.7.1_hybrid开发者完全指南:从API集成到自定义微调
  • Hy3-preview-MTP-4bit安全部署:确保推理服务稳定性的7个最佳实践
  • 从零到一:实战配置JMX远程连接与JConsole监控
  • Z-Image-Turbo+ControlNet+Krea2:AI图像生成与修复实战指南
  • AI代理盲视问题解析:环境感知与容错设计实战
  • ComfyUI高清图像修复:Z-Image-Turbo+ControlNet+Krea2全流程实战
  • AI音乐视频制作全流程:从TTS语音合成到Stable Diffusion角色生成
  • SDXL AMD NPU版故障排除与常见问题解答
  • YOLOv8热力图可视化:原理、实现与优化技巧
  • 数据导师:一线从业者向业务赋能者转型的实战路径
  • 技术项目命名规范与个性化实践:从变量到项目的艺术
  • AI一上手机就卡死?30年老兵拆解模型的“瘦身”与部署术
  • C 语言字符串 内存函数:用法 + 模拟实现全攻略
  • 20_Git 仓库使用手册(实用版)
  • MCU 休眠死机彻底起不来?RTC 定时唤醒喂狗完美解决低功耗看门狗难题
  • C++23协程与CUDA异步编程实战:构建高性能GPU计算框架
  • 锂电池组电压平衡方案:PIC18F86K90与MCP3202实战
  • FPGA 时钟资源详解
  • AI代理与记忆引擎:技术架构与工程实践
  • PbootCMS
  • Spine动画版本降级实战:从4.0到3.6的兼容性处理与路径动画迁移