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

Redis实现秒杀

前期准备

缓存选择考虑

Redis和Redis Cluster(分布式版本),是一个分布式缓存系统。其支持多种数据结构,也支持MQ。Redis在性能上做了大量优化。因此使用Redis或者Redis Cluster就可以轻松实现一个强大的秒杀系统。

用Redis的这些命令就可以了。

RPUSH key value

插入秒杀请求

当插入的秒杀请求数达到上限时,停止所有后续插入。

后台启动多个工作线程,使用

LPOP key

读取秒杀成功者的用户id,进行后续处理。

或者使用LRANGE key start end命令读取秒杀成功者的用户id,进行后续处理。

每完成一条秒杀记录的处理,就执行INCR key_num。一旦所有库存处理完毕,就结束该商品的本次秒杀,关闭工作线程,也不再接收秒杀请求。

秒杀思路(缓存redis,定时器:spring整合quartz)

1、秒杀商品由商家后台添加,秒杀商品数据保存在tb_seckilll_goods表中,关键字段包括:

id,status(审核状态),start_time(开始时间),end_time(结束时间),stock_count(库存量);

2、写一个定时器,定时从秒杀商品表中扫描数据,将符合条件的商品加载到缓存(redis)中;条件:审核状态="1",start_time < 当前时间 < end_time,库存量大于0;

3、前端展示,此处略

4、点击抢购,拿着秒杀商品的id去缓存中查询,如果缓存中商品不存在或者为空,提示“已售罄”,否则生成订单(原子操作),保存到缓存中,防止用户重复秒杀,订单表tb_seckill_order;

5、库存-1,判断减完之后缓存中商品的库存是否大于0,大于0则更新缓存,否则删除该秒杀商品的缓存,并更新到数据库

6、异步处理,所有秒杀订单和库存在内存(redis),后边异步同步到mysql

方案一:使用商品ID作为分布式锁,加锁后扣减库存

实现流程为:

  • 用户发起秒杀请求到RedisRedis先使用商品ID作为key尝试加锁,保证只有一个用户进入之后流程,保证原子性
  • 如果加锁成功,则查询库存。如果库存充足,则扣减库存,记录订单,代表秒杀成功;若库存不足,直接返回秒杀失败;
/*** 使用分布式锁秒杀,加锁后再查询redis库存,最后扣减库存* @param lockId 锁ID* @param userId 用户ID* @param goodKey 商品ID* @return 秒杀成功返回 true,否则返回 false*/
private boolean subStock(String lockId, String userId, String goodKey) {// 尝试先加锁,如果加锁成功再进行查询库存量,和扣减库存操作,此时只能有一个线程进入代码块if (redisLock.lock(lockId, userId, 4000)) {try {// 查询库存Integer stock = (Integer) redisTemplate.opsForValue().get(goodKey);if (stock == null) {System.out.println("商品不在缓存中");}// 如果剩余库存量大于零,则扣减库存if (stock > 0) {redisTemplate.opsForValue().decrement(goodKey);return true;} else {return false;}} finally {// 释放锁redisLock.unlock(lockId, userId);}}return false;
}

该方案存在一些缺点:

用户进来后都要抢锁,即便是库存量已经为零,仍然需要抢锁,这无疑带来了很多无用争抢;
锁的是商品ID,锁粒度太大,并发性能可以进一步优化;
解决方案:

抢锁前先查询库存,如果库存已经为零,则直接返回false,不必参与抢锁过程;
使用商品ID+库存量作为锁,降低锁粒度,进一步提升并发性能;

方案二:先查询,再使用商品ID作为分布式锁,加锁后扣减库存

实现流程为:

  • 用户发起秒杀请求到RedisRedis先查询库存量,然后根据商品ID+库存量作为key尝试加锁,保证只有一个用户进入之后流程,保证原子性
  • 如果加锁成功,则查询库存(解决超卖问题,第一步骤并发查有库存,但是另外一个thread已经先行扣除成功,需要原子操作再加一步查询)。如果库存充足,则扣减库存,代表秒杀成功;若库存不足,直接返回秒杀失败;
//查询库存是否>0Integer curStock = (Integer) redisTemplate.opsForValue().get(goodKey);if (curStock <= 0) {return false;}
//尝试加锁实现秒杀下单过程if (redisLock.lock(lockId, userId, 4000)) {try {// 查询库存Integer stock = (Integer) redisTemplate.opsForValue().get(goodKey);if (stock == null) {System.out.println("商品不在缓存中");}// 如果剩余库存量大于零,则扣减库存if (stock > 0) {redisTemplate.opsForValue().decrement(goodKey);return true;} else {return false;}} finally {// 释放锁redisLock.unlock(lockId, userId);}}return false;

Java+Redis系统实现

步骤参考:Redis实现商品秒杀-CSDN博客

1、将数据库中的商品库存数量存入Redis中。

