秒杀场景的缓存、队列、锁使用Redis优化设计方案
发布时间:2023-05-29 19:07:18
一、为什么难
秒杀系统难做的原因:库存只有一份,所有人会在集中的时间读和写这些数据。例如小米手机每周二的秒杀,可能手机只有1万部,但瞬时进入的流量可能是几百几千万。又例如12306抢票,亦与秒杀类似,瞬时流量更甚。这篇文章主要介绍了秒杀场景的缓存、队列、锁使用Redis优化设计方案,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
主要需要解决的问题有两个:
高并发对数据库产生的压力
竞争状态下如何解决库存的正确减少(
超卖
问题)
对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。重点在于第二个问题,常规写法:
查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数
二、常见架构
流量到了亿级别,常见站点架构如上:
浏览器端,最上层,会执行到一些JS代码
站点层,这一层会访问后端数据,拼html页面返回给浏览器
服务层,向上游屏蔽底层数据细节
数据层,最终的库存是存在这里的,mysql是一个典型
三、优化方向
1)将请求尽量拦截在系统上游:传统秒杀系统之所以挂,请求都压倒了后端数据层,数据读写锁冲突严重,并发高响应慢,几乎所有请求都超时,流量虽大,下单成功的有效流量甚小【一趟火车其实只有2000张票,200w个人来买,基本没有人能买成功,请求有效率为0】
2)充分利用缓存:这是一个典型的读多写少
的应用场景【一趟火车其实只有2000张票,200w个人来买,最多2000个人下单成功,其他人都是查询库存,写比例只有0.1%,读比例占99.9%】,非常适合使用缓存。
四、优化细节
4.1)浏览器层请求拦截
点击了“查询”按钮之后,系统那个卡呀,进度条涨的慢呀,作为用户,我会不自觉的再去点击“查询”,继续点,继续点,点点点。。。有用么?平白无故的增加了系统负载(一个用户点5次,80%的请求是这么多出来的),怎么整?
a 产品层面,用户点击“查询”或者“购票”后,按钮置灰,禁止用户重复提交请求
b JS层面,限制用户在x秒之内只能提交一次请求
如此限流,80%流量已拦。
4.2)站点层请求拦截与页面缓存
浏览器层的请求拦截,只能拦住小白用户(不过这是99%的用户哟),高端的程序员根本不吃这一套,写个for循环,直接调用你后端的http请求,怎么整?
a 同一个uid,限制访问频度,做页面缓存,x秒内到达站点层的请求,均返回同一页面
b 同一个item的查询,例如手机车次,做页面缓存,x秒内到达站点层的请求,均返回同一页面
如此限流,又有99%的流量会被拦截在站点层
4.3)服务层请求拦截与数据缓存站点层的请求拦截,只能拦住普通程序员,高级黑客,假设他控制了10w台肉鸡(并且假设买票不需要实名认证),这下uid的限制不行了吧?怎么整?
a 大哥,我是服务层,我清楚的知道小米只有1万部手机,我清楚的知道一列火车只有2000张车票,我透10w个请求去数据库有什么意义呢?对于写请求,做请求队列
,每次只透有限的写请求去数据层,如果均成功再放下一批,如果库存不够则队列里的写请求全部返回“已售完”
b 对于读请求
,还要我说么?cache抗,不管是memcached还是redis,单机抗个每秒10w应该都是没什么问题的
如此限流,只有非常少的写请求,和非常少的读缓存mis的请求会透到数据层去,又有99.9%的请求被拦住了
4.4)数据层到了数据这一层,几乎就没有什么请求了,单机也能扛得住,还是那句话,库存是有限的,小米的产能有限,透这么多请求来数据库没有意义。
4.5)mysql批量入库提高INSERT效率
五、Redis
使用redis
队列(list
),push
和pop
操作保证了原子性
的实现。即使有很多用户同时到达,也是依次执行。(mysql事务在高并发下性能下降很厉害)
先将商品库存存入队列:
<?php
$store=1000; //商品库存
$redis=new Redis();
$result=$redis->connect('127.0.0.1',6379);
$res=$redis->llen('goods_store');
for($i=0; $i<$store; $i++){
$redis->lpush('goods_store',1);
}
echo $redis->llen('goods_store');
?>
客户执行下单操作:
$redis=new Redis();
$result=$redis->connect('127.0.0.1',6379);
$count = $redis->lpop('goods_store');
if(!$count){
echo '抢购失败!';
return;
}
缓存也是可以应对写请求的,比如我们就可以把数据库中的库存数据转移到Redis缓存中,所有减库存操作都在Redis中进行,然后再通过后台进程把Redis中的用户秒杀请求同步到数据库中
六、总结
没什么总结了,上文应该描述的非常清楚了,对于秒杀系统,再次重复下两个架构优化思路:1)尽量将请求拦截在系统上游
2)读多写少经量多使用缓存
3) redis队列缓存 + mysql 批量入库


