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

Redis 在订单系统中的实战应用:防重、限流与库存扣减

一、背景:为什么订单系统离不开 Redis?

在高并发订单系统中,我们常常面临三大问题:

问题类型说明常见后果
防重下单用户重复点击“提交订单”按钮重复扣款、重复库存
接口限流秒杀/大促活动瞬时请求暴增系统崩溃、拒绝服务
库存扣减多人同时抢购同一商品超卖、库存为负数

Redis 凭借高性能 + 原子操作 + 多数据结构支持,成为电商类系统“抗高并发”的首选组件。


二、整体架构思维导图

Redis 在订单系统中的实战
├── 防重下单
│   ├── 原理:唯一 Key + TTL
│   └── 实现:SETNX 防重复提交
├── 限流算法
│   ├── 固定窗口
│   ├── 滑动窗口
│   ├── 令牌桶(Token Bucket)
│   └── 漏桶(Leaky Bucket)
└── 库存扣减├── 原理:Redis 原子操作 + Lua 脚本└── 实现:watch + eval 实现库存一致性

三、防重下单:用 Redis 保证接口幂等性

 理论分析

在电商系统中,用户点击“提交订单”可能由于:

  • 网络延迟;

  • 前端多次触发;

  • 用户误操作。

导致同一订单被重复创建

用户操作请求时间服务器行为结果
第一次点击下单0ms创建订单A✅ 成功
第二次点击下单10ms再次创建订单B❌ 重复下单

由于 HTTP 请求是无状态且幂等性不保证的,后端必须自己设计防重机制。

要解决这个问题,我们通常采用 幂等性机制

相同业务请求(用户ID + 订单号)无论请求多少次,结果都一致。

Redis 提供的 SETNX(set if not exists)正好适合这种场景。


示例代码

@Service
public class OrderService {@Autowiredprivate StringRedisTemplate redisTemplate;private static final String ORDER_REPEAT_KEY = "order:repeat:";public String createOrder(String userId, String productId) {String key = ORDER_REPEAT_KEY + userId + ":" + productId;Boolean success = redisTemplate.opsForValue().setIfAbsent(key, "1", 5, TimeUnit.SECONDS); // 设置5秒过期防重复点击if (Boolean.FALSE.equals(success)) {throw new RuntimeException("请勿重复提交订单!");}// 模拟生成订单逻辑return "订单创建成功:" + UUID.randomUUID();}
}

 四、接口限流:Redis 实现令牌桶算法

 限流算法原理对比

算法机制特点
固定窗口每分钟允许 N 次请求简单但可能突发请求
滑动窗口实时统计过去 N 秒请求数更平滑,但计算复杂
漏桶算法固定速率流出稳定但响应不够灵活
令牌桶算法按速率生成令牌,请求需消耗令牌✅ 最灵活且常用

在分布式环境中,我们用 Redis 原子 Lua 脚本 实现令牌桶算法,保证并发安全。


 2.Redis 令牌桶原理

令牌桶算法的核心思想是:

  1. 初始化‌:设置一个桶(bucket),里面有一定数量的令牌(tokens)。
  2. 令牌生成‌:每隔一定的时间(例如每秒),向桶中添加一定数量的令牌。
  3. 请求处理‌:每次处理请求时,从桶中取出一个令牌。如果没有足够的令牌,则请求被阻塞或延迟处理。

       +---------------------+|  Redis Key: rate:api ||---------------------|| tokens = 10         || last_time = 1690000 |+---------------------+|┌────────────────────────────┐│ 每次请求到来:              ││  1. 计算可新增令牌数量       ││  2. 判断是否有足够令牌       ││  3. 减少令牌 or 拒绝请求     │└────────────────────────────┘

3. 示例代码(Redis + Lua 实现令牌桶)

@Component
public class RedisRateLimiter {@Autowiredprivate StringRedisTemplate redisTemplate;private static final String LUA_SCRIPT = """local key = KEYS[1]local rate = tonumber(ARGV[1])  -- 每秒生成令牌数local capacity = tonumber(ARGV[2]) -- 桶容量local now = tonumber(ARGV[3])local requested = tonumber(ARGV[4])local data = redis.call("HMGET", key, "tokens", "timestamp")local tokens = tonumber(data[1])local timestamp = tonumber(data[2])if tokens == nil thentokens = capacitytimestamp = nowendlocal delta = math.max(0, now - timestamp)local new_tokens = math.min(capacity, tokens + delta * rate)local allowed = new_tokens >= requestedif allowed thennew_tokens = new_tokens - requestedendredis.call("HMSET", key, "tokens", new_tokens, "timestamp", now)redis.call("EXPIRE", key, 60)return allowed""";public boolean tryAcquire(String key, int rate, int capacity, int requested) {long now = System.currentTimeMillis() / 1000;DefaultRedisScript<Long> script = new DefaultRedisScript<>(LUA_SCRIPT, Long.class);Long result = redisTemplate.execute(script,Collections.singletonList("rate:" + key),String.valueOf(rate),String.valueOf(capacity),String.valueOf(now),String.valueOf(requested));return result != null && result == 1L;}
}

✅ 使用示例

@RestController
@RequestMapping("/api/order")
public class OrderController {@Autowiredprivate RedisRateLimiter rateLimiter;@GetMapping("/create")public String createOrder(@RequestParam String userId) {boolean pass = rateLimiter.tryAcquire("createOrder", 5, 10, 1);if (!pass) {return "请求过多,请稍后再试!";}return "订单创建成功!";}
}

