ToB企服应用市场:ToB评测及商务社交产业平台

标题: 高并发场景秒杀抢购超卖Bug实战重现 [打印本页]

作者: 小小小幸运    时间: 2024-12-19 04:44
标题: 高并发场景秒杀抢购超卖Bug实战重现


引言

在电商平台的秒杀运动中,高并发场景下的抢购超卖Bug是一个常见且棘手的问题。一旦处理不妥,不仅会引发用户投诉,还会对商家的信誉和长处造成严重损害。本文将具体先容秒杀抢购超卖Bug的背景汗青、业务场景、底层原理以及Java代码实现,旨在资助开辟者更好地理解和解决这一问题。
背景汗青

秒杀运动的劈头与发展

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

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

秒杀运动的典范特点

秒杀运动具有以下几个典范特点:
秒杀流程概述

秒杀流程通常包括以下几个步调:
底层原理

数据库层面的并发控制

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

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

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

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

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

在秒杀运动开始前,可以将商品库存数据加载到Redis缓存中。在秒杀过程中,系统可以直接从Redis缓存中读取库存数据,并执行库存扣减操作。这样可以制止直接访问数据库,从而减轻数据库的压力。
消息队列削峰填谷

在秒杀运动开始前,可以将用户的秒杀哀求写入消息队列中。后端服务可以按照顺序从消息队列中消费秒杀哀求,并执行库存扣减和订单天生操作。这样可以将高并发的秒杀哀求分散到差别的时间段内处理,从而制止系统瞬间过载。
Java代码实现

环境准备

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

项目布局

项目布局如下:
  1. 复制代码
  2. seckill-demo
  3. ├── src
  4. │   ├── main
  5. │   │   ├── java
  6. │   │   │   └── com
  7. │   │   │       └── example
  8. │   │   │           └── seckill
  9. │   │   │               ├── SeckillApplication.java
  10. │   │   │               ├── controller
  11. │   │   │               │   └── SeckillController.java
  12. │   │   │               ├── service
  13. │   │   │               │   ├── SeckillService.java
  14. │   │   │               │   └── impl
  15. │   │   │               │       └── SeckillServiceImpl.java
  16. │   │   │               ├── repository
  17. │   │   │               │   └── SeckillRepository.java
  18. │   │   │               └── util
  19. │   │   │                   └── RedisLockUtil.java
  20. │   │   └── resources
  21. │   │       ├── application.properties
  22. │   │       └── mybatis-config.xml
  23. └── pom.xml
复制代码
依赖配置

在pom.xml文件中添加项目所需的依赖:
  1. <dependencies>
  2. <!-- Spring Boot Starter Web -->
  3. <dependency>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-starter-web</artifactId>
  6. </dependency>
  7. <!-- Spring Boot Starter Data JPA -->
  8. <dependency>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-starter-data-jpa</artifactId>
  11. </dependency>
  12. <!-- MySQL Connector -->
  13. <dependency>
  14. <groupId>mysql</groupId>
  15. <artifactId>mysql-connector-java</artifactId>
  16. </dependency>
  17. <!-- Redis Client -->
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter-data-redis</artifactId>
  21. </dependency>
  22. <!-- MyBatis Spring Boot Starter -->
  23. <dependency>
  24. <groupId>org.mybatis.spring.boot</groupId>
  25. <artifactId>mybatis-spring-boot-starter</artifactId>
  26. <version>2.2.0</version>
  27. </dependency>
  28. <!-- Lombok -->
  29. <dependency>
  30. <groupId>org.projectlombok</groupId>
  31. <artifactId>lombok</artifactId>
  32. <optional>true</optional>
  33. </dependency>
  34. </dependencies>
复制代码
数据库配置

在application.properties文件中配置数据库连接信息:
  1. spring.datasource.url=jdbc:mysql://localhost:3306/seckill_demo?useSSL=false&serverTimezone=UTC
  2. spring.datasource.username=root
  3. spring.datasource.password=root
  4. spring.jpa.hibernate.ddl-auto=update
  5. spring.jpa.show-sql=true
  6. spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
复制代码
实体类

