高并发场景秒杀抢购超卖Bug实战重现

简介: 在电商平台的秒杀活动中,高并发场景下的抢购超卖Bug是一个常见且棘手的问题。一旦处理不当,不仅会引发用户投诉,还会对商家的信誉和利益造成严重损害。本文将详细介绍秒杀抢购超卖Bug的背景历史、业务场景、底层原理以及Java代码实现,旨在帮助开发者更好地理解和解决这一问题。


引言

在电商平台的秒杀活动中,高并发场景下的抢购超卖Bug是一个常见且棘手的问题。一旦处理不当,不仅会引发用户投诉,还会对商家的信誉和利益造成严重损害。本文将详细介绍秒杀抢购超卖Bug的背景历史、业务场景、底层原理以及Java代码实现,旨在帮助开发者更好地理解和解决这一问题。

背景历史

秒杀活动的起源与发展

秒杀活动起源于电商平台的促销活动,旨在通过限时限量的低价商品吸引大量用户参与,从而提升平台流量和销售额。随着电商行业的快速发展,秒杀活动逐渐成为各大电商平台的标配,每年双11、618等大型购物节期间,秒杀活动更是成为用户抢购的热点。

超卖问题的出现与影响

然而,在高并发场景下,秒杀活动往往面临超卖问题的挑战。所谓超卖,是指在商品库存已经售罄的情况下,系统仍然允许用户下单购买,导致实际售出的商品数量超过库存数量。超卖问题不仅会导致商家亏损,还会引发用户投诉和信任危机,对平台的声誉和长期发展造成严重影响。

业务场景

秒杀活动的典型特点

秒杀活动具有以下几个典型特点:

  1. 定时触发,流量瞬间突增:秒杀活动通常在固定时间点开始,用户会在活动开始前大量涌入,导致系统流量瞬间突增。
  2. 请求量远大于库存量:由于秒杀商品的价格远低于市场价,因此会吸引大量用户参与抢购,导致请求量远大于库存量。
  3. 业务逻辑简单,但并发要求高:秒杀活动的业务逻辑相对简单,主要是库存扣减和订单生成,但对系统的并发处理能力要求极高。

秒杀流程概述

秒杀流程通常包括以下几个步骤:

  1. 用户请求秒杀:用户在秒杀活动开始后,通过前端页面发起秒杀请求。
  2. 系统校验请求:系统对用户的秒杀请求进行校验,包括用户身份验证、商品库存检查等。
  3. 库存扣减:如果校验通过,系统执行库存扣减操作,将商品库存数量减一。
  4. 生成订单:库存扣减成功后,系统生成秒杀订单,并将订单信息存储到数据库中。
  5. 返回结果给用户:系统将秒杀结果返回给用户,包括秒杀成功或失败的信息。

底层原理

数据库层面的并发控制

在高并发场景下,数据库层面的并发控制是防止超卖问题的关键。常见的并发控制手段包括悲观锁、乐观锁和分布式锁等。

悲观锁

悲观锁是一种独占锁,它在数据读取时就会对数据进行加锁,以防止其他事务对数据进行修改。在秒杀场景中,可以使用数据库的排他锁(如MySQL的SELECT ... FOR UPDATE)来实现悲观锁。然而,悲观锁会导致其他事务在读取数据时被阻塞,从而降低系统的并发处理能力。

乐观锁

乐观锁是一种乐观的并发控制手段,它假设在数据读取到提交更新的这段时间内,数据不会被其他事务修改。在更新数据时,乐观锁会检查数据是否被其他事务修改过,如果被修改过,则放弃更新操作。在秒杀场景中,可以使用数据库的版本号(如MySQL的version字段)来实现乐观锁。然而,乐观锁在高并发场景下可能存在更新成功率较低的问题。

分布式锁

分布式锁是一种跨进程的锁机制,它可以在分布式系统中实现多个进程之间的互斥。在秒杀场景中,可以使用Redis等分布式缓存系统来实现分布式锁。然而,分布式锁的实现需要考虑锁的失效问题、死锁问题等。

缓存层面的优化

为了减轻数据库的压力,提高系统的并发处理能力,可以在缓存层面进行优化。常见的缓存优化手段包括使用Redis等内存数据库来缓存库存数据、使用消息队列来削峰填谷等。

