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

SpringBoot+Redis实现电商秒杀方案

一、系统目标

在整个秒杀活动中的核心稳定性目标主要有以下几点:

  1. 系统不崩溃:保证即使在峰值流量下系统依然可用。
  2. 数据一致性:保证库存准确性,不出现超卖现象。
  3. 高性能:保证绝大多数请求在100毫秒内响应。

二、系统架构设计

前端页面 → 网关层 → 业务层 → Redis → 消息队列 → 数据库(库存校验)        (异步下单)
2.1 分层削峰
  1. 前端层面:

    • 目标: 拦截80%无效请求
    • 措施:
      • 按钮防重复点击
      • 验证码校验
      • 活动未开始前端限制
  2. 网关层面:

    • 目标: 过滤90%恶意请求
    • 措施:
      • IP限流
      • 用户限流
      • 恶意请求识别
  3. 服务层面:

    • 目标: 平稳处理核心业务
    • 措施:
      • 令牌桶限流
      • 请求队列化
      • 异步处理
2.2 防超卖核心策略
  1. Redis原子操作:保证库存扣减的原子性
  2. 分布式锁:防止同一用户重复抢购
  3. 库存预热:活动开始前将库存加载到Redis
  4. 异步下单:快速响应,后端异步处理订单

三、代码实现