 五、库存扣减:Lua 保证原子性防止超卖

1️⃣ 问题分析

在秒杀场景下,多个用户同时购买同一商品:

  • 请求1:读取库存=1;

  • 请求2:读取库存=1;

  • 两个线程同时扣减;

  • 库存 = -1(超卖!)

所以必须保证库存扣减原子执行


2️⃣ Redis Lua 脚本实现

@Service
public class StockService {@Autowiredprivate StringRedisTemplate redisTemplate;private static final String STOCK_LUA = """local stock = tonumber(redis.call('GET', KEYS[1]))if stock <= 0 thenreturn -1endredis.call('DECR', KEYS[1])return stock - 1""";public boolean deductStock(String productId) {DefaultRedisScript<Long> script = new DefaultRedisScript<>(STOCK_LUA, Long.class);Long result = redisTemplate.execute(script, Collections.singletonList("stock:" + productId));return result != null && result >= 0;}
}

✅ 使用效果

@RestController
@RequestMapping("/api/stock")
public class StockController {@Autowiredprivate StockService stockService;@GetMapping("/deduct")public String deduct(@RequestParam String productId) {boolean success = stockService.deductStock(productId);return success ? "扣减成功" : "库存不足";}
}

六、总结与扩展

模块Redis 技术点适用场景
防重下单SETNX + TTL幂等请求
接口限流Lua 脚本 + 令牌桶算法防止接口过载
库存扣减Lua + 原子操作秒杀库存一致性

Redis 不仅仅是一个缓存,更是一个轻量级高性能并发控制中心
在实际项目中,这些功能可以与 RedissonSpring AOP分布式锁 结合,构建更健壮的高并发架构。

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

相关文章:

  • flex 做网站去成都旅游攻略怎么做
  • PHP网站开发涉及的工具有哪些秦皇岛市海港区建设局网站
  • 如何定期清理电脑垃圾文件
  • 网站怎么做现场直播视频全国企业信息公示系统查询
  • JAVA村里租房系统小区租售系统源码支持微信小程序 + H5
  • 【图像处理】图像色彩空间 Lab、YCbCr、HSV
  • 怎么自己在电脑上做网站win2008做的网站打不开
  • 嘉兴外贸网站建网站备案号含义
  • 一个虚拟主机可以做几个网站个人网站备案填写要求
  • 通过ssh连接GitHub远程仓库
  • venv - python新手推荐的轻量化环境隔离方式
  • 网站核验单中国外包加工网
  • Ubuntu解决Github无法访问的问题
  • 关于 Qt5.11/12/15的QtCreator中对conncet宏SIGNAL不提示 的解决方法
  • C语言入门(十一)续:函数的深入认识
  • wordpress站点路径网上购物商城官网入口
  • 告别“手绘”图表:Illustrator与XD联动的数据可视化(Data Viz)工作流
  • m-card卡片组件
  • 企业内部网站建设方案怎样营销
  • 推荐一款开源的轻量级知识管理工具
  • GNU/Linux - GCC编译的静态库
  • 西安建网站哪家好企业网站蓝色模板下载
  • 成都模板建站代理网站优化要做哪些工作
  • Java MyBatis(一)--- 注解和XML的使用
  • 东莞官方网站 优帮云网站怎么做推广和优化
  • win64_11gR2_client.zip 怎么安装?Oracle 11g 客户端详细安装步骤
  • 建设向58同城的网站给客户做网站需要提供
  • flink sql 所有函数详细用例
  • Flink 1.20 flink-config.yml 配置详解
  • 湖州网站集约化平台南京做网站哪家公司好