创建商品和订单实体类:
  1. package com.example.seckill.entity;
  2. import lombok.Data;
  3. import javax.persistence.Entity;
  4. import javax.persistence.GeneratedValue;
  5. import javax.persistence.GenerationType;
  6. import javax.persistence.Id;
  7. @Data
  8. @Entity
  9. public class SeckillGoods {
  10. @Id
  11. @GeneratedValue(strategy = GenerationType.IDENTITY)
  12. private Long id;
  13. private String name;
  14. private Integer stock;
  15. private Integer price;
  16. }
  17. @Data
  18. @Entity
  19. public class SeckillOrder {
  20. @Id
  21. @GeneratedValue(strategy = GenerationType.IDENTITY)
  22. private Long id;
  23. private Long userId;
  24. private Long goodsId;
  25. private Integer orderPrice;
  26. private Integer orderStatus;
  27. }
复制代码
Repository接口

创建商品和订单的Repository接口:
  1. package com.example.seckill.repository;
  2. import com.example.seckill.entity.SeckillGoods;
  3. import com.example.seckill.entity.SeckillOrder;
  4. import org.springframework.data.jpa.repository.JpaRepository;
  5. import org.springframework.stereotype.Repository;
  6. @Repository
  7. public interface SeckillGoodsRepository extends JpaRepository<SeckillGoods, Long> {
  8. }
  9. @Repository
  10. public interface SeckillOrderRepository extends JpaRepository<SeckillOrder, Long> {
  11. }
复制代码
Service层

创建秒杀服务接口和实现类:
  1. package com.example.seckill.service;
  2. import com.example.seckill.entity.SeckillGoods;
  3. import com.example.seckill.entity.SeckillOrder;
  4. public interface SeckillService {
  5.     SeckillGoods getGoodsById(Long goodsId);
  6. boolean reduceStock(Long goodsId);
  7.     SeckillOrder createOrder(Long userId, Long goodsId);
  8. }
  9. package com.example.seckill.service.impl;
  10. import com.example.seckill.entity.SeckillGoods;
  11. import com.example.seckill.entity.SeckillOrder;
  12. import com.example.seckill.repository.SeckillGoodsRepository;
  13. import com.example.seckill.repository.SeckillOrderRepository;
  14. import com.example.seckill.service.SeckillService;
  15. import com.example.seckill.util.RedisLockUtil;
  16. import org.springframework.beans.factory.annotation.Autowired;
  17. import org.springframework.data.redis.core.StringRedisTemplate;
  18. import org.springframework.stereotype.Service;
  19. import org.springframework.transaction.annotation.Transactional;
  20. @Service
  21. public class SeckillServiceImpl implements SeckillService {
  22. @Autowired
  23. private SeckillGoodsRepository goodsRepository;
  24. @Autowired
  25. private SeckillOrderRepository orderRepository;
  26. @Autowired
  27. private StringRedisTemplate redisTemplate;
  28. @Override
  29. public SeckillGoods getGoodsById(Long goodsId) {
  30. return goodsRepository.findById(goodsId).orElse(null);
  31.     }
  32. @Override
  33. @Transactional
  34. public boolean reduceStock(Long goodsId) {
  35. // 使用Redis分布式锁防止超卖
  36. String lockKey = "seckill_lock_" + goodsId;
  37. boolean lockAcquired = RedisLockUtil.tryLock(redisTemplate, lockKey, 10);
  38. if (!lockAcquired) {
  39. return false; // 获取锁失败,返回秒杀失败
  40.         }
  41. try {
  42. SeckillGoods goods = goodsRepository.findById(goodsId).orElse(null);
  43. if (goods == null || goods.getStock() <= 0) {
  44. return false; // 商品不存在或库存不足,返回秒杀失败
  45.             }
  46.             goods.setStock(goods.getStock() - 1);
  47.             goodsRepository.save(goods);
  48. return true; // 库存扣减成功,返回秒杀成功
  49.         } finally {
  50.             RedisLockUtil.unlock(redisTemplate, lockKey); // 释放锁
  51.         }
  52.     }
  53. @Override
  54. @Transactional
  55. public SeckillOrder createOrder(Long userId, Long goodsId) {
  56. SeckillOrder order = new SeckillOrder();
  57.         order.setUserId(userId);
  58.         order.setGoodsId(goodsId);
  59.         order.setOrderPrice(getGoodsById(goodsId).getPrice());
  60.         order.setOrderStatus(1); // 假设订单状态为1表示已支付
  61. return orderRepository.save(order);
  62.     }
  63. }
