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作为分布式锁,加锁后扣减库存
实现流程为:
- 用户发起秒杀请求到
Redis,Redis先使用商品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作为分布式锁,加锁后扣减库存
实现流程为:
- 用户发起秒杀请求到
Redis,Redis先查询库存量,然后根据商品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(分布式版本),是一个分布式缓存系统。其支持多种数据结构,也支持MQ。Redis在性能上做了大量优化。因此使用Redis或者Redis Cluster就可以轻松实现一个强大的秒杀系统。 用Redis的这…...
4 scala集合-Map
和 Java 一样,Scala 也有表示键值对(Key-Value)集合的 Map 数据结构。同样,Map 也分不可变和可变,不可变需要使用类 scala.collection.mutable.Map。 1 不可变 Map 可以使用以下语法定义不可变 Map 对象 val/var ma…...
QT 对象树模型
QObject是Qt里边绝大部分类的根类 QObject对象之间是以对象树的形式组织起来的。 当两个QObject(或子类)的对象建立了父子关系的时候。子对象就会加入到父对象的一个成员变量叫children(孩子)的list(列表)…...
ubuntu快速安装miniconda
ubuntu快速安装miniconda 环境 ubuntu.22.04 显卡 RTX 3050 关于选择Miniconda还是Anaconda的问题,Anaconda安装包比较大,耗时比较长,如果你是绝对的初学者,选择Anaconda会比较稳妥一些;否则建议你还是选择Miniconda安…...
阿里云游戏服务器多少钱一年?
阿里云游戏服务器租用价格表:4核16G服务器26元1个月、146元半年,游戏专业服务器8核32G配置90元一个月、271元3个月,阿里云服务器网aliyunfuwuqi.com分享阿里云游戏专用服务器详细配置和精准报价: 阿里云游戏服务器租用价格表 阿…...
小游戏和GUI编程(7) | SimpleNN 界面源码解析
小游戏和GUI编程(7) | SimpleNN 界面源码解析 0. 简介 SimpleNN 是 AdamYuan 在高中一年级时用 1 天时间写出来的简易 CNN, 使用 SFML 做 UI, 用于交互式输入手写数字,这个数字被训练好的 CNN 网络执行推理得到识别结果, 它的运行效果如下: 这一篇我们…...
c++设计模式之代理模式
作用 代理模式主要用于,通过代理类,来控制实际对象的访问权限 案例 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;中间的百分比数字逐渐消失,即透明度 opacity 从 1 到 0;背景图片从模糊变为清晰,滤镜 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之间的区别
前言 大家好,我是chowley,在我之前的面试中,遇到过这样一个问题:Exception和Error之间有什么区别?今天我就来好好地总结一下! 主体 在Java编程中,Exception和Error都是Java中的可抛出对象&am…...
【RabbitMQ(一)】:基本介绍 | 配置安装与快速入门
应该是新年前最后一篇博客了,明天浅浅休息一下,提前祝大家新年快乐捏!😊😊😊 01. 基础理解 1.1 同步调用和异步调用 👉 同步调用 的时候调用者会 阻塞 等待被调用函数或方法执行完成ÿ…...
ElasticSearch之search API
写在前面 本文看下查询相关内容,这也是我们在实际工作中接触的最多的,所以有必要好好学习下! 1:查询的分类 主要分为如下2类: 1:基于get查询参数的URI search 2:基于post body的request body search&am…...
07-Java桥接模式 ( Bridge Pattern )
Java桥接模式 摘要实现范例 桥接模式(Bridge Pattern)是用于把抽象化与实现化解耦,使得二者可以独立变化 桥接模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类,这两种类型的类可被结构化改变而互不影…...
golang集成sentry: go-redis
网上没有找到go-redis集成sentry的库, 所以我简单实现了一个 代码: https://github.com/Shujie-Tan/go-redis-sentry 使用方法: import (redis_sentry "github.com/Shujie-Tan/go-redis-sentry" ) rdb : redis.NewClient(&re…...
用EXCEL从地址(上海)中提取各区(浦东新区等区)信息
背景: 朋友工作需要经常用EXCEL把各上海用户收货地址中的区提取出来,之前一直手动处理,希望我帮忙用EXCEL公式直接提取处理。 数据样式: 中国上海市浦东新区A小区 上海徐汇区B小区 中国,上海,浦东新区&a…...
关于在分布式环境中RVN和使用场景的介绍3
简介 在《关于在分布式环境中RVN和使用场景的介绍2》和《关于在分布式环境中RVN和使用场景的介绍1》中我们介绍了RVN的概念和在一些具体用例中的使用。在本文中我们讨论一下在分布式环境中使用RVN需要注意的问题。 问题 我们在收到一条待处理的事件时,需要检查该…...
计算最小公倍数math.lcm()
【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 计算最小公倍数 math.lcm() 请问以下代码输出的结果是? import math print("【执行】math.lcm(2, 4)") print(math.lcm(2, 4)) print("【执行】math.lcm(1, 2, 3…...
VUE SEO 几种方案经典面试题
1、SSR服务器渲染 Vue.js 是构建客户端应用程序的框架。默认情况下,可以再浏览器中输出Vue组件,进行生成DOM和操作DOM。然而,也可以将同一个组件渲染未服务器端的HTML字符串,将它们直接发送到浏览器,最后将这些静态标…...
Python和VBA批量提取Word中的表格
表格在word文档中常见的文档元素之一。操作word文件时有时需要提取文件中多个表格的内容到一个新的文件,甚至有时还会要提取题注信息。 今天,给大家分享两种批量提取文档中表格的两种方法,分别是VBA法和Python法两种。 一、VBA法提取word中…...
Swift Combine 有序的异步操作 从入门到精通十二
Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二Swift Combine 管道 从入门到精通三Swift Combine 发布者publisher的生命周期 从入门到精通四Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五Swift Com…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...