猜你喜欢
- 本文实例为大家分享了python类支持比较运算的具体代码,供大家参考,具体内容如下案例:有时我们希望自定义的类,实例间可以使用比较运算符进行
- 下面先给大家介绍下Python 3 判断2个字典相同的方法,Python自带的数据结构dict非常好用,之前不知道怎么比较2个字典是否相同,
- 原作者:Jonathan 翻译:charlee原文:http://f6design.com/journal/2006/10/21/the-v
- 几天前,想把上个月校园招聘的餐旅费报销一下。结果在公司内网的报销系统折腾了三个半小时才搞定。看看自己报销的金额:802块。觉得挺无奈,花了三
- 参考官网地址:Windows端:https://tensorflow.google.cn/install/source_windowsCPU
- 实现方法分位三步:在template中设置2个按钮,通过v-if ,v-show来控制;data中设置按钮的默认值;methods中控制点击
- 装饰器实现Python 函数重载函数重载指的是有多个同名的函数,但是它们的签名或实现却不同。当调用一个重载函数 fn 时,程序会检验传递给函
- 网上找了挺久,感觉方法都不合适我这新手,想了个歪点子from tkinter import *from tkinter import mes
- 先给大家介绍下Python读取文件夹按数字排序的代码,内容如下所示:python中 os.listdir()方法用于返回指定的文件夹包含的文
- 1. RS.OPEN SQL,CONN,A,B,C2. CONN.EXECUTE(SQL,RowsAffected,C)参数含义:SQL的值
- 本文实例为大家分享了python3实现猜数字游戏的具体代码,供大家参考,具体内容如下需求目标:需求:猜数字游戏1: 开始游戏产生一个1~10
- 前言今天在编码中,看到了一个非常经典的接口用法如下,于是查阅了相关资料,发现此种写法为接口型函数,本文对此做了细致的阐述。// A Gett
- 有个需求需要把markdown转成html模块,查询了一下刚好有这个模块安装 pip install amrkdown安装完成直接转换并保存
- MySql explain语句的返回结果中,filtered字段要怎么理解?MySql5.7官方文档中描述如下:The filte
- 本文实例讲述了Python使用ConfigParser模块操作配置文件的方法。分享给大家供大家参考,具体如下:一、简介用于生成和修改常见配置
- 前言Sphinx是一款支持多种编程语言的文档生成工具,在python项目开发过程中,可以帮助开发者根据需求生成相应的说明文档,拿今天我们就基
- 一、通过结构(struct) 实现 接口(interface)1、在了解iris框架的时候,经常看到有这样去写的使用一个空结构体作为 * ,
- 示例函数为了开发类型检查器,我们需要一个简单的函数对其进行实验。欧几里得算法就是一个完美的例子: def gcd(a, b):
- 源代码: 传送门Vue会对我们在data中传入的数据进行拦截:对象:递归的为对象的每个属性都设置get/set方法数组:修改数组的原型方法,
- 我就废话不多说了,直接上代码吧!pip install pymysqlimport pymysqlimport pandas as pdco