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

引言

在电商平台的秒杀活动中,高并发场景下的抢购超卖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文件中添加项目所需的依赖:

<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文件中配置数据库连接信息:

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

实体类

创建商品和订单实体类:

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接口:

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层

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

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分布式锁工具类:

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层

创建秒杀控制器:

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启动类:

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数据库中创建商品和订单表,并插入初始数据:

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问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/64037.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

redis 架构详解

Redis架构详解可以从以下几个方面进行阐述&#xff1a; 一、部署架构 Redis有多种部署架构&#xff0c;适用于不同的应用场景和需求&#xff0c;主要包括以下几种&#xff1a; 单机模式&#xff08;Standalone Mode&#xff09; 特点&#xff1a;部署简单&#xff0c;配置方便…

[python]使用 Pandas 分组和汇总表数据

在数据分析中&#xff0c;数据的分组与汇总是非常常见的操作。下面使用 Python 的 Pandas 库来处理表数据&#xff0c;并生成汇总结果。 导入数据 首先&#xff0c;我们需要导入必要的库并读取 Excel 文件中的数据&#xff1a; import pandas as pd# 读取工资表数据 df pd.…

多线程的知识总结(8):用 thread 类 或全局 async (...) 函数,创建新线程时,谁才是在新线程里第一个被执行的函数

&#xff08;40&#xff09;用 thread 类 或全局 async (…) 函数&#xff0c;创建新线程时&#xff0c;谁才是在新线程里第一个被执行的函数&#xff1f; 弄清楚这个问题&#xff0c;有利于推测和理解线程中代码的执行流程。根据 thread 类 和 async &#xff08;…&#xff0…

PostgreSql集群安装 Pgpool-II以及服务器设置和操作

2 .安装 Pgpool-II 2.1. 规划 由于 Pgpool-II 是一个管理 PostgreSQL 的工具&#xff0c;我们需要决定如何 首先部署它们。此外&#xff0c;还可以有多个 Pgpool-II 安装数量达到 增强 Pgpool-II 本身的可用性。我们需要提前计划需要多少次 Pgpool-II 安装。在 本章我们首先讨…

UOB大华银行|校招网申综合能力SHL测评题库英语版本真题分析

大华银行有限公司&#xff08;大华银行&#xff09;是亚洲银行业的翘楚&#xff0c;大华银行总部位于新加坡&#xff0c;并在中国、印度尼西亚、马来西亚、泰国及越南设立了全资法人银行&#xff0c;在全球拥有约500 间分行及办事处&#xff0c;分布在亚太、欧洲与北美的19 个国…

WebSpoon9.0(KETTLE的WEB版本)编译 + tomcatdocker部署 + 远程调试教程

前言 Kettle简介 Kettle是一款国外开源的ETL工具&#xff0c;纯Java编写&#xff0c;可以在Window、Linux、Unix上运行&#xff0c;绿色无需安装&#xff0c;数据抽取高效稳定 WebSpoon是Kettle的Web版本&#xff0c;由Kettle社区维护&#xff0c;不受Pentaho支持&#xff0c;…

用人话讲计算机:Python篇!(十二)正则运算+re模块

目录 一、正则表达式 &#xff08;1&#xff09;什么是正则表达式 &#xff08;2&#xff09;它的结构及使用 示例&#xff1a; 1.字符 . &#xff08;←这里有个小点哦&#xff09; 2.字符 | 3.字符 [ ] 4.字符^ 5.字符\d &#xff08;3&#xff09;补充&#xff…

Service Discovery in Microservices 客户端/服务端服务发现

原文链接 Client Side Service Discovery in Microservices - GeeksforGeeks 原文链接 Server Side Service Discovery in Microservices - GeeksforGeeks 目录 服务发现介绍 Server-Side 服务发现 实例&#xff1a; Client-Side 服务发现 实例&#xff1a; 服务发现介绍…

基于Python深度学习的【猫狗宠物识别】系统设计实现

一、简介 宠物识别系统&#xff0c;本系统使用Python作为主要开发语言&#xff0c;基于TensorFlow搭建卷积神经网络算法&#xff0c;并收集了37种常见的猫狗宠物种类数据集【‘阿比西尼亚猫&#xff08;Abyssinian&#xff09;’, ‘孟加拉猫&#xff08;Bengal&#xff09;’…

