当前位置: 首页 > news >正文

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.秒杀结束,超时秒杀结束

http://www.lqws.cn/news/92413.html

相关文章:

  • Servlet 快速入门
  • 1130 - Host ‘xxx.x.xx.xxx‘is not allowed to connect to this MySQL server
  • 70道Hive高频题整理(附答案背诵版)
  • 如何合理设计缓存 Key的命名规范,以避免在共享 Redis 或跨服务场景下的冲突?
  • Java并发编程:读写锁与普通互斥锁的深度对比
  • 【ROS2】各种相关概念汇总解释
  • 动态规划-1143.最长公共子序列-力扣(LeetCode)
  • 机器学习——随机森林算法
  • 【如何在IntelliJ IDEA中新建Spring Boot项目(基于JDK 21 + Maven)】
  • Linux Maven Install
  • 【论文笔记】High-Resolution Representations for Labeling Pixels and Regions
  • 3.2 HarmonyOS NEXT跨设备任务调度与协同实战:算力分配、音视频协同与智能家居联动
  • 机器学习——SVM
  • Foundation Models for Generalist Geospatial Artificial Intelligence论文阅读
  • 微软Build 2025:Copilot Studio升级,解锁多智能体协作未来
  • 论文阅读:CLIP:Learning Transferable Visual Models From Natural Language Supervision
  • 谷歌地图手机版(Google maps)v11.152.0100安卓版 - 前端工具导航
  • 力扣刷题 -- 225. 用队列实现栈
  • Spring 中创建 Bean 有几种方式?
  • 深入理解Android进程间通信机制
  • 秋招Day12 - 计算机网络 - IP
  • 蓝桥杯 k倍区间
  • docker创建postgreSql带多个init的sql
  • openharmony5.0.0中kernel子系统编译构建流程概览(rk3568)
  • Dockerfile 使用多阶段构建(build 阶段 → release 阶段)前端配置
  • 5.Nginx+Tomcat负载均衡群集
  • PyTorch——非线性激活(5)
  • Docker 插件生态:从网络插件到存储插件的扩展能力解析
  • SQL Indexes(索引)
  • 安全大模型的思考