Java-redis实现限时在线秒杀功能
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 {@Autowiredprivate 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 {@Autowiredprivate ProductMapper productMapper;@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate SeckillRecordMapper seckillRecordMapper;@Autowiredprivate 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.秒杀结束,超时秒杀结束
相关文章:

Java-redis实现限时在线秒杀功能
1.使用redisson pom文件添加redisson <!--redisson--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.23.4</version></dependency> 2.mysql数据库表设…...

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

VMWare安装常见问题
如果之前安装过VMWare软件,只要是 15/16 版本的,可以正常使用的,不用卸载!!! 如果之前安装过,卸载了,一定要保证通过正常的渠道去卸载(通过控制面板卸载软件)…...
set_property LOC约束
##下列指令是用于清除自带GT CELL相关的LOC约束,或者覆盖 ##你需要把IP中自带的GT cell相关的LOC约束清除掉,或者覆盖掉 ##以下命令可以用来覆盖GT_CHANNEL的LOC约束, 在这条命令之后执行你自己的physical constraint: ##GT的channel的相关管脚有两种设计…...

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

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

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

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

用户管理页面(解决toggleRowSelection在dialog用不了的隐患,包含el-table的plus版本的组件)
新增/编辑/删除/分配角色,图片上传在此文章分类下另一个文章 1.重点分配角色: <template><!-- 客户资料 --><div class"pageBox"><elPlusTable :tableData"tableData" :tablePage"tablePage" onSi…...
打卡第35天:GPU训练以及类的Call方法
知识点回归: 1.CPU性能的查看:看架构代际、核心数、线程数 2.GPU性能的查看:看显存、看级别、看架构代际 3.GPU训练的方法:数据和模型移动到GPU device上 4.类的call方法:为什么定义前向传播时可以直接写作self.fc1(x)…...

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) 表的内外连接
标题:[MySQL初阶]MySQL(7)表的内外连接 水墨不写bug 文章目录 一. 内连接 (INNER JOIN)二. 外连接 (OUTER JOIN)关键区别总结 三、 如何选择 在 MySQL 中,连接(JOIN)用于根据两个或多个表之间的相关列组合行。内连接(I…...
Spring Boot中Excel处理完全指南:从基础到高级实践
Excel处理基础知识 1.1 为什么需要在应用中处理Excel文件? 在企业应用开发中,Excel文件处理是一个非常常见的需求,主要用于以下场景: 数据导入:允许用户通过Excel上传批量数据到系统 数据导出:将系统数据…...
Windows下NVM的安装与使用
本文将介绍windows下nvm相关知识。 在不同的项目中可能会使用不同版本的Node.js,例如A项目中需要node>18;B项目中需要node>20。这时候就需要使用NVM切换不同的node版本。进而可以在同一台设备上使用多个node版本。 一、NVM是什么? n…...
Ubuntu挂起和休眠
Ubuntu挂起和休眠 1. 挂起(Suspend)2. 休眠(Hibernate)3. 混合挂起(Hybrid-Sleep)注意事项图形界面操作 在 Ubuntu 系统中,挂起(Suspend)和休眠(Hibernate&am…...

【R语言编程绘图-mlbench】
mlbench库简介 mlbench是一个用于机器学习的R语言扩展包,主要用于提供经典的基准数据集和工具,常用于算法测试、教学演示或研究场景。该库包含多个知名数据集,涵盖分类、回归、聚类等任务。 包含的主要数据集 BostonHousing 波士顿房价数据…...
云服务器部署Gin+gorm 项目 demo
更多个人笔记见: (注意点击“继续”,而不是“发现新项目”) github个人笔记仓库 https://github.com/ZHLOVEYY/IT_note gitee 个人笔记仓库 https://gitee.com/harryhack/it_note 个人学习,学习过程中还会不断补充&…...
MySQL数据一致性守护者:pt-table-checksum原理与实战全解析
MySQL数据一致性守护者:pt-table-checksum原理与实战全解析 在MySQL主从复制环境中,数据一致性是DBA和运维人员最关心的问题之一。主从数据不一致可能导致业务逻辑错误、报表数据失真甚至系统故障。Percona Toolkit中的pt-table-checksum工具正是为解决这一痛点而生,它能够…...

检索器组件深入学习与使用技巧 BaseRetriever 检索器基类
1. BaseRetriever 检索器基类 在 LangChain 中,传递一段 query 并返回与这段文本相关联文档的组件被称为 检索器,并且 LangChain 为所有检索器设计了一个基类——BaseRetriever,该类继承了 RunnableSerializable,所以该类是一个 …...
Unity——QFramework工具 AciontKit时序动作执行系统
AciontKit 是一个时序动作执行系统。 游戏中,动画的播放、延时、资源的异步加载、网络请求等,这些全部都是时序任务,而 ActionKit,可以把这些任务全部整合在一起,使用统一的 API,来对他们的执行进行计划。…...

【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) 即将开启 在信息技术飞速发展的当下,信号处理与智能计算作为前沿科技领域,正深刻改变着我们的生活与产业格局。为汇聚全球顶尖智慧,推动该领域进一步突破,第三届信号处理与智能…...

Android12 Launcher3显示所有应用列表
Android12 Launcher3显示所有应用列表 1.前言: 最近在Android12Rom定制时需要显示所有桌面应用的图标,并且不能去掉抽屉,在手机上面抽屉和所有应该列表是两种不同模式,用户基可以自行选择,但是在自定义的launcher中这…...
24.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--认证微服务
SP.IdentityService 项目为微服务架构中的核心认证中心,采用 OpenIddict 框架实现 OAuth2.0 和 OpenID Connect 协议,提供完整的身份认证和授权解决方案。项目集成了 ASP.NET Core Identity 框架,实现了用户管理、角色权限控制等基础功能&…...
基于React Native开发鸿蒙新闻类应用的实战开发笔记
以下为基于React Native开发鸿蒙新闻资讯类应用的实战开发笔记,结合架构特性与踩坑经验,重点记录关键实现方案和技术决策: 一、环境搭建与工程初始化(关键步骤复盘) Node.js版本锁定 必须使用Node 18&…...
[Java 基础]运算符,将盒子套起来
在 Java 中,运算符(Operator)用于执行特定的操作,例如数学计算、赋值、比较等。运算符是 Java 语言的重要组成部分,能够帮助我们高效地操作数据。 1. 算术运算符 运算符说明示例结果加法5 38-减法5 - 32*乘法5 * 31…...

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

华为OD机试真题——模拟消息队列(2025B卷:100分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
2025 B卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《模拟消息队列》: 目录 题…...
c# 显示正在运行的线程数
在 C# 中,若想获取当前进程正在运行的线程数,可以使用 System.Diagnostics 命名空间中的 Process 类来实现。该方法适用于 Windows 平台,并能够获取当前进程的线程信息,包括线程总数和运行中的线程数量。 ✅ 方法一:使…...
MySQL 日志数据同步的详细教程
以下是 MySQL 日志数据同步的详细教程,主要介绍基于二进制日志(binlog)的主从复制和基于 GTID 的高级同步方案: 一、MySQL 二进制日志(binlog)同步基础 1. 二进制日志原理 binlog 是 MySQL 的事务性日志&am…...