Redis缓存库存数据

在秒杀活动开始前,可以将商品库存数据加载到Redis缓存中。在秒杀过程中,系统可以直接从Redis缓存中读取库存数据,并执行库存扣减操作。这样可以避免直接访问数据库,从而减轻数据库的压力。

消息队列削峰填谷

在秒杀活动开始前,可以将用户的秒杀请求写入消息队列中。后端服务可以按照顺序从消息队列中消费秒杀请求,并执行库存扣减和订单生成操作。这样可以将高并发的秒杀请求分散到不同的时间段内处理,从而避免系统瞬间过载。

Java代码实现

环境准备

在进行Java代码实现之前,需要准备以下环境:

  • JDK:Java开发工具包,用于编译和运行Java代码。
  • Maven:项目构建工具,用于管理项目的依赖和构建过程。
  • Redis:分布式缓存系统,用于缓存库存数据和实现分布式锁。
  • MySQL:关系型数据库管理系统,用于存储商品和订单数据。

项目结构

项目结构如下:

复制代码
seckill-demo
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           └── seckill
│   │   │               ├── SeckillApplication.java
│   │   │               ├── controller
│   │   │               │   └── SeckillController.java
│   │   │               ├── service
│   │   │               │   ├── SeckillService.java
│   │   │               │   └── impl
│   │   │               │       └── SeckillServiceImpl.java
│   │   │               ├── repository
│   │   │               │   └── SeckillRepository.java
│   │   │               └── util
│   │   │                   └── RedisLockUtil.java
│   │   └── resources
│   │       ├── application.properties
│   │       └── mybatis-config.xml
└── pom.xml

依赖配置

pom.xml文件中添加项目所需的依赖:

xml复制代码
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Redis Client -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- MyBatis Spring Boot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

数据库配置

application.properties文件中配置数据库连接信息:

properties复制代码
spring.datasource.url=jdbc:mysql://localhost:3306/seckill_demo?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

实体类

创建商品和订单实体类:

java复制代码
package com.example.seckill.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Data
@Entity
public class SeckillGoods {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer stock;
private Integer price;
}
@Data
@Entity
public class SeckillOrder {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private Long goodsId;
private Integer orderPrice;
private Integer orderStatus;
}

Repository接口

创建商品和订单的Repository接口:

java复制代码
package com.example.seckill.repository;
import com.example.seckill.entity.SeckillGoods;
import com.example.seckill.entity.SeckillOrder;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SeckillGoodsRepository extends JpaRepository<SeckillGoods, Long> {
}
@Repository
public interface SeckillOrderRepository extends JpaRepository<SeckillOrder, Long> {
}

Service层

创建秒杀服务接口和实现类:

java复制代码
package com.example.seckill.service;
import com.example.seckill.entity.SeckillGoods;
import com.example.seckill.entity.SeckillOrder;
public interface SeckillService {
    SeckillGoods getGoodsById(Long goodsId);
boolean reduceStock(Long goodsId);
    SeckillOrder createOrder(Long userId, Long goodsId);
}
package com.example.seckill.service.impl;
import com.example.seckill.entity.SeckillGoods;
import com.example.seckill.entity.SeckillOrder;
import com.example.seckill.repository.SeckillGoodsRepository;
import com.example.seckill.repository.SeckillOrderRepository;
import com.example.seckill.service.SeckillService;
import com.example.seckill.util.RedisLockUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class SeckillServiceImpl implements SeckillService {
@Autowired
private SeckillGoodsRepository goodsRepository;
@Autowired
private SeckillOrderRepository orderRepository;
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public SeckillGoods getGoodsById(Long goodsId) {
return goodsRepository.findById(goodsId).orElse(null);
    }
@Override
@Transactional
public boolean reduceStock(Long goodsId) {
// 使用Redis分布式锁防止超卖
String lockKey = "seckill_lock_" + goodsId;
boolean lockAcquired = RedisLockUtil.tryLock(redisTemplate, lockKey, 10);
if (!lockAcquired) {
return false; // 获取锁失败,返回秒杀失败
        }
try {
SeckillGoods goods = goodsRepository.findById(goodsId).orElse(null);
if (goods == null || goods.getStock() <= 0) {
return false; // 商品不存在或库存不足,返回秒杀失败
            }
            goods.setStock(goods.getStock() - 1);
            goodsRepository.save(goods);
return true; // 库存扣减成功,返回秒杀成功
        } finally {
            RedisLockUtil.unlock(redisTemplate, lockKey); // 释放锁
        }
    }
@Override
@Transactional
public SeckillOrder createOrder(Long userId, Long goodsId) {
SeckillOrder order = new SeckillOrder();
        order.setUserId(userId);
        order.setGoodsId(goodsId);
        order.setOrderPrice(getGoodsById(goodsId).getPrice());
        order.setOrderStatus(1); // 假设订单状态为1表示已支付
return orderRepository.save(order);
    }
}

