Java-redis实现限时在线秒杀功能

news2025/6/6 17:27:47

1.使用redisson pom文件添加redisson

 <!--redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.23.4</version>
        </dependency>

2.mysql数据库表设计

CREATE TABLE `order` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `product_id` bigint(20) NOT NULL COMMENT '商品ID',
  `order_no` varchar(50) NOT NULL COMMENT '订单编号',
  `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '订单状态(0:未支付,1:已支付,2:已取消)',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `pay_time` datetime DEFAULT NULL COMMENT '支付时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_order_no` (`order_no`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1929810703233400835 DEFAULT CHARSET=utf8mb4 COMMENT='订单表';




CREATE TABLE `product` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
  `name` varchar(100) NOT NULL COMMENT '商品名称',
  `price` decimal(10,2) NOT NULL COMMENT '商品价格',
  `stock` int(11) NOT NULL COMMENT '库存数量',
  `start_time` datetime DEFAULT NULL COMMENT '秒杀开始时间',
  `end_time` datetime DEFAULT NULL COMMENT '秒杀结束时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1929810179796844547 DEFAULT CHARSET=utf8mb4 COMMENT='秒杀商品表';


CREATE TABLE `seckill_record` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `product_id` bigint(20) NOT NULL COMMENT '商品ID',
  `order_no` varchar(50) DEFAULT NULL COMMENT '订单编号',
  `seckill_time` datetime NOT NULL COMMENT '秒杀时间',
  `status` tinyint(4) NOT NULL COMMENT '状态(0:秒杀成功未支付,1:已支付,2:已取消)',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_product` (`user_id`,`product_id`) COMMENT '用户商品唯一索引',
  KEY `idx_product_id` (`product_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1929810703233400836 DEFAULT CHARSET=utf8mb4 COMMENT='秒杀记录表';

3.实现逻辑

1.新增秒杀商品,添加库存数量,设置秒杀开始时间和结束时间

2.确认开始秒杀,将库存数量入redis中,避免进行超卖

3.进行秒杀

 3.1.根据商品id查询商品是否存在

 3.2.判断当前时间是否在秒杀开始时间

 3.3. 获取Redisson锁

 3.4.判断当前人是否已经秒杀过,避免重复秒杀

 3.5.判断库存是否充足,避免超卖,库存不足,返回商品已售罄

 3.6.判断库存充足,扣减库存并记录用户

 3.7.秒杀成功创建订单,返回订单号

 3.8.关闭锁,避免死锁

4.代码实现

1.秒杀控制器

@RestController
@RequestMapping("/seckill")
public class SeckillController {
    @Autowired
    private SeckillService seckillService;

    /**
     *  确认开始秒杀,初始化商品库存到Redis
     * @param product
     * @return
     */
    @PostMapping("/initStock")
    public AjaxResult initStock(@RequestBody Product product) {
        return seckillService.initStock(product);
    }

    /**
     * 开始秒杀
     * @return
     */
    @PostMapping("/start")
    public AjaxResult startSeckill(Long userId, Long productId) {
        return seckillService.startSeckill(userId,productId);
    }

}

2.秒杀实现类

package com.thk.service.impl;

import com.thk.domain.Order;
import com.thk.domain.Product;
import com.thk.domain.SeckillRecord;
import com.thk.mapper.OrderMapper;
import com.thk.mapper.ProductMapper;
import com.thk.mapper.SeckillRecordMapper;
import com.thk.service.SeckillService;
import com.thk.utils.AjaxResult;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private SeckillRecordMapper seckillRecordMapper;
    @Autowired
    private RedissonClient redissonClient;

    // 商品库存key前缀
    private static final String STOCK_PREFIX = "seckill:stock:";
    // 秒杀用户集合key前缀(用于防止重复秒杀)
    private static final String USER_SET_PREFIX = "seckill:users:";
    // 分布式锁key前缀
    private static final String LOCK_PREFIX = "seckill:lock:";

    /**
     * 确认开始秒杀,初始化商品库存到Redis
     *
     * @param product
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult initStock(Product product) {
        Product product1 = productMapper.selectById(product.getId());
        if (product1 != null) {
            String stockKey = STOCK_PREFIX + product1.getId();
            RAtomicLong atomicLong = redissonClient.getAtomicLong( stockKey );
            atomicLong.set(product1.getStock());

            // 设置过期时间(秒)
            long expireSeconds = (product1.getEndTime().getTime() - System.currentTimeMillis()) / 1000;
            boolean expireResult = atomicLong.expire(expireSeconds, TimeUnit.SECONDS);

            return AjaxResult.success(expireResult);
        }
        return AjaxResult.error("商品不存在");
    }



    /**
     * 开始秒杀
     *
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)

    public AjaxResult startSeckill(Long userId, Long productId) {
        // 1. 验证秒杀时间
        Product product = productMapper.selectById(productId);
        if (product == null) return AjaxResult.error("商品不存在");

        long now = System.currentTimeMillis();
        if (now < product.getStartTime().getTime()) {
            return AjaxResult.error("秒杀未开始");
        }
        if (now > product.getEndTime().getTime()) {
            return AjaxResult.error("秒杀已结束");
        }

        // 2. 获取Redisson分布式锁
        String lockKey = LOCK_PREFIX + productId;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if (!locked) return AjaxResult.error("系统繁忙,请稍后再试");

            // 3. 使用Redisson的RSet检查重复秒杀
            String userKey = USER_SET_PREFIX + productId;
            RSet<String> userSet = redissonClient.getSet(userKey);
            if (userSet.contains(userId.toString())) {
                return AjaxResult.error("不能重复秒杀");
            }

            // 4. 使用Redisson的RAtomicLong检查库存
            String stockKey = STOCK_PREFIX + productId;
            RAtomicLong atomicStock = redissonClient.getAtomicLong(stockKey);
            if (atomicStock.get() <= 0) {
                return AjaxResult.error("商品已售罄");
            }

            // 5. 扣减库存并记录用户
            atomicStock.decrementAndGet();
            userSet.add(userId.toString());

            // 6. 创建订单
            String orderNo = generateOrderNo();
            createOrder(userId, productId, orderNo);
            return AjaxResult.success(orderNo);

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return AjaxResult.error("系统异常");
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                //关闭锁
                lock.unlock();
            }
        }
    }




    private void createOrder(Long userId, Long productId, String orderNo) {
        Order order = new Order();
        order.setUserId( userId );
        order.setProductId( productId );
        order.setOrderNo( orderNo );
        order.setStatus( 0 );
        order.setCreateTime( new Date() );
        orderMapper.insert( order );

        SeckillRecord record = new SeckillRecord();
        record.setUserId( userId );
        record.setProductId( productId );
        record.setOrderNo( orderNo );
        record.setSeckillTime( new Date() );
        record.setStatus( 0 );
        seckillRecordMapper.insert( record );
    }

    private String generateOrderNo() {
        return "O" + System.currentTimeMillis();
    }
}

5.测试

1.添加秒杀商品,设置库存,开始时间,结束时间

2.确认开始,将库存存入redis中

redis查看库存是否存在

3.开始秒杀,输入用户id和商品编号进行秒杀

秒杀成功,创建订单,新增商品秒杀记录,返回订单号,redis库存减少,redis新增用户秒杀记录

订单记录

秒杀记录

redis库存

redis新增用户秒杀记录

4.重复秒杀

5.避免超卖

6.秒杀结束,超时秒杀结束

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

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

相关文章

simulink mask、sfunction和tlc的联动、接口

这里全部是讲的level2 sfunction&#xff08;用m语言编写&#xff09;&#xff0c;基于matlab 2020a。 1.mask的参数操作 1&#xff09;mask通过set_param和get_param这2个函数接口对mask里面定义的Parameters&Dialog的参数的大部分属性进行读写&#xff0c;一般是Value值…

VMWare安装常见问题

如果之前安装过VMWare软件&#xff0c;只要是 15/16 版本的&#xff0c;可以正常使用的&#xff0c;不用卸载&#xff01;&#xff01;&#xff01; 如果之前安装过&#xff0c;卸载了&#xff0c;一定要保证通过正常的渠道去卸载&#xff08;通过控制面板卸载软件&#xff09…

【北邮 操作系统】第十二章 文件系统实现

一、文件的物理结构 1.1 文件块、磁盘块 类似于内存分页&#xff0c;磁盘中的存储单元也会被分为一个个“块/磁盘块/物理块”。很多操作系统中&#xff0c;磁盘块的大小与内存块、页面的大小相同 内存与磁盘之间的数据交换(即读/写操作、磁盘I/0)都是以“块”为单位进行的。即…

Docker 插件生态:从网络插件到存储插件的扩展能力解析

Docker 容器技术以其轻量、快速、可移植的特性,迅速成为构建和部署现代应用的核心工具。然而,尽管 Docker Engine 自身功能强大,但在面对多样化的生产环境和复杂业务需求时,仅靠核心功能往往无法满足所有场景。 例如,跨主机的容器网络通信、异构存储系统的持久化数据管理…

WordPress搜索引擎优化的最佳重定向插件:进阶指南

在管理网站时&#xff0c;我们经常需要调整网页地址或修复错误链接。这时&#xff0c;通过重定向不仅能有效解决这些问题&#xff0c;还能显著提升网站在搜索引擎中的排名。对于熟悉基础重定向插件的用户来说&#xff0c;一些功能更强大的工具可以帮助你更全面地管理网站&#…

org.junit.runners.model.InvalidTestClassError:此类问题的解决

不知道大家是否遇见过以上这种情况&#xff0c;我也是今天被这个错误搞得很烦&#xff0c;后来通过网上查找资料终于找到了问题所在————就是简单的Test注解的错误使用 Test注解的注意情况 &#xff1a;1 权限必须是public 2 不能有参数 3 返回值类型是void 4 本类的其他的…

用户管理页面(解决toggleRowSelection在dialog用不了的隐患,包含el-table的plus版本的组件)

新增/编辑/删除/分配角色&#xff0c;图片上传在此文章分类下另一个文章 1.重点分配角色&#xff1a; <template><!-- 客户资料 --><div class"pageBox"><elPlusTable :tableData"tableData" :tablePage"tablePage" onSi…

Linux-GCC、makefile、GDB

GCC gcc -E test.c -o test.i预处理(-o指定文件名) gcc -S test.i -o test.s编译gcc -c test.s -o test.o汇编gcc test.o -o test链接(生成一个可执行程序的软连接) gcc test.c -o test一条指令可以完成以上所有内容 gcc *.c -I(大写的i) include由于在main.c中找不到当前文件…

[MySQL初阶]MySQL(7) 表的内外连接

标题&#xff1a;[MySQL初阶]MySQL(7)表的内外连接 水墨不写bug 文章目录 一. 内连接 (INNER JOIN)二. 外连接 (OUTER JOIN)关键区别总结 三、 如何选择 在 MySQL 中&#xff0c;连接&#xff08;JOIN&#xff09;用于根据两个或多个表之间的相关列组合行。内连接&#xff08;I…

【R语言编程绘图-mlbench】

mlbench库简介 mlbench是一个用于机器学习的R语言扩展包&#xff0c;主要用于提供经典的基准数据集和工具&#xff0c;常用于算法测试、教学演示或研究场景。该库包含多个知名数据集&#xff0c;涵盖分类、回归、聚类等任务。 包含的主要数据集 BostonHousing 波士顿房价数据…

检索器组件深入学习与使用技巧 BaseRetriever 检索器基类

1. BaseRetriever 检索器基类 在 LangChain 中&#xff0c;传递一段 query 并返回与这段文本相关联文档的组件被称为 检索器&#xff0c;并且 LangChain 为所有检索器设计了一个基类——BaseRetriever&#xff0c;该类继承了 RunnableSerializable&#xff0c;所以该类是一个 …

【Doris基础】Doris中的Replica详解:Replica原理、架构

目录 1 Replica基础概念 1.1 什么是Replica 1.2 Doris中的副本类型 2 Doris副本架构设计 2.1 副本分布机制 2.2 副本一致性模型 3 副本生命周期管理 3.1 副本创建流程 3.2 副本恢复机制 4 副本读写流程详解 4.1 写入流程与副本同步 4.2 查询流程与副本选择 5 副本…

【中国·广州】第三届信号处理与智能计算国际学术会议 (SPIC2025) 即将开启

第三届信号处理与智能计算国际学术会议 (SPIC2025) 即将开启 在信息技术飞速发展的当下&#xff0c;信号处理与智能计算作为前沿科技领域&#xff0c;正深刻改变着我们的生活与产业格局。为汇聚全球顶尖智慧&#xff0c;推动该领域进一步突破&#xff0c;第三届信号处理与智能…

Android12 Launcher3显示所有应用列表

Android12 Launcher3显示所有应用列表 1.前言&#xff1a; 最近在Android12Rom定制时需要显示所有桌面应用的图标&#xff0c;并且不能去掉抽屉&#xff0c;在手机上面抽屉和所有应该列表是两种不同模式&#xff0c;用户基可以自行选择&#xff0c;但是在自定义的launcher中这…

智能快递地址解析接口如何用PHP调用?

一、什么是智能快递地址解析接口 随着互联网技术的普及和电子商务的迅猛发展&#xff0c;网购已成为现代人日常生活的重要组成部分。然而&#xff0c;在这个便捷的背后&#xff0c;一个看似不起眼却影响深远的问题正悄然浮现——用户填写的快递地址格式混乱、信息不全甚至错漏…

华为OD机试真题——模拟消息队列(2025B卷:100分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 B卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《模拟消息队列》: 目录 题…

基于VU37P的高性能采集板卡

基于VU37P的高性能采集板卡是一款最大可提供20路ADC接收通道的高性能采集板卡。每路A/D通道支持1GS/s的采样率&#xff0c;分辨率为14bit&#xff0c;模拟输入带宽可达500MHz&#xff0c;交流耦合&#xff0c;输入阻抗50欧姆。 产品简介 可提供20路ADC接收通道的高性能采集板…

2025-05-31 Python深度学习10——模型训练流程

文章目录 1 数据准备1.1 下载与预处理1.2 数据加载 2 模型构建2.1 自定义 CNN 模型2.2 GPU加速 3 训练配置3.1 损失函数3.2 优化器3.3 训练参数 4 训练循环4.1 训练模式 (model.train())4.2 评估模式 (model.eval()) 5 模型验证 本文环境&#xff1a; Pycharm 2025.1Python 3.1…

K8S StatefulSet 快速开始

其实这篇文章的梗概已经写了很久了&#xff0c;中间我小孩出生了&#xff0c;从此人间多了一份牵挂。抽出一些时间去办理新生儿相关手续。初为人父确实艰辛&#xff0c;就像学技术一样&#xff0c;都需要有极大的耐心&#xff0c;付出很多的时间。 一、引子 1.1、独立的存储 …

nav2笔记-250603

合作背景&#xff1a; AMD与Open Navigation在过去几个月里进行了合作&#xff0c;旨在向ROS 2社区展示AMD强大的Ryzen AI、Embedded和Kria能力。 演示内容&#xff1a; 帖子提到&#xff0c;他们已经开始展示如何使用Ryzen AI为自主机器人产品提供动力&#xff0c;在各种现实世…