// 商品列表名称
String redisKey = "goods:" + seckillGoods.getId();
// 添加所有库存商品
for (int i = 0; i < seckillGoods.getGoodsCount(); i++) {redisTemplate.opsForList().rightPush(redisKey, String.valueOf(seckillGoods.getId()));
}

2、判断用户是否已经秒杀成功过。

// 查询用户是否已经秒杀过该商品
Object orderObj = redisTemplate.opsForHash().get("seckill_orders", seckillUser.getId() + ":" + seckillGoods.getId());
if (orderObj != null) {throw new SEckillException(ErrorCodeEnum.REPEAT_SEC_KILL_ERROR);
}
// 查询用户是否在排队中
Object userInQueueObj = redisTemplate.opsForSet().isMember("seckill_queues:" + seckillGoods.getId(), seckillUser.getId());
if (userInQueueObj != null) {throw new SEckillException(ErrorCodeEnum.WAITING_IN_QUEUE_ERROR);
}

3、利用Redis的事务实现处理抢购成功的逻辑。

// 开启事务
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.multi();
// 从商品列表中弹出一个商品
redisTemplate.opsForList().leftPop(redisKey);
// 利用setValueAt等方法,获取用户信息和商品信息,此处略过
// 判断是否获取到商品信息
if (seckillGoods == null) {redisTemplate.discard();throw new SEckillException(ErrorCodeEnum.SEC_KILL_FINISH_ERROR);
}
// 秒杀成功,生成秒杀订单
redisTemplate.opsForHash().put("seckill_orders", seckillUser.getId() + ":" + seckillGoods.getId(), seckillOrder);
// 秒杀成功的商品写入Set中
redisTemplate.opsForSet().add("seckill_success:" + seckillGoods.getId(), String.valueOf(seckillGoods.getId()));
// 提交事务
redisTemplate.exec();

相关文章:

Redis实现秒杀

前期准备 缓存选择考虑 Redis和Redis Cluster&#xff08;分布式版本&#xff09;&#xff0c;是一个分布式缓存系统。其支持多种数据结构&#xff0c;也支持MQ。Redis在性能上做了大量优化。因此使用Redis或者Redis Cluster就可以轻松实现一个强大的秒杀系统。 用Redis的这…...

4 scala集合-Map

和 Java 一样&#xff0c;Scala 也有表示键值对&#xff08;Key-Value&#xff09;集合的 Map 数据结构。同样&#xff0c;Map 也分不可变和可变&#xff0c;不可变需要使用类 scala.collection.mutable.Map。 1 不可变 Map 可以使用以下语法定义不可变 Map 对象 val/var ma…...

QT 对象树模型

QObject是Qt里边绝大部分类的根类 QObject对象之间是以对象树的形式组织起来的。 当两个QObject&#xff08;或子类&#xff09;的对象建立了父子关系的时候。子对象就会加入到父对象的一个成员变量叫children&#xff08;孩子&#xff09;的list&#xff08;列表&#xff09;…...

ubuntu快速安装miniconda

ubuntu快速安装miniconda 环境 ubuntu.22.04 显卡 RTX 3050 关于选择Miniconda还是Anaconda的问题&#xff0c;Anaconda安装包比较大&#xff0c;耗时比较长&#xff0c;如果你是绝对的初学者&#xff0c;选择Anaconda会比较稳妥一些&#xff1b;否则建议你还是选择Miniconda安…...

阿里云游戏服务器多少钱一年?

阿里云游戏服务器租用价格表&#xff1a;4核16G服务器26元1个月、146元半年&#xff0c;游戏专业服务器8核32G配置90元一个月、271元3个月&#xff0c;阿里云服务器网aliyunfuwuqi.com分享阿里云游戏专用服务器详细配置和精准报价&#xff1a; 阿里云游戏服务器租用价格表 阿…...

小游戏和GUI编程(7) | SimpleNN 界面源码解析

小游戏和GUI编程(7) | SimpleNN 界面源码解析 0. 简介 SimpleNN 是 AdamYuan 在高中一年级时用 1 天时间写出来的简易 CNN, 使用 SFML 做 UI, 用于交互式输入手写数字&#xff0c;这个数字被训练好的 CNN 网络执行推理得到识别结果, 它的运行效果如下&#xff1a; 这一篇我们…...

c++设计模式之代理模式

作用 代理模式主要用于&#xff0c;通过代理类&#xff0c;来控制实际对象的访问权限 案例 class VideoSite { public:virtual void freeVideo()0;virtual void vipVideo()0;virtual void trickVideo()0; };class FixBugVideoSite:public VideoSite { public:void freeVideo()…...

第5个-模糊加载