Redis分布式锁工具类

创建Redis分布式锁工具类:

java复制代码
package com.example.seckill.util;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisLockUtil {
public static boolean tryLock(StringRedisTemplate redisTemplate, String lockKey, int expireTime) {
Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
    }
public static void unlock(StringRedisTemplate redisTemplate, String lockKey) {
        redisTemplate.delete(lockKey);
    }
}

Controller层

创建秒杀控制器:

java复制代码
package com.example.seckill.controller;
import com.example.seckill.entity.SeckillGoods;
import com.example.seckill.entity.SeckillOrder;
import com.example.seckill.service.SeckillService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/seckill")
public class SeckillController {
@Autowired
private SeckillService seckillService;
@GetMapping("/goods/{id}")
public SeckillGoods getGoodsById(@PathVariable Long id) {
return seckillService.getGoodsById(id);
    }
@PostMapping("/seckill/{id}")
public String seckill(@PathVariable Long id, @RequestParam Long userId) {
if (seckillService.reduceStock(id)) {
SeckillOrder order = seckillService.createOrder(userId, id);
return "秒杀成功,订单ID:" + order.getId();
        } else {
return "秒杀失败,库存不足";
        }
    }
}

启动类

创建Spring Boot启动类:

java复制代码
package com.example.seckill;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SeckillApplication {
public static void main(String[] args) {
        SpringApplication.run(SeckillApplication.class, args);
    }
}

实战重现

数据库初始化

在MySQL数据库中创建商品和订单表,并插入初始数据:

sql复制代码
CREATE TABLE seckill_goods (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    stock INT NOT NULL,
    price INT NOT NULL
);
CREATE TABLE seckill_order (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    goods_id BIGINT NOT NULL,
    order_price INT NOT NULL,
    order_status INT NOT NULL
);
INSERT INTO seckill_goods (name, stock, price) VALUES ('iPhone 14', 10, 4999);

启动项目

启动Spring Boot项目,确保Redis和MySQL服务已经启动。

模拟秒杀请求

使用Postman或curl等工具模拟秒杀请求,例如:

bash复制代码
curl -X POST "http://localhost:8080/seckill/seckill/1?userId=123456"

观察结果

在秒杀过程中,观察Redis缓存中的库存变化以及MySQL数据库中订单表的记录。确保在高并发场景下不会出现超卖问题。

总结

本文通过背景历史、业务场景、底层原理以及Java代码实现等方面详细介绍了高并发场景秒杀抢购超卖Bug的实战重现。通过使用Redis分布式锁和数据库乐观锁等手段,可以有效防止超卖问题的发生。同时,通过缓存优化和消息队列削峰填谷等策略,可以进一步提高系统的并发处理能力和稳定性。希望本文能够帮助开发者更好地理解和解决秒杀抢购超卖Bug问题。