3.1 实体类定义
// 秒杀活动实体
@Data
public class SeckillActivity {private Long id;private String name;private Long productId;private BigDecimal seckillPrice;private Integer stock;private Integer initialStock;private Date startTime;private Date endTime;private Integer status; // 0-未开始 1-进行中 2-已结束
}// 秒杀订单实体
@Data
public class SeckillOrder {private Long id;private Long userId;private Long activityId;private Long productId;private String orderNo;private BigDecimal amount;private Integer quantity;private Integer status; // 0-待支付 1-已支付 2-已取消private Date createTime;
}
3.2 Redis Key管理
@Component
public class RedisKeyManager {// 秒杀库存KEYpublic static String getStockKey(Long activityId) {return "seckill:stock:" + activityId;}// 用户秒杀记录KEY(防重复抢购)public static String getUserSeckillKey(Long activityId, Long userId) {return "seckill:user:" + activityId + ":" + userId;}// 秒杀活动信息KEYpublic static String getActivityKey(Long activityId) {return "seckill:activity:" + activityId;}// 分布式锁KEYpublic static String getSeckillLockKey(Long activityId) {return "seckill:lock:" + activityId;}
}
3.3 库存预热服务
@Service
@Slf4j
public class SeckillPreheatService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate SeckillActivityMapper activityMapper;/*** 预热秒杀库存到Redis*/public void preheatStock(Long activityId) {SeckillActivity activity = activityMapper.selectById(activityId);if (activity == null) {throw new RuntimeException("秒杀活动不存在");}String stockKey = RedisKeyManager.getStockKey(activityId);String activityKey = RedisKeyManager.getActivityKey(activityId);// 设置库存redisTemplate.opsForValue().set(stockKey, activity.getStock());// 缓存活动信息redisTemplate.opsForValue().set(activityKey, activity);log.info("秒杀活动{}库存预热完成,库存量:{}", activityId, activity.getStock());}/*** 获取Redis中的库存*/public Integer getStockFromRedis(Long activityId) {String stockKey = RedisKeyManager.getStockKey(activityId);Object stockObj = redisTemplate.opsForValue().get(stockKey);return stockObj != null ? Integer.parseInt(stockObj.toString()) : 0;}
}
3.4 核心秒杀服务(防超卖关键)
@Service
@Slf4j
public class SeckillService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate RabbitTemplate rabbitTemplate;/*** 方案一:使用Redis原子操作扣减库存(推荐)* 利用Redis单线程特性,保证原子性*/public SeckillResult seckillV1(Long activityId, Long userId) {// 1. 参数校验if (activityId == null || userId == null) {return SeckillResult.error("参数错误");}// 2. 校验用户是否重复参与String userSeckillKey = RedisKeyManager.getUserSeckillKey(activityId, userId);if (Boolean.TRUE.equals(redisTemplate.hasKey(userSeckillKey))) {return SeckillResult.error("请勿重复参与秒杀");}// 3. 原子扣减库存String stockKey = RedisKeyManager.getStockKey(activityId);Long remainStock = redisTemplate.opsForValue().decrement(stockKey);if (remainStock == null) {return SeckillResult.error("秒杀活动不存在");}if (remainStock < 0) {// 库存不足,恢复库存// 这里恢复库存意图是好的,为了确保库存不会变成负数// 在极高并发下,可能会出现多个线程都扣减了库存,然后都发现库存为负数,然后都恢复了库存。这样,库存最终恢复到了0,但是实际上这些线程都没有成功下单。所以,并没有超卖。// 但是在这个过程中, 每次扣减都要操作Redis两次(decrement和increment),增加了Redis的负担,并不是个完美的方案。redisTemplate.opsForValue().increment(stockKey);return SeckillResult.error("商品已售罄");}try {// 4. 记录用户秒杀成功redisTemplate.opsForValue().set(userSeckillKey, "1", Duration.ofMinutes(30));// 5. 发送异步下单消息sendSeckillOrderMessage(activityId, userId);return SeckillResult.success("秒杀成功,请尽快支付");} catch (Exception e) {// 异常情况恢复库存redisTemplate.opsForValue().increment(stockKey);redisTemplate.delete(userSeckillKey);log.error("秒杀异常", e);return SeckillResult.error("系统繁忙,请重试");}}/*** 方案二:使用Lua脚本保证原子性(更安全)*/public SeckillResult seckillV2(Long activityId, Long userId) {String luaScript = "local stockKey = KEYS[1] " +"local userKey = KEYS[2] " +"local activityId = ARGV[1] " +"local userId = ARGV[2] " +// 检查用户是否已参与"if redis.call('exists', userKey) == 1 then " +"   return 2 " +"end " +// 检查库存"local stock = tonumber(redis.call('get', stockKey)) " +"if not stock or stock <= 0 then " +"   return 3 " +"end " +// 扣减库存"redis.call('decr', stockKey) " +// 记录用户参与"redis.call('setex', userKey, 1800, 1) " +"return 1 ";List<String> keys = Arrays.asList(RedisKeyManager.getStockKey(activityId),RedisKeyManager.getUserSeckillKey(activityId, userId));DefaultRedisScript<Long> script = new DefaultRedisScript<>();script.setScriptText(luaScript);script.setResultType(Long.class);Long result = redisTemplate.execute(script, keys, activityId.toString(), userId.toString());switch (result.intValue()) {case 1:sendSeckillOrderMessage(activityId, userId);return SeckillResult.success("秒杀成功");case 2:return SeckillResult.error("请勿重复参与");case 3:return SeckillResult.error("商品已售罄");default:return SeckillResult.error("秒杀失败");}}/*** 方案三:分布式锁 + Redis原子操作(最安全,适合极端高并发)*/public SeckillResult seckillV3(Long activityId, Long userId) {String lockKey = RedisKeyManager.getSeckillLockKey(activityId);RLock lock = redissonClient.getLock(lockKey);try {// 尝试加锁,最多等待100ms,锁有效期30秒boolean locked = lock.tryLock(100, 30000, TimeUnit.MILLISECONDS);if (!locked) {return SeckillResult.error("系统繁忙,请重试");}// 执行秒杀逻辑return seckillV2(activityId, userId);} catch (InterruptedException e) {Thread.currentThread().interrupt();return SeckillResult.error("系统异常");} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}/*** 发送秒杀订单消息到消息队列*/private void sendSeckillOrderMessage(Long activityId, Long userId) {Map<String, Object> message = new HashMap<>();message.put("activityId", activityId);message.put("userId", userId);message.put("createTime", System.currentTimeMillis());rabbitTemplate.convertAndSend("seckill.order.exchange", "seckill.order.route", message);log.info("发送秒杀订单消息: activityId={}, userId={}", activityId, userId);}
}
3.5 消息队列消费者(异步下单)
@Component
@Slf4j
public class SeckillOrderConsumer {@Autowiredprivate SeckillOrderService orderService;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@RabbitListener(queues = "seckill.order.queue")public void processSeckillOrder(Map<String, Object> message) {Long activityId = Long.valueOf(message.get("activityId").toString());Long userId = Long.valueOf(message.get("userId").toString());try {// 创建订单orderService.createSeckillOrder(activityId, userId);log.info("秒杀订单创建成功: activityId={}, userId={}", activityId, userId);} catch (Exception e) {log.error("创建秒杀订单失败", e);// 创建订单失败,恢复库存recoverStock(activityId, userId);}}/*** 恢复库存*/private void recoverStock(Long activityId, Long userId) {String stockKey = RedisKeyManager.getStockKey(activityId);String userKey = RedisKeyManager.getUserSeckillKey(activityId, userId);redisTemplate.opsForValue().increment(stockKey);redisTemplate.delete(userKey);log.warn("恢复库存: activityId={}, userId={}", activityId, userId);}
}
3.6 订单服务
@Service
@Transactional
@Slf4j
public class SeckillOrderService {@Autowiredprivate SeckillOrderMapper orderMapper;@Autowiredprivate SeckillActivityMapper activityMapper;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 创建秒杀订单*/public void createSeckillOrder(Long activityId, Long userId) {// 双重校验,防止消息重复消费String userKey = RedisKeyManager.getUserSeckillKey(activityId, userId);if (!Boolean.TRUE.equals(redisTemplate.hasKey(userKey))) {log.warn("用户秒杀记录不存在: activityId={}, userId={}", activityId, userId);return;}// 查询活动信息SeckillActivity activity = getActivityFromCache(activityId);if (activity == null) {throw new RuntimeException("秒杀活动不存在");}// 生成订单号String orderNo = generateOrderNo();// 创建订单SeckillOrder order = new SeckillOrder();order.setUserId(userId);order.setActivityId(activityId);order.setProductId(activity.getProductId());order.setOrderNo(orderNo);order.setAmount(activity.getSeckillPrice());order.setQuantity(1);order.setStatus(0); // 待支付order.setCreateTime(new Date());orderMapper.insert(order);// 更新数据库库存(可选,用于对账)updateDatabaseStock(activityId);log.info("创建秒杀订单成功: orderNo={}, userId={}", orderNo, userId);}/*** 从缓存获取活动信息*/private SeckillActivity getActivityFromCache(Long activityId) {String activityKey = RedisKeyManager.getActivityKey(activityId);Object activityObj = redisTemplate.opsForValue().get(activityKey);if (activityObj instanceof SeckillActivity) {return (SeckillActivity) activityObj;}return activityMapper.selectById(activityId);}/*** 更新数据库库存*/private void updateDatabaseStock(Long activityId) {int rows = activityMapper.decreaseStock(activityId);if (rows == 0) {log.error("更新数据库库存失败: activityId={}", activityId);throw new RuntimeException("库存不足");}}/*** 生成订单号*/private String generateOrderNo() {return "SO" + System.currentTimeMillis() + RandomUtil.randomNumbers(6);}
}
3.7 控制器层
@RestController
@RequestMapping("/seckill")
@Slf4j
public class SeckillController {@Autowiredprivate SeckillService seckillService;@Autowiredprivate SeckillPreheatService preheatService;/*** 秒杀接口*/@PostMapping("/{activityId}")public SeckillResult seckill(@PathVariable Long activityId, @RequestHeader("userId") Long userId) {try {// 使用方案二:Lua脚本保证原子性return seckillService.seckillV2(activityId, userId);} catch (Exception e) {log.error("秒杀异常", e);return SeckillResult.error("系统繁忙,请重试");}}/*** 预热库存*/@PostMapping("/preheat/{activityId}")public String preheat(@PathVariable Long activityId) {preheatService.preheatStock(activityId);return "预热成功";}/*** 查询库存*/@GetMapping("/stock/{activityId}")public Integer getStock(@PathVariable Long activityId) {return preheatService.getStockFromRedis(activityId);}
}// 返回结果封装
@Data
class SeckillResult {private boolean success;private String message;private String orderNo;public static SeckillResult success(String message) {SeckillResult result = new SeckillResult();result.setSuccess(true);result.setMessage(message);return result;}public static SeckillResult error(String message) {SeckillResult result = new SeckillResult();result.setSuccess(false);result.setMessage(message);return result;}
}

四、方案总结