不能通过 ip 直接访问 共享盘 解决方法

from base_config.config import OpenSMB, SMB import os, time, calendar, requests, decimal, platform, fs.smbfsinfo_dict SMB.EPDI_dict info_dict[host] (FS03,10.6.12.182) info_dict[direct_tcp] True# smb OpenSMB(info_dict)print(ok)# 根据 ip 查询电脑名 impor…

11. qml ShaderEffect实现阴影效果

目录 MDropShadow.qml使用 MDropShadow.qml 基于上一章所制作的MGaussianBlur.qml 开发 MDropShadow阴影效果 import QtQuick 2.12Item {id: controlproperty var source;property real radius: 4property bool cached: falseproperty int offsetX: 0property int offsetY: 0…

10篇--图像噪点消除

概念 何为噪点&#xff1f; 噪点&#xff1a;指图像收到的一些干扰因素&#xff0c;通常是由图像采集设备、传输信道等因素造成的&#xff0c;表现为图像中随机的亮度&#xff0c;也可以理解为有那么一些点的像素值与周围的像素值格格不入。 常见的噪声类型 高斯噪声&#…

科研绘图系列:R语言绘制网络图和密度分布图(network density plot)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载图1图2图3图4图5图6图7图8系统信息参考介绍 R语言绘制网络图和密度分布图(network & density plot) 加载R包 library(magrittr) library(dplyr) library(…

数据结构——ST表

ST表的定义 ST表&#xff0c;又名稀疏表&#xff0c;是一种基于倍增思想&#xff0c;用于解决可重复贡献问题的数据结构 倍增思想 这里列举一个去寻找一个区间内的最大值的例子 因为每次会将将区间增大一倍&#xff0c;所以才被称之为倍增思想 &#xff0c;这种思想十分好用…

创建vue3项目步骤以及安装第三方插件步骤【保姆级教程】

&#x1f399;座右铭&#xff1a;得之坦然&#xff0c;失之淡然。 &#x1f48e;擅长领域&#xff1a;前端 是的&#xff0c;我需要您的&#xff1a; &#x1f9e1;点赞❤️关注&#x1f499;收藏&#x1f49b; 是我持续下去的动力&#xff01; 目录 一. 简单汇总一下创建…

【PlantUML系列】部署图(七)

一、部署图的组成部分 节点&#xff08;Node&#xff09;&#xff1a;使用node关键字定义一个节点&#xff0c;节点可以是服务器、数据库或其他硬件设备。组件&#xff08;Component&#xff09;&#xff1a;使用component关键字定义一个组件&#xff0c;组件可以是软件模块或服…

hive 小文件分析

1、获取fsimage文件&#xff1a; hdfs dfsadmin -fetchImage /data/xy/ 2、从二进制文件解析&#xff1a; hdfs oiv -i /data/xy/fsimage_0000000019891608958 -t /data/xy/tmpdir -o /data/xy/out -p Delimited -delimiter “,” 3、创建hive表 create database if not exists…

java agent 介绍

java agent 系列 java agent 介绍 java agent-02-Java Instrumentation API java agent-03-Java Instrumentation 结合 bytekit 实战笔记 agent attach java agent-03-Java Instrumentation 结合 bytekit 实战笔记 agent premain java -javaagent 解释 在Java中&#xff…

【JAVA】旅游行业中大数据的使用

一、应用场景 数据采集与整合&#xff1a;全面收集旅游数据&#xff0c;如客流量、游客满意度等&#xff0c;整合形成统一数据集&#xff0c;为后续分析提供便利。 舆情监测与分析&#xff1a;实时监测旅游目的地的舆情信息&#xff0c;运用NLP算法进行智能处理&#xff0c;及…

Halcon面试题及参考答案

Halcon 的图像数据类型是什么? Halcon 中有多种图像数据类型。其中一种重要的数据类型是单通道图像(byte 类型),这种类型的图像像素值通常用一个字节(8 位)来表示,范围是 0 - 255,例如灰度图像,0 代表黑色,255 代表白色,中间的值代表不同程度的灰色。 还有整型图像,…