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…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...

WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式
简介 在我的 QT/C 开发工作中,合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式:工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...

图解JavaScript原型:原型链及其分析 | JavaScript图解
忽略该图的细节(如内存地址值没有用二进制) 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么:保存在堆中一块区域,同时在栈中有一块区域保存其在堆中的地址(也就是我们通常说的该变量指向谁&…...