相关文章
|
3天前
|
存储 运维 安全
云上金融量化策略回测方案与最佳实践
2024年11月29日,阿里云在上海举办金融量化策略回测Workshop,汇聚多位行业专家,围绕量化投资的最佳实践、数据隐私安全、量化策略回测方案等议题进行深入探讨。活动特别设计了动手实践环节,帮助参会者亲身体验阿里云产品功能,涵盖EHPC量化回测和Argo Workflows量化回测两大主题,旨在提升量化投研效率与安全性。
云上金融量化策略回测方案与最佳实践
|
5天前
|
人工智能 自然语言处理 前端开发
从0开始打造一款APP:前端+搭建本机服务,定制暖冬卫衣先到先得
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。
5859 18
|
17天前
|
人工智能 自动驾驶 大数据
预告 | 阿里云邀您参加2024中国生成式AI大会上海站,马上报名
大会以“智能跃进 创造无限”为主题,设置主会场峰会、分会场研讨会及展览区,聚焦大模型、AI Infra等热点议题。阿里云智算集群产品解决方案负责人丛培岩将出席并发表《高性能智算集群设计思考与实践》主题演讲。观众报名现已开放。
|
9天前
|
自然语言处理 数据可视化 API
Qwen系列模型+GraphRAG/LightRAG/Kotaemon从0开始构建中医方剂大模型知识图谱问答
本文详细记录了作者在短时间内尝试构建中医药知识图谱的过程,涵盖了GraphRAG、LightRAG和Kotaemon三种图RAG架构的对比与应用。通过实际操作,作者不仅展示了如何利用这些工具构建知识图谱,还指出了每种工具的优势和局限性。尽管初步构建的知识图谱在数据处理、实体识别和关系抽取等方面存在不足,但为后续的优化和改进提供了宝贵的经验和方向。此外,文章强调了知识图谱构建不仅仅是技术问题,还需要深入整合领域知识和满足用户需求,体现了跨学科合作的重要性。
|
5天前
|
人工智能 容器
三句话开发一个刮刮乐小游戏!暖ta一整个冬天!
本文介绍了如何利用千问开发一款情侣刮刮乐小游戏,通过三步简单指令实现从单个功能到整体框架,再到多端优化的过程,旨在为生活增添乐趣,促进情感交流。在线体验地址已提供,鼓励读者动手尝试,探索编程与AI结合的无限可能。
|
1月前
|
存储 人工智能 弹性计算
阿里云弹性计算_加速计算专场精华概览 | 2024云栖大会回顾
2024年9月19-21日,2024云栖大会在杭州云栖小镇举行,阿里云智能集团资深技术专家、异构计算产品技术负责人王超等多位产品、技术专家,共同带来了题为《AI Infra的前沿技术与应用实践》的专场session。本次专场重点介绍了阿里云AI Infra 产品架构与技术能力,及用户如何使用阿里云灵骏产品进行AI大模型开发、训练和应用。围绕当下大模型训练和推理的技术难点,专家们分享了如何在阿里云上实现稳定、高效、经济的大模型训练,并通过多个客户案例展示了云上大模型训练的显著优势。
|
9天前
|
Cloud Native Apache 流计算
PPT合集|Flink Forward Asia 2024 上海站
Apache Flink 年度技术盛会聚焦“回顾过去,展望未来”,涵盖流式湖仓、流批一体、Data+AI 等八大核心议题,近百家厂商参与,深入探讨前沿技术发展。小松鼠为大家整理了 FFA 2024 演讲 PPT ,可在线阅读和下载。
3506 10
PPT合集|Flink Forward Asia 2024 上海站
|
2天前
|
弹性计算 运维 监控
阿里云云服务诊断工具:合作伙伴架构师的深度洞察与优化建议
作为阿里云的合作伙伴架构师,我深入体验了其云服务诊断工具,该工具通过实时监控与历史趋势分析,自动化检查并提供详细的诊断报告,极大提升了运维效率和系统稳定性,特别在处理ECS实例资源不可用等问题时表现突出。此外,它支持预防性维护,帮助识别潜在问题,减少业务中断。尽管如此,仍建议增强诊断效能、扩大云产品覆盖范围、提供自定义诊断选项、加强教育与培训资源、集成第三方工具,以进一步提升用户体验。
607 242
|
22天前
|
人工智能 自然语言处理 前端开发
100个降噪蓝牙耳机免费领,用通义灵码从 0 开始打造一个完整APP
打开手机,录制下你完成的代码效果,发布到你的社交媒体,前 100 个@玺哥超Carry、@通义灵码的粉丝,可以免费获得一个降噪蓝牙耳机。
5944 16
|
4天前
|
消息中间件 人工智能 运维
12月更文特别场——寻找用云高手,分享云&AI实践
我们寻找你,用云高手,欢迎分享你的真知灼见!
488 37