6步带你用Spring Boot开发出商城高并发秒杀系统

spring,boot,开发,商城,并发,秒杀,系统 · 浏览次数 : 275

小编点评

**排版内容** * **标题**:华为云新鲜技术~ * **主标题**:秒杀活动 * **内容** * 了解华为云新鲜技术~ * 秒杀活动介绍 * 秒杀活动步骤 * 如何确保商品库存数量的一致性和秒杀结果的正确性 * 使用分布式锁实现秒杀接口 * 注意事项 * **结尾** * 返回华为云新鲜技术~

正文

摘要:本博客将介绍如何使用 Spring Boot 实现一个简单的商城秒杀系统,并通过使用 Redis 和 MySQL 来增强其性能和可靠性。

本文分享自华为云社区《Spring Boot实现商城高并发秒杀案例》,作者:林欣。

随着经济的发展和人们消费观念的转变,电子商务逐渐成为人们购物的主要方式之一。高并发是电子商务网站面临的一个重要挑战。本博客将介绍如何使用 Spring Boot 实现一个简单的商城秒杀系统,并通过使用 Redis 和 MySQL 来增强其性能和可靠性。

准备工作

在开始之前,您需要准备以下工具和环境:

  • JDK 1.8 或更高版本
  • Redis
  • MySQL
  • MyBatis

实现步骤

步骤一:创建数据库

首先,我们需要创建一个数据库来存储商品信息、订单信息和秒杀活动信息。在这里,我们使用 MySQL 数据库,创建一个名为 shop 的数据库,并建立三个表 goods、order 和 seckill。

表 goods 存储了所有的商品信息,包括商品编号、名称、描述、价格和库存数量等等。

CREATE TABLE `goods` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
 `name` varchar(50) NOT NULL COMMENT '商品名称',
 `description` varchar(100) NOT NULL COMMENT '商品描述',
 `price` decimal(10,2) NOT NULL COMMENT '商品价格',
 `stock_count` int(11) NOT NULL COMMENT '商品库存',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

表 order 存储了所有的订单信息,包括订单编号、用户ID、商品ID、秒杀活动ID 和订单状态等等。