复制代码
Redis分布式锁工具类

创建Redis分布式锁工具类:
  1. package com.example.seckill.util;
  2. import org.springframework.data.redis.core.StringRedisTemplate;
  3. import org.springframework.stereotype.Component;
  4. import java.util.concurrent.TimeUnit;
  5. @Component
  6. public class RedisLockUtil {
  7. public static boolean tryLock(StringRedisTemplate redisTemplate, String lockKey, int expireTime) {
  8. Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", expireTime, TimeUnit.SECONDS);
  9. return Boolean.TRUE.equals(result);
  10.     }
  11. public static void unlock(StringRedisTemplate redisTemplate, String lockKey) {
  12.         redisTemplate.delete(lockKey);
  13.     }
  14. }
复制代码
Controller层

创建秒杀控制器:
  1. package com.example.seckill.controller;
  2. import com.example.seckill.entity.SeckillGoods;
  3. import com.example.seckill.entity.SeckillOrder;
  4. import com.example.seckill.service.SeckillService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.web.bind.annotation.*;
  7. @RestController
  8. @RequestMapping("/seckill")
  9. public class SeckillController {
  10. @Autowired
  11. private SeckillService seckillService;
  12. @GetMapping("/goods/{id}")
  13. public SeckillGoods getGoodsById(@PathVariable Long id) {
  14. return seckillService.getGoodsById(id);
  15.     }
  16. @PostMapping("/seckill/{id}")
  17. public String seckill(@PathVariable Long id, @RequestParam Long userId) {
  18. if (seckillService.reduceStock(id)) {
  19. SeckillOrder order = seckillService.createOrder(userId, id);
  20. return "秒杀成功,订单ID:" + order.getId();
  21.         } else {
  22. return "秒杀失败,库存不足";
  23.         }
  24.     }
  25. }
复制代码
启动类

创建Spring Boot启动类:
  1. package com.example.seckill;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class SeckillApplication {
  6. public static void main(String[] args) {
  7.         SpringApplication.run(SeckillApplication.class, args);
  8.     }
  9. }
复制代码
实战重现

数据库初始化

在MySQL数据库中创建商品和订单表,并插入初始数据:
  1. CREATE TABLE seckill_goods (
  2.     id BIGINT AUTO_INCREMENT PRIMARY KEY,
  3.     name VARCHAR(255) NOT NULL,
  4.     stock INT NOT NULL,
  5.     price INT NOT NULL
  6. );
  7. CREATE TABLE seckill_order (
  8.     id BIGINT AUTO_INCREMENT PRIMARY KEY,
  9.     user_id BIGINT NOT NULL,
  10.     goods_id BIGINT NOT NULL,
  11.     order_price INT NOT NULL,
  12.     order_status INT NOT NULL
  13. );
  14. INSERT INTO seckill_goods (name, stock, price) VALUES ('iPhone 14', 10, 4999);
复制代码
启动项目

启动Spring Boot项目,确保Redis和MySQL服务已经启动。
模拟秒杀哀求

利用Postman或curl等工具模拟秒杀哀求,例如:
  1. bash复制代码
  2. curl -X POST "http://localhost:8080/seckill/seckill/1?userId=123456"
复制代码
观察结果

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

本文通过背景汗青、业务场景、底层原理以及Java代码实现等方面具体先容了高并发场景秒杀抢购超卖Bug的实战重现。通过利用Redis分布式锁和数据库乐观锁等本事,可以有效防止超卖问题的发生。同时,通过缓存优化和消息队列削峰填谷等计谋,可以进一步进步系统的并发处理本事和稳定性。盼望本文可以或许资助开辟者更好地理解和解决秒杀抢购超卖Bug问题。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4