订单系统幂等方案流程 + 代码实现

订单系统幂等方案流程 + 代码实现 订单系统幂等方案流程 代码实现一、整体架构说明1. 设计目标高并发下单场景实现多层幂等防重、防篡改、抗重复提交分层拦截前端参数校验 → 布隆过滤器(前置快速拦截) → RedisLua(原子判重) → 业务逻辑 → DB唯一索引(最终兜底)2. 核心规则采用国密SM3对订单核心稳态字段生成摘要作为全局幂等KeySM3摘要仅做幂等校验对外订单ID使用短ID雪花/MurmurHash提升存储索引性能Redis幂等Key强制配置TTL避免死键判断删除操作由Lua原子执行杜绝并发竞态数据库双层唯一约束兜底哈希碰撞、缓存失效场景落库失败自动回补Redis幂等键支持重试。3. 术语定义幂等原文用户ID、商品ID、购买数量、实付金额、收货信息等稳态业务字段剔除前端随机串、动态时间戳、客户端IPsm3DigestSM3算法计算出的64位十六进制摘要全局幂等唯一键orderId对外暴露订单ID短整型/雪花IDbloomFilter分布式布隆过滤器前置拦截idempotentKeyRedis幂等Key格式order:idempotent:{sm3Digest}二、完整业务流程图阶段1订单确认预生成摘要 写入缓存/布隆接收前端订单参数合并库内基础数据过滤动态可变字段组装稳态业务字段串计算sm3Digest基于sm3Digest生成短订单ID对外返回将sm3Digest写入布隆过滤器Redis写入幂等Key设置TTL示例10分钟向前端返回orderIdsm3Digest提交阶段使用阶段2订单正式提交幂等校验 业务落库前端携带sm3Digest发起正式提交请求第一层布隆过滤器校验不存在 → 直接拒绝返回重复请求存在 → 进入下一级校验第二层Redis Lua 原子校验删键执行Lua脚本判断Key是否存在不存在 → 拒绝请求存在 → 原子删除Key放行执行业务逻辑扣库存、生成流水、风控等第三层数据库落库 唯一索引兜底写入订单主表触发主键/联合唯一索引校验写入成功正常返回下单成功写入失败唯一索引冲突/异常回补Redis幂等Key返回失败允许重试三、核心配置项# 布隆过滤器配置 bloom.expected.insertions1000000 # 预估订单量 bloom.false.probability0.0001 # 误判率 0.01% # Redis 幂等Key过期时间单位秒 order.idempotent.ttl600 # Redis Key前缀 redis.key.prefix.orderorder:idempotent:四、核心代码实现Java Redis Lua 伪代码技术栈参考SpringBoot Redisson 国密SM3工具 布隆过滤器(Redisson)1. 工具类SM3 摘要生成工具importorg.bouncycastle.crypto.digests.SM3Digest;importorg.bouncycastle.util.encoders.Hex;importjava.nio.charset.StandardCharsets;/** * 国密SM3 摘要工具类生成幂等唯一摘要 */publicclassSm3Util{/** * 计算字符串SM3摘要返回64位十六进制字符串 * param content 订单稳态业务字段拼接串 * return sm3摘要 */publicstaticStringencrypt(Stringcontent){byte[]datacontent.getBytes(StandardCharsets.UTF_8);SM3Digestsm3newSM3Digest();sm3.update(data,0,data.length);byte[]resultnewbyte[sm3.getDigestSize()];sm3.doFinal(result,0);returnHex.toHexString(result);}}2. 工具类短订单ID生成基于SM3二次哈希importcom.google.common.hash.Hashing;importjava.nio.charset.StandardCharsets;/** * 短订单ID生成SM3摘要 - MurmurHash 转长整型 * 解决SM3字符串过长、索引性能差问题 */publicclassOrderIdUtil{/** * 根据sm3摘要生成短订单ID */publicstaticlonggenerateShortOrderId(Stringsm3Digest){returnHashing.murmur3_128().hashString(sm3Digest,StandardCharsets.UTF_8).asLong();}}3. 订单确认阶段代码预存布隆 Redisimportorg.redisson.api.RBloomFilter;importorg.redisson.api.RBucket;importorg.redisson.api.RedissonClient;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Service;importjavax.annotation.PostConstruct;ServicepublicclassOrderConfirmService{privatefinalRedissonClientredissonClient;privateRBloomFilterStringorderBloomFilter;Value(${order.idempotent.ttl})privatelongidempotentTtl;Value(${redis.key.prefix.order})privateStringredisKeyPrefix;Value(${bloom.expected.insertions})privatelongbloomInsertions;Value(${bloom.false.probability})privatedoublebloomFalseRate;publicOrderConfirmService(RedissonClientredissonClient){this.redissonClientredissonClient;}/** * 初始化布隆过滤器项目启动执行 */PostConstructpublicvoidinitBloomFilter(){orderBloomFilterredissonClient.getBloomFilter(order:bloom:filter);// 初始化预估数量 误判率orderBloomFilter.tryInit(bloomInsertions,bloomFalseRate);}/** * 订单确认生成摘要、写入布隆Redis、返回订单ID * param orderBizContent 订单稳态业务字段拼接串 * return 订单确认结果orderId sm3Digest */publicOrderConfirmVOorderConfirm(StringorderBizContent){// 1. 计算SM3摘要幂等核心KeyStringsm3DigestSm3Util.encrypt(orderBizContent);// 2. 生成对外短订单IDlongorderIdOrderIdUtil.generateShortOrderId(sm3Digest);// 3. 写入布隆过滤器orderBloomFilter.add(sm3Digest);// 4. 写入Redis幂等Key 设置TTLStringredisKeyredisKeyPrefixsm3Digest;RBucketStringbucketredissonClient.getBucket(redisKey);bucket.set(1,idempotentTtl,java.util.concurrent.TimeUnit.SECONDS);// 5. 封装返回给前端OrderConfirmVOvonewOrderConfirmVO();vo.setOrderId(orderId);vo.setSm3Digest(sm3Digest);returnvo;}}// 返回实体classOrderConfirmVO{privatelongorderId;privateStringsm3Digest;// getter setter}4. Lua 脚本Redis 原子判重 删除文件order_idempotent.lua功能判断Key是否存在存在则删除并返回1放行不存在返回0拒绝-- KEYS[1] 完整redis幂等keylocalkeyKEYS[1]localexistsredis.call(EXISTS,key)ifexists0then-- 幂等key不存在拒绝请求return0else-- 存在则原子删除放行业务redis.call(DEL,key)return1end5. 订单提交阶段核心代码全链路幂等校验importorg.redisson.api.RScript;importorg.springframework.core.io.ClassPathResource;importorg.springframework.data.redis.core.script.DefaultRedisScript;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;importjavax.annotation.PostConstruct;importjava.util.Collections;importjava.util.List;ServicepublicclassOrderSubmitService{privatefinalRedissonClientredissonClient;privatefinalOrderMapperorderMapper;privateRBloomFilterStringorderBloomFilter;Value(${order.idempotent.ttl})privatelongidempotentTtl;Value(${redis.key.prefix.order})privateStringredisKeyPrefix;// 加载Lua脚本privateDefaultRedisScriptLongidempotentScript;publicOrderSubmitService(RedissonClientredissonClient,OrderMapperorderMapper){this.redissonClientredissonClient;this.orderMapperorderMapper;}PostConstructpublicvoidloadLuaScript(){idempotentScriptnewDefaultRedisScript();idempotentScript.setResultType(Long.class);idempotentScript.setLocation(newClassPathResource(lua/order_idempotent.lua));// 关联布隆过滤器orderBloomFilterredissonClient.getBloomFilter(order:bloom:filter);}/** * 订单正式提交全链路幂等校验 * param sm3Digest 前端传过来的SM3摘要 * param orderInfo 订单业务实体 * return 提交结果 */Transactional(rollbackForException.class)publicStringsubmitOrder(Stringsm3Digest,OrderInfoorderInfo){// 第一层布隆过滤器快速拦截 booleanmightContainorderBloomFilter.contains(sm3Digest);if(!mightContain){return请求非法/重复提交请重试;}// 第二层Redis Lua 原子校验 StringredisKeyredisKeyPrefixsm3Digest;ListStringkeysCollections.singletonList(redisKey);LongresultredissonClient.getScript().eval(RScript.Mode.READ_WRITE,idempotentScript,RScript.ReturnType.INTEGER,keys);// Lua返回0Key不存在拒绝if(result0){return订单已提交或请求重复;}// 执行业务逻辑 // 示例扣库存、风控、生成业务流水等// ... 自定义业务代码 ...// 第三层数据库落库 唯一索引兜底 try{orderMapper.insert(orderInfo);}catch(Exceptione){// 捕获唯一索引冲突 / SQL异常回补Redis幂等Key允许用户重试redissonClient.getBucket(redisKey).set(1,idempotentTtl,java.util.concurrent.TimeUnit.SECONDS);return订单创建失败请稍后重试;}return下单成功;}}// 订单数据库实体classOrderInfo{privateLongorderId;// 短订单ID主键privateStringsm3Digest;// SM3摘要联合唯一索引字段privateLonguserId;privateLonggoodsId;// 其他业务字段 getter setter}6. 数据库表结构MySQLCREATETABLEorder_main(idbigintNOTNULLAUTO_INCREMENTCOMMENT自增主键(可选),order_idbigintNOTNULLCOMMENT对外短订单ID,sm3_digestvarchar(64)NOTNULLCOMMENTSM3幂等摘要,user_idbigintNOTNULLCOMMENT用户ID,goods_idbigintNOTNULLCOMMENT商品ID,order_amountdecimal(10,2)NOTNULLCOMMENT订单金额,create_timedatetimeNOTNULLDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(id),-- 主键短订单ID保证订单唯一UNIQUEKEYuk_order_id(order_id),-- 业务联合唯一索引兜底哈希碰撞、缓存失效UNIQUEKEYuk_sm3_user(sm3_digest,user_id))ENGINEInnoDBDEFAULTCHARSETutf8mb4COMMENT订单主表;五、关键异常处理规则布隆过滤器误判现象正常订单被布隆判定为存在 → 进入Redis二次校验Redis无Key直接拒绝优化调小误判率配合监控统计误判次数。Redis Key 未正常删除服务宕机/脚本异常解决方案依赖TTL自动过期超时后自动释放幂等占位。数据库落库失败唯一索引冲突处理逻辑事务回滚 回补Redis幂等Key用户可重新提交。服务重启布隆数据丢失优化方案项目启动时定时任务从Redis加载近期有效摘要预热布隆过滤器。六、运维监控建议监控指标布隆过滤器命中数、误判数Lua脚本执行失败率Redis幂等Key堆积数量数据库唯一索引冲突次数。告警规则DB唯一索引频繁冲突疑似哈希碰撞/恶意刷单Lua执行异常、Redis连接超时。扩容建议超大流量场景使用分片布隆过滤器拆分数据降低单节点压力。七、方案总结分层防护完整布隆(前置) → RedisLua(原子) → DB索引(兜底)高并发性能与幂等安全性兼顾SM3保证数据防篡改短ID解决长字符串索引性能问题TTL异常回补解决死键、重试问题代码可直接基于SpringBootRedisson落地适配主流微服务架构。