CREATE TABLE `order` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID',
 `user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
 `goods_id` BIGINT(20) NOT NULL COMMENT '商品ID',
 `seckill_id` BIGINT(20) DEFAULT NULL COMMENT '秒杀活动ID',
 `status` TINYINT(4) NOT NULL COMMENT '订单状态,0-未支付,1-已支付,2-已发货,3-已收货,4-已退款,5-已完成',
 `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 PRIMARY KEY (`id`),
 UNIQUE KEY `unique_order` (`user_id`,`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

表 seckill 存储了所有的秒杀活动信息,包括秒杀活动编号、商品ID、开始时间和结束时间等等。

CREATE TABLE `seckill` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '秒杀活动ID',
 `goods_id` BIGINT(20) NOT NULL COMMENT '商品ID',
 `start_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '开始时间',
 `end_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '结束时间',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀活动表';

步骤二:创建 Spring Boot 项目

接下来,我们需要创建一个 Spring Boot 项目,用于实现商城高并发秒杀案例。可以使用 Spring Initializr 来快速创建一个基本的 Spring Boot 项目。

步骤三:配置 Redis 和 MySQL

在 Spring Boot 项目中,我们需要配置 Redis 和 MySQL 的连接信息。可以在 application.properties 文件中设置以下属性:

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=123456

步骤四:编写实体类和 DAO 接口

在这一步中,我们需要定义三个实体类分别对应数据库中的 goods、order 和 seckill 表。同时,我们需要编写相应的 DAO 接口,用于操作这些实体类。

// 商品实体类
@Data
public class Goods {
 private Long id;
 private String name;
 private String description;
 private BigDecimal price;
 private Integer stockCount;
}
// 商品 DAO 接口
@Mapper
public interface GoodsDao {
    @Select("SELECT * FROM goods WHERE id = #{id}")
    Goods getGoodsById(Long id);
    @Update("UPDATE goods SET stock_count = stock_count - 1 WHERE id = #{id} AND stock_count > 0")
    int reduceStockCount(Long id);
}
// 订单实体类
@Data
public class Order {
 private Long id;
 private Long userId;
 private Long goodsId;
 private Long seckillId;
 private Byte status;
 private Date createTime;
 private Date updateTime;
}
// 订单 DAO 接口
@Mapper
public interface OrderDao {
    @Select("SELECT * FROM `order` WHERE user_id = #{userId} AND goods_id = #{goodsId}")
    Order getOrderByUserIdAndGoodsId(@Param("userId") Long userId, @Param("goodsId") Long goodsId);
    @Insert("INSERT INTO `order` (user_id, goods_id, seckill_id, status, create_time, update_time) VALUES (#{userId}, #{goodsId}, #{seckillId}, #{status},#{createTime},#{updateTime})")
    int insertOrder(Order order);
    @Select("SELECT o.*, g.name, g.price FROM `order` o LEFT JOIN goods g ON o.goods_id = g.id WHERE o.user_id = #{userId}")
    List<OrderVo> getOrderListByUserId(Long userId);
}
// 秒杀活动实体类
@Data
public class Seckill {
 private Long id;
 private Long goodsId;
 private Date startTime;
 private Date endTime;
}
// 秒杀活动 DAO 接口
@Mapper
public interface SeckillDao {
    @Select("SELECT * FROM seckill WHERE id = #{id}")
    Seckill getSeckillById(Long id);
    @Update("UPDATE seckill SET end_time = #{endTime} WHERE id = #{id}")
    int updateSeckillEndTime(@Param("id") Long id, @Param("endTime") Date endTime);
}

步骤五:编写 Service 层和 Controller

在这一步中,我们需要编写 Service 层和 Controller 类,用于实现商城高并发秒杀案例的核心功能。

  • 商品 Service 层:用于获取商品信息和减少商品库存数量。
@Service
public class GoodsService {
 private final GoodsDao goodsDao;
    @Autowired
 public GoodsService(GoodsDao goodsDao) {
 this.goodsDao = goodsDao;
 }
 public Goods getGoodsById(Long id) {
 return goodsDao.getGoodsById(id);
 }
 public boolean reduceStockCount(Long id) {
 return goodsDao.reduceStockCount(id) > 0;
 }
}
  • 订单 Service 层:用于创建订单和获取订单信息。
@Service
public class OrderService {
 private final OrderDao orderDao;
    @Autowired
 public OrderService(OrderDao orderDao) {
 this.orderDao = orderDao;
 }
 public Order createOrder(Long userId, Long goodsId, Long seckillId) {
        Order order = new Order();
        order.setUserId(userId);
        order.setGoodsId(goodsId);
        order.setSeckillId(seckillId);
        order.setStatus((byte) 0);
        order.setCreateTime(new Date());
        order.setUpdateTime(new Date());
        orderDao.insertOrder(order);
 return order;
 }
 public List<OrderVo> getOrderListByUserId(Long userId) {
 return orderDao.getOrderListByUserId(userId);
 }
}
  • 秒杀活动 Service 层:用于获取秒杀活动信息和更新秒杀活动结束时间。
@Service
public class SeckillService {
 private final SeckillDao seckillDao;
    @Autowired
 public SeckillService(SeckillDao seckillDao) {
 this.seckillDao = seckillDao;
 }
 public Seckill getSeckillById(Long id) {
 return seckillDao.getSeckillById(id);
 }
 public boolean updateSeckillEndTime(Long id, Date endTime) {
 return seckillDao.updateSeckillEndTime(id, endTime) > 0;
 }
}
  • 订单 Controller:用于处理订单相关的请求。
@RestController
@RequestMapping("/order")
public class OrderController {
 private final OrderService orderService;
    @Autowired
 public OrderController(OrderService orderService) {
 this.orderService = orderService;
 }
    @PostMapping("/create")
 public CommonResult<Order> createOrder(@RequestParam("userId") Long userId,
                                           @RequestParam("goodsId") Long goodsId,
                                           @RequestParam("seckillId") Long seckillId) {
        Order order = orderService.createOrder(userId, goodsId, seckillId);
 if (order == null) {
 return CommonResult.failed(ResultCode.FAILURE);
 }
 return CommonResult.success(order);
 }
    @GetMapping("/list")
 public CommonResult<List<OrderVo>> getOrderListByUserId(@RequestParam("userId") Long userId) {
        List<OrderVo> orderList = orderService.getOrderListByUserId(userId);
 return CommonResult.success(orderList);
 }
}

秒杀活动 Controller:用于处理秒杀活动相关的请求。

@RestController
@RequestMapping("/seckill")
public class SeckillController {
 private final SeckillService seckillService;
 private final GoodsService goodsService;
 private final OrderService orderService;
    @Autowired
 public SeckillController(SeckillService seckillService, GoodsService goodsService, OrderService orderService) {
 this.seckillService = seckillService;
 this.goodsService = goodsService;
 this.orderService = orderService;
 }
    @PostMapping("/start")
 public CommonResult<Object> startSeckill(@RequestParam("userId") Long userId,
                                             @RequestParam("goodsId") Long goodsId,
                                             @RequestParam("seckillId") Long seckillId) {
 // 查询秒杀活动是否有效
        Seckill seckill = seckillService.getSeckillById(seckillId);
 if (seckill == null || seckill.getStartTime().after(new Date()) || seckill.getEndTime().before(new Date())) {
 return CommonResult.failed(ResultCode.FAILURE, "秒杀活动不存在或已结束");
 }
 // 判断商品库存是否充足
        Goods goods = goodsService.getGoodsById(goodsId);
 if (goods == null || goods.getStockCount() <= 0) {
 return CommonResult.failed(ResultCode.FAILURE, "商品库存不足");
 }
 // 生成订单
        Order order = orderService.createOrder(userId, goodsId, seckillId);
 if (order == null) {
 return CommonResult.failed(ResultCode.FAILURE, "订单创建失败,请稍后再试");
 }
 // 减少商品库存
        boolean success = goodsService.reduceStockCount(goodsId);
 if (!success) {
 return CommonResult.failed(ResultCode.FAILURE, "减少商品库存失败,请稍后再试");
 }
 return CommonResult.success("秒杀成功");
 }
}

步骤六:使用 Redis 实现分布式锁

在商城高并发秒杀案例中,一个重要的问题是如何保证商品库存数量的一致性和秒杀结果的正确性。为了解决这个问题,我们可以使用 Redis 实现分布式锁。

在 RedisService 类中实现分布式锁:

@Service
public class RedisService {
 private final RedisTemplate<String, Object> redisTemplate;
    @Autowired
 public RedisService(RedisTemplate<String, Object> redisTemplate) {
 this.redisTemplate = redisTemplate;
 }
 public boolean lock(String key, String value, long expire) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofSeconds(expire));
 return result != null && result;
 }
 public void unlock(String key, String value) {
 if (value.equals(redisTemplate.opsForValue().get(key))) {
            redisTemplate.delete(key);
 }
 }
}

在 SeckillService 中使用分布式锁实现秒杀接口:

@Service
public class SeckillService {
 private final RedisService redisService;
 private final SeckillDao seckillDao;
 private final GoodsDao goodsDao;
 private final OrderDao orderDao;
    @Autowired
 public SeckillService(RedisService redisService, SeckillDao seckillDao, GoodsDao goodsDao, OrderDao orderDao) {
 this.redisService = redisService;
 this.seckillDao = seckillDao;
 this.goodsDao = goodsDao;
 this.orderDao = orderDao;
 }
 public CommonResult<Object> startSeckill(Long userId, Long goodsId, Long seckillId) {
        String lockKey = "seckill:lock:" + goodsId;
        String lockValue = UUID.randomUUID().toString();
 try {
 // 获取分布式锁
 if (!redisService.lock(lockKey, lockValue, 10)) {
 return CommonResult.failed(ResultCode.FAILURE, "当前请求太过频繁,请稍后再试");
 }
 // 查询秒杀活动是否有效
            Seckill seckill = seckillDao.getSeckillById(seckillId);
 if (seckill == null || seckill.getStartTime().after(new Date()) || seckill.getEndTime().before(new Date())) {
 return CommonResult.failed(ResultCode.FAILURE, "秒杀活动不存在或已结束");
 }
 // 判断商品库存是否充足
            Goods goods = goodsDao.getGoodsById(goodsId);
 if (goods == null || goods.getStockCount() <= 0) {
 return CommonResult.failed(ResultCode.FAILURE, "商品库存不足");
 }
 // 创建订单
            Order order = new Order();
            order.setUserId(userId);
            order.setGoodsId(goodsId);
            order.setSeckillId(seckillId);
            order.setStatus((byte) 0);
            order.setCreateTime(new Date());
            order.setUpdateTime(new Date());
            int count = orderDao.insertOrder(order);
 if (count <= 0) {
 return CommonResult.failed(ResultCode.FAILURE, "订单创建失败,请稍后再试");
 }
 // 减少商品库存
            boolean success = goodsDao.reduceStockCount(goodsId) > 0;
 if (!success) {
 throw new Exception("减少商品库存失败,请稍后再试");
 }
 return CommonResult.success("秒杀成功");
 } catch (Exception e) {
            e.printStackTrace();
 return CommonResult.failed(ResultCode.FAILURE, "秒杀失败," + e.getMessage());
 } finally {
 // 释放分布式锁
            redisService.unlock(lockKey, lockValue);
 }
 }
}

 

点击关注,第一时间了解华为云新鲜技术~

与6步带你用Spring Boot开发出商城高并发秒杀系统相似的内容:

6步带你用Spring Boot开发出商城高并发秒杀系统

摘要:本博客将介绍如何使用 Spring Boot 实现一个简单的商城秒杀系统,并通过使用 Redis 和 MySQL 来增强其性能和可靠性。 本文分享自华为云社区《Spring Boot实现商城高并发秒杀案例》,作者:林欣。 随着经济的发展和人们消费观念的转变,电子商务逐渐成为人们购物的主要方式之

阅读翻译Mathematics for Machine Learning之2.7 Linear Mappings

阅读翻译Mathematics for Machine Learning之2.7 Linear Mappings 关于: 首次发表日期:2024-07-23 Mathematics for Machine Learning官方链接: https://mml-book.com ChatGPT和KIMI

前端使用 Konva 实现可视化设计器(18)- 素材嵌套 - 加载阶段

本章主要实现素材的嵌套(加载阶段)这意味着可以拖入画布的对象,不只是图片素材,还可以是嵌套的图片和图形。

Langchain 与 LlamaIndex:LLM 应用开发框架的比较与使用建议

Langchain 和 Llamaindex 是两种广泛使用的主流 LLM 应用开发框架。两者有什么不同?我们该如何使用?以下我根据各类资料和相关文档做了初步选型。 一、Langchain 1. 适用场景 (1)需要构建灵活、可扩展的通用应用程序。 (2)需要复杂的工作流程支持。 (3)需要复杂的交

全网最适合入门的面向对象编程教程:20 类和对象的 Python 实现-组合关系的实现与 CSV 文件保存

本文主要介绍了在使用Python面向对象编程时,如何实现组合关系,同时对比了组合关系和继承关系的优缺点,并讲解了如何通过csv模块来保存Python接收/生成的数据。

全网最适合入门的面向对象编程教程:18 类和对象的 Python 实现-多重继承与 PyQtGraph 串口数据绘制曲线图

本文主要介绍了Python中创建自定义类时如何使用多重继承、菱形继承的概念和易错点,同时讲解了如何使用PyQtGraph库对串口接收的数据进行绘图。

Nuxt.js头部魔法:轻松自定义页面元信息,提升用户体验

title: Nuxt.js头部魔法:轻松自定义页面元信息,提升用户体验 date: 2024/7/16 updated: 2024/7/16 author: [ cmdragon ](https://cmdragon.cn) excerpt: 摘要:“Nuxt.js头部魔法:轻松自定义页面元信息,

全网最适合入门的面向对象编程教程:17 类和对象的Python实现-鸭子类型与“file-like object“

本文主要介绍了Python中创建自定义类时鸭子类型的基本定义、特点和应用场景,同时列举了“file-like object“的例子对鸭子类型进行了说明。

Serverless无服务应用架构纵横谈2:边缘计算激战正酣

Serverless无服务应用架构纵横谈2 前言 6年前,我写了一篇《Serverless无服务应用架构纵横谈》。 文中说到无论是公有云FaaS还是私有云FaaS,都不是云计算的未来。 因为无论是Kubernetes还是PHP、Python、Node这些传统技术栈都太重了。 那么,6年来,Serve

算法金 | 来了,pandas 2.0

大侠幸会,在下全网同名「算法金」 0 基础转 AI 上岸,多个算法赛 Top 「日更万日,让更多人享受智能乐趣」 今日 210+/10000,内含 Pandas 是一个强大的数据分析库,广泛应用于科学研究、金融分析、商业智能等领域。它提供了高效的数据结构和数据分析工具,使得处理和分析数据变得更加简单