Redis系列六 - 浅谈如何设计秒杀系统
前言
设计一个系统之前,我们肯定要先确认系统业务场景是怎样的,下面就以某电商平台上的秒杀活动为场景,一起来探讨一个秒杀系统改如何去设计。
场景
我们现在要卖100件纸尿布,按照系统的用户量及以往经验来看,目测这样的秒杀活动会有10万人参加来抢这100件纸尿布。
10万人,这服务器哪里顶得住啊!如果打在DB上那肯定得挂啊。但是我们做点什么嘛。下面好好分析一下,秒杀系统可能会出现什么问题。
常见问题
高并发:
高并发这一点是毋庸置疑的,秒杀开始的一瞬间这么多用户访问系统,肯定是会出现高并发问题的。
是的,秒杀的特点就是这样时间极短、瞬间用户量大。
正常的店铺营销都是用极低的价格,再配合短信、APP等平台的精准推送,吸引了大量的用户来参加这场秒杀活动,这么多的用户量,商家肯定爽上天了,只是我们就苦逼了。。。
我们都知道,秒杀活动宣传到位、价格诱人的话,几十万的流量完全不是问题,而单机的Redis 一般来说3-4万的QPS(每秒请求数)还是能顶得住的,但是再高了就没办法了,那这个3-4万的数据随便搞个热销商品的秒杀都不止这个数了。
大量的请求涌入,我们需要需要考虑的问题就比较多了,缓存雪崩、缓存击穿、缓存穿透这些都有可能发生,出现了这些问题直接打在DB上,DB肯定扛不住这么大的流量,那这个秒杀活动也算是凉了。。。
库存超卖:
但凡是秒杀,都怕库存超卖,我们这里只是卖尿不湿,顶多也就不赚钱而已,要是换成100台华为Mate30Pro,商家预算卖100台可以赚点零花钱,还能造势,结果写错程序多卖出去了200台,不发货用户会投诉你、平台也会关你店,你发货就裤衩都赔进去了,这可不是杀两个程序猿祭天就能解决的事。
秒杀本来就是以低价吸引顾客来参与的,基本上都是不赚钱的,超卖就很恐怖了,所以库存超卖也是很关键的一个点。
恶意请求:
秒杀这么低的价格,如果我抢到了,我转手一卖大保健的钱又有了。。。就算我不卖那我也不亏,用户知道,我们能知道,某些有心人(黑客、黄牛。。)肯定也知道的。
这就简单了,我知道你什么时候活动开始(公开的),我搞几台服务器,再搞几个脚本,我也模拟出来十几万个人左右的请求,这是不是意味着80%的单子都来我这里了?
真是情况可能远远不止,因为机器请求的速度比人的手速往往快很多很多,每年过年抢票我们我们抢不过黄牛也是有一定道理的(好想宰一头黄牛祭天!!!)。。。
链接暴露:
前面几个问题我们都比较好理解,但是这个链接暴露估计就有不少的兄die会比较迷惑了。
相信我们对这个画面并不陌生,稍微懂点的都知道打开开发者模式,然后看看网页的代码,有的网页就写有url。对于一些查看不到源码的,我们可以点击一下查看对应的请求地址啊,不过好像这个可以对按钮在秒杀前置灰。
不管怎么样子都是有危险的,姑且算你外面的所有东西你都能挡住了,如果你卖的这个东西实在是便宜,极具诱惑力,你能保证开发不动心?开发知道地址,在秒杀的时候自己提前请求。。。(开发:为什么TM又是我???)
数据库:
每秒上万甚至十几万的QPS(每秒请求数)直接打在数据库上,基本上都能把数据库打挂,而且你服务器不单单是做秒杀的,还涉及到其他的业务,你没有做降级、限流、熔断等处理,直接挂一片,小公司的话可能是全站崩溃404。
反正不管你秒杀怎么挂,别把我其他的业务整挂就行了。要真挂一片了,这就不是随便杀一两个程序猿就能糊弄过去的事了。
程序猿:我。。。我TM挖谁祖坟了吗???
问题是讲出来了,但你别把问题丢给大佬呀,大佬要的是设计,你得给大佬讲清楚怎么设计,怎么解决这些问题呀。
设计一个能扛住高并发的系统,我觉得还是得单一职责。
什么意思呢?我们都知道现在设计都是微服务的设计思想,然后再用分布式的部署方式。
也就是我们下单是有个订单服务,用户登录管理有用户服务等。既然这样,我们为何不能给秒杀也开一个服务呢?
我们把秒杀的代码业务逻辑放一起。单独给他建立一个数据库,现在的互联网架构部署都是分库的,一样的就是订单服务对应订单库,秒杀我们也给他建立自己的秒杀库。
至于表怎么设计,就看大家的设计习惯了,该设置索引的地方还是要设置索引的,建完后记得用 explain 看看 SQL 的执行计划。
单一职责的好处就是就算秒杀没抗住,秒杀库崩了,服务器挂了,也不会影响到其他的服务。(勉强算是高可用吧)
秒杀链接加盐:
我们上面提到了链接要是提前暴露出去就有可能被人直接访问 url 就提前秒杀了。这位兄die就有话说了:我做个时间的校验就好了。那我告诉你,知道链接地址比起页面人工点击的还是有很大优势的。
我知道 url 了,那我可以通过程序不断地获取北京的最新时间,可以达到毫秒级别的,我就在00毫秒的时候请求,我敢说绝对比你人工点的成功率高很多,而且我可以一毫秒发送N次请求,一个不小心100个产品都是我的了。
这种情况要怎么避免呢?
这就比较简单了,把 url 动态化,就连写代码的人都不知道,你就通过MD5之类的加密算法,随机加密的字符串去做 url,然后通过前端代码获取 url 后台校验才能通过。
下面放一个简单的 url 加密给各位参考一下。
/**
* url md5加密
* @param url
* @return url
*/
public static String getURLsign(String url) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update((url).getBytes("UTF-8"));
byte[] b = md5.digest(); int i ;
StringBuffer buf = new StringBuffer();
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0) {
i += 256;
}
if (i < 16) {
buf.append("0");
}
buf.append(Integer.toHexString(i));
}
url = buf.toString();
System.out.println("result = " + url); } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
logger.err("error:" ,e);
}
return url;
}
Redis集群:
之前不是说了单机的Redis顶不住嘛,那简单啊,一个不行就找两个,两个不行就整三个嘛,多整几个兄弟一起扛(春哥:肆胸弟就来砍我。。。)。
秒杀本来就是读多写少,这就需要用到我们提到过的 Redis集群、主从同步、读写分离了,我们还搞点哨兵,开启持久化直接无敌高可用了!
Nginx:
Nginx 相比大家都不陌生了,这东西是高性能的web服务器,并发也随便顶个几万不是梦,但是我们 Tomcat 只能订几百的并发呀。
那简单呀,负载均衡嘛,一台服务几百,那就多搞几台就好了嘛(这是几台能解决的事?),在秒杀的时候多租点流量机。
Tip:据网传,我国内某大厂就是在去年春节活动期间,租光了亚洲所有的服务器,小公司也很喜欢在双十一期间买流量来顶住压力。
这样一对比,是不是觉得你的集群能顶半边天了。
恶意请求拦截也需要用到它,一般单个用户请求次数太夸张,不像人为的请求在网关那一层就得拦截掉,不然请求多了他抢不抢得到是一回事,服务器压力上去了,可能占用网络宽带或者把服务器打崩、缓存击穿等。
资源静态化:
秒杀一般都是特定的商品和特定的页面模板,现在一般都是前后端分离的,所以页面一般都是不会经过后端的,但是前端也要自己的服务器啊,那就把能提前放入 cdn服务器 的东西都放进去,反正把所有能提升效率的步骤都做一下,减少真正秒杀时候服务器的压力。
按钮控制:
大家有没有发现没到秒杀前,一般按钮都是置灰的,只有时间到了,才能点击。
这是因为怕用户在时间快到的最后几秒疯狂点击、请求服务器,然后一个不小心,秒杀还没开始基本上服务器就挂了。
这个时候就需要前端的配合,定时去请求你的后端服务器,获取最新北京时间,到时间点再给按钮置成可用状态。
按钮可以点击之后也得给他置灰几秒,不然用户一样在开始之后一直点。
限流:
限流这点,可以分为前端限流和后端限流。
前端限流:
这个比较简单,一版秒杀不会让你一直点的,一般都是点击一下或者两下,然后几秒之后才可以继续点击,这也是保护服务器的一种手段。
后端限流:
秒杀的时候肯定涉及到后续的订单生成和支付等操作,但是都只是成功的幸运儿才会走到那一步,那一旦100个产品卖完了,return 了一个 false,前端直接秒杀结束,然后你后端也关闭后续无效请求的接入了。
Tip:真正的限流还会有限流组件的加入,例如:阿里的Sentinel、Hystrix等。这就就不展开,就说一下物理的限流。
库存预热:
秒杀的本质,就是对库存的抢夺,每个秒杀的用户来,你都去查一下数据库校验库存,然后扣减库存,撇开性能因素,你不觉得这样很繁琐,对业务开发人员都不友好,而且数据库顶不住啊。
那怎么办呢?
我们都知道数据库顶不住,但是他的兄弟(非关系型数据库Redis)能顶啊!
这就好说了,我们在开始秒杀前,通过定时任务或者运维同学提前把商品的库存加载都Redis中去,让整个流程都在Redis里面去做,然后等秒杀结束了,再异步的去修改库存就好了。
但是用了Redis就有一个问题了,我们上面说了要采用主从,就是我们会去读取库存,然后再判断,之后有库存再去减库存,正常情况下是很OK的,但是高并发的情况下问题就很大了。
现在库存只剩下1个了,高并发嘛,假设4个服务器一起查询了,发现都是还有1个,那大家都觉得是自己抢到了,就都去扣库存,结果就变成 -3。是的,有一个是真的抢到了,但别的都超卖了。咋整?赔钱咯。。。
Lua:
不要慌,赔几个钱而已,问题不大。。。下面说个神器,连几个钱也不用赔。
Lua 脚本功能是 Redis 在2.6版本的最大亮点,通过内嵌对 Lua 环境的支持,Reids 解决了长久以来不能高效地处理 CAS(check-and-set)命令的缺点,并且可以通过组合使用多个命令,轻松实现以前很难实现或者不能高效实现的模式。
Lua 脚本是类似 Redis 事务,有一定的原子性,不会被其他命令插队,可以完成一些 Redis 事务性的操作,这点是关键。
知道了原理,我们就写一个脚本把判断库存和扣减库存的操作都写在一个脚本,丢给 Redis 去做,到0后面的都 return false 了,一个失败了你修改一个开关,直接挡住了所有的请求,然后再做后面的事情嘛。
限流&降级&熔断&隔离:
这个为啥要做呢?不怕一万就怕万一,万一真顶不住了,限流,顶不住就挡一部分出去,但是不能说不行;降级,降级了还是被打挂了,熔断,至少不要影响别的系统,隔离,你本身就是独立的,但是你会调用其他系统嘛,你快不行了你也不能拖累其他兄弟呀。
削峰填谷:
一说到这个词,很多兄die就知道了,对的,MQ,你买东西少了,你直接100个请求改库我觉得也没啥问题,但是万一秒杀一万个呢?服务器挂了,又要杀程序猿祭天了。。。
Tip:可能有的兄die说,我们业务量达不到这个量级,没必要。但是我想说我们写代码,就不应该写出有逻辑漏洞的代码,至少以后公司体量上去了,代码居然不用改。小伙子这手代码写得真漂亮!
总结:
到这里,我觉得基本上把该考虑的店,还有对应的解决方案都说了一下,也许还有没有考虑到的,欢迎补充哈。
最后放个完整的流程图出来吧。
Redis系列六 - 浅谈如何设计秒杀系统的更多相关文章
- 【Fiori系列】浅谈SAP Fiori的设计美感与发展历程
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[Fiori系列]浅谈SAP Fiori的设计美 ...
- 【WebApi系列】浅谈HTTP
[01]浅谈HTTP在WebApi开发中的运用 [02]聊聊WebApi体系结构 [03]详解WebApi如何传递参数 [04]详解WebApi测试和PostMan [05]浅谈WebApi Core ...
- 【WebApi系列】浅谈HTTP在WebApi开发中的运用
WebApi系列文章 [01]浅谈HTTP在WebApi开发中的运用 [02]聊聊WebApi体系结构 [03]详解WebApi参数的传递 [04]详解WebApi测试和PostMan [05]浅谈W ...
- 【公众号系列】浅谈SAP项目管理的技能
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[[公众号系列]浅谈SAP项目管理的技能 写 ...
- 【ASP.NET MVC系列】浅谈数据注解和验证
[ASP.NET MVC系列]浅谈数据注解和验证 [01]浅谈Google Chrome浏览器(理论篇) [02]浅谈Google Chrome浏览器(操作篇)(上) [03]浅谈Google C ...
- Spring5.0源码学习系列之浅谈BeanFactory创建
Spring5.0源码学习系列之浅谈BeanFactory创建过程 系列文章目录 提示:Spring源码学习专栏链接 @ 目录 系列文章目录 博客前言介绍 一.获取BeanFactory主流程 二.r ...
- 【ASP.NET MVC系列】浅谈NuGet在VS中的运用
一 概述 在我们讲解NuGet前,我们先来看看一个例子. 1.例子: 假设现在开发一套系统,其中前端框架我们选择Bootstrap,由于选择Bootstrap作为前端框架,因此,在项目中,我们 ...
- 【ASP.NET MVC系列】浅谈表单和HTML辅助方法
[01]浅谈Google Chrome浏览器(理论篇) [02]浅谈Google Chrome浏览器(操作篇)(上) [03]浅谈Google Chrome浏览器(操作篇)(下) [04]浅谈ASP. ...
- 【ASP.NET MVC系列】浅谈ASP.NET MVC八大类扩展(上篇)
lASP.NET MVC系列文章 [01]浅谈Google Chrome浏览器(理论篇) [02]浅谈Google Chrome浏览器(操作篇)(上) [03]浅谈Google Chrome浏览器(操 ...
随机推荐
- Hibernate学习之hql 与sql
Hibernate中查询: createQuery( String qlString)使用的是HQL语句: createNativeQuery (String sqlString)使用的是SQL语句: ...
- 转载-WebSocket协议解析
现在,很多网站为了实现推送技术,所用的技术都是轮询.轮询是指在特定的时间间隔(如每一秒),由浏览器对服务器发起HTTP请求,然后由服务器返回数据给浏览器.由于HTTP协议是惰性的,只有客户端发起请求, ...
- OpenCV 改变图像的对比度和亮度
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <ios ...
- OpenCV Mat - 基本图像容器
Mat 在2001年刚刚出现的时候,OpenCV基于 C 语言接口而建.为了在内存(memory)中存放图像,当时采用名为 IplImage 的C语言结构体,时至今日这仍出现在大多数的旧版教程和教学材 ...
- SHELL用法六(Find语句)
1.SHELL编程Find语句案例实战 1)SHELL编程四剑客工具:Find.Grep.Sed.Awk,通过四剑客可以完成常规Linux指令无法完成或者比较复杂的功能,学好SHELL编程四剑客有助于 ...
- python中sort和sorted排序的相关方法
Python list内置sort()方法用来排序,也可以用python内置的全局sorted()方法来对可迭代的序列排序生成新的序列. 1)排序基础 简单的升序排序是非常容易的.只需要调用sorte ...
- mysql表关联问题(第三卷:外键多对多)
现在我们整理一下多对多的问题,举个例子现在一个男的可能和多个女的谈过恋爱,一个女的也可能和多个男的谈过恋爱,把他们恋爱的关系整理为数据关联表就成为了多对多的关系. 准备三张表,男人信息表,女人信息表, ...
- JS的时间差换算(String to 自己想要的时间格式)
JS的时间差换算(String to 标准的时间格式) 1.字符串到标准时间格式: 字符串: var time1="2018-05-11 00:00:00" var time2=& ...
- UnitTest测试框架-操作步骤
一.UnitTest 1. TestCase 说明:测试用例 1.新建类并集成unittest.TestCase 2. TestSuite 说明:测试套件(多条用例) 方法: 1. 实例化 suite ...
- Java web期末项目第一阶段成果发表
摘要 我们做的系统是一个基于Java web与MySQL的食堂订餐系统 班级: 计科二班 小组成员:李鉴宣.袁超 我们的第一阶段主要完成以下三件事: 完成项目的需求分析 完成项目的领域逻辑(domai ...