  1. Redis原子操作

    // 关键代码:原子扣减库存
    Long remainStock = redisTemplate.opsForValue().decrement(stockKey);
    
  2. Lua脚本原子性

    • 将库存检查、扣减、用户记录等多个操作封装在一个Lua脚本中
    • Redis单线程执行,保证原子性
  3. 分布式锁

    // 防止极端情况下的并发问题
    RLock lock = redissonClient.getLock(lockKey);
    lock.tryLock(100, 30000, TimeUnit.MILLISECONDS);
    
  4. 用户防重

    // 记录用户参与记录
    String userSeckillKey = RedisKeyManager.getUserSeckillKey(activityId, userId);
    redisTemplate.opsForValue().set(userSeckillKey, "1", Duration.ofMinutes(30));
    
  5. 异常恢复机制

    // 下单失败时恢复库存
    redisTemplate.opsForValue().increment(stockKey);
    redisTemplate.delete(userKey);
    
http://www.dtcms.com/a/461432.html

相关文章:

  • 电子商务网站模板 html数据型网站
  • 【QT常用技术讲解】QSerialPort串口开发,包含文件发送功能
  • STM32 外设驱动模块【含代码】:SG90 舵机模块
  • 深圳城乡和住房建设局网站263企业邮箱官网登录
  • K8s概念基础(一)
  • 计算机视觉毕业设计选题该如何选?——根据自身情况合理选择
  • 返利网一类的网站怎么做深圳好看的网站建设哪家公司好
  • 2025-2031年全球 MT 插芯市场全景分析报告:技术演进、供需格局与投资前景
  • 优化的网站做域名跳转做网站现在什么尺寸合适
  • 北京网站建设中企云达电商平台项目运营策划方案
  • 符号主义对人工智能自然语言处理中深层语义分析的影响与启示
  • Excel 常用功能自救手册:遇到问题快速排查指南 (个人备忘版)
  • Excel 数据自动校对:AI 助力精准对比与高效复核
  • 迅为RK3568开发板OpenHarmony系统南向驱动开发手册-实操-HDF驱动配置LED-新增topeet子系统
  • 提供佛山顺德网站建设宜宾注册公司
  • AI原生应用架构白皮书 - AI应用开发框架及上下文工程
  • 北京品牌网站建设公司排名广州有哪些建筑公司
  • [教学资料] Web架构 | 前后端开发模式演进:从混合到分离的技术之路
  • IDEA2025无法更新使用Terminal控制台
  • LLaVA-OneVision论文阅读
  • CentOS 7.9安装OpenSSL 1.1.1w
  • JavaWeb后端实战(MySql基础)
  • 网站设计 三把火科技家装商城系统网站建设
  • 新书速览|AI摄影与创意设计:Stable Diffusion-ComfyUI
  • 网站源码是啥wordpress 移植
  • NLP-注意力机制
  • 从比特流到可靠帧——【深入理解计算机网络05】数据链路层:组帧,差错控制,流量控制与可靠传输的工程级落地
  • React + Ant Design + Tailwind CSS 打造「无痕」垂直滚动区域:功能全上,滚动条隐身
  • 设计模式篇之 模式总览(分类)
  • Kubernetes 入门指南