Day 5 - Blurry Loading 1. 项目展示 2. 分析思路 变化过程 数字从 0 不断增长到 100&#xff1b;中间的百分比数字逐渐消失&#xff0c;即透明度 opacity 从 1 到 0&#xff1b;背景图片从模糊变为清晰&#xff0c;滤镜 filter.blur()的参数设置为从 30px 到 0px。 小 tips…...

rtt设备io框架面向对象学习-adc设备

目录 1.adc设备基类2.adc设备基类的子类3.初始化/构造流程3.1设备驱动层3.2 设备驱动框架层3.3 设备io管理层 4.总结5.使用 1.adc设备基类 此层处于设备驱动框架层。也是抽象类。 在/ components / drivers / include / drivers 下的adc.h定义了如下adc设备基类 struct rt_ad…...

面试官:介绍一下Exception和Error之间的区别

前言 大家好&#xff0c;我是chowley&#xff0c;在我之前的面试中&#xff0c;遇到过这样一个问题&#xff1a;Exception和Error之间有什么区别&#xff1f;今天我就来好好地总结一下&#xff01; 主体 在Java编程中&#xff0c;Exception和Error都是Java中的可抛出对象&am…...

【RabbitMQ(一)】:基本介绍 | 配置安装与快速入门

应该是新年前最后一篇博客了&#xff0c;明天浅浅休息一下&#xff0c;提前祝大家新年快乐捏&#xff01;&#x1f60a;&#x1f60a;&#x1f60a; 01. 基础理解 1.1 同步调用和异步调用 &#x1f449; 同步调用 的时候调用者会 阻塞 等待被调用函数或方法执行完成&#xff…...

ElasticSearch之search API

写在前面 本文看下查询相关内容&#xff0c;这也是我们在实际工作中接触的最多的&#xff0c;所以有必要好好学习下&#xff01; 1&#xff1a;查询的分类 主要分为如下2类&#xff1a; 1:基于get查询参数的URI search 2&#xff1a;基于post body的request body search&am…...

07-Java桥接模式 ( Bridge Pattern )

Java桥接模式 摘要实现范例 桥接模式&#xff08;Bridge Pattern&#xff09;是用于把抽象化与实现化解耦&#xff0c;使得二者可以独立变化 桥接模式涉及到一个作为桥接的接口&#xff0c;使得实体类的功能独立于接口实现类&#xff0c;这两种类型的类可被结构化改变而互不影…...

golang集成sentry: go-redis

网上没有找到go-redis集成sentry的库&#xff0c; 所以我简单实现了一个 代码&#xff1a; https://github.com/Shujie-Tan/go-redis-sentry 使用方法&#xff1a; import (redis_sentry "github.com/Shujie-Tan/go-redis-sentry" ) rdb : redis.NewClient(&re…...

用EXCEL从地址(上海)中提取各区(浦东新区等区)信息

背景&#xff1a; 朋友工作需要经常用EXCEL把各上海用户收货地址中的区提取出来&#xff0c;之前一直手动处理&#xff0c;希望我帮忙用EXCEL公式直接提取处理。 数据样式&#xff1a; 中国上海市浦东新区A小区 上海徐汇区B小区 中国&#xff0c;上海&#xff0c;浦东新区&a…...

关于在分布式环境中RVN和使用场景的介绍3

简介 在《关于在分布式环境中RVN和使用场景的介绍2》和《关于在分布式环境中RVN和使用场景的介绍1》中我们介绍了RVN的概念和在一些具体用例中的使用。在本文中我们讨论一下在分布式环境中使用RVN需要注意的问题。 问题 我们在收到一条待处理的事件时&#xff0c;需要检查该…...

计算最小公倍数math.lcm()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 计算最小公倍数 math.lcm() 请问以下代码输出的结果是&#xff1f; import math print("【执行】math.lcm(2, 4)") print(math.lcm(2, 4)) print("【执行】math.lcm(1, 2, 3…...

VUE SEO 几种方案经典面试题

1、SSR服务器渲染 Vue.js 是构建客户端应用程序的框架。默认情况下&#xff0c;可以再浏览器中输出Vue组件&#xff0c;进行生成DOM和操作DOM。然而&#xff0c;也可以将同一个组件渲染未服务器端的HTML字符串&#xff0c;将它们直接发送到浏览器&#xff0c;最后将这些静态标…...

Python和VBA批量提取Word中的表格

表格在word文档中常见的文档元素之一。操作word文件时有时需要提取文件中多个表格的内容到一个新的文件&#xff0c;甚至有时还会要提取题注信息。 今天&#xff0c;给大家分享两种批量提取文档中表格的两种方法&#xff0c;分别是VBA法和Python法两种。 一、VBA法提取word中…...

Swift Combine 有序的异步操作 从入门到精通十二

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二Swift Combine 管道 从入门到精通三Swift Combine 发布者publisher的生命周期 从入门到精通四Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五Swift Com…...

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

ip子接口配置及删除

配置永久生效的子接口&#xff0c;2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...