中生代技术

链接技术大咖,分享技术干货

全文:4000字

作者:Yrion

原文首发:https://www.cnblogs.com/wyq178/p/11261711.html

前言:秒杀系统相信很多人见过,比如京东或者淘宝的秒杀,小米手机的秒杀,那么秒杀系统的后台是如何实现的呢?我们如何设计一个秒杀系统呢?对于秒杀系统应该考虑哪些问题?如何设计出健壮的秒杀系统?本期我们就来探讨一下这个问题:

目录

一:秒杀系统应该考虑的问题

二:秒杀系统的设计和技术方案

三:系统架构图

四:总结

一:秒杀应该考虑哪些问题

1.1:超卖问题

分析秒杀的业务场景,最重要的有一点就是超卖问题,假如备货只有100个,但是最终超卖了200,一般来讲秒杀系统的价格都比较低,如果超卖将严重影响公司的财产利益,因此首当其冲的就是解决商品的超卖问题。

1.2:高并发

秒杀具有时间短、并发量大的特点,秒杀持续时间只有几分钟,而一般公司都为了制造轰动效应,会以极低的价格来吸引用户,因此参与抢购的用户会非常的多。短时间内会有大量请求涌进来,后端如何防止并发过高造成缓存击穿或者失效,击垮数据库都是需要考虑的问题。

1.3:接口防刷

现在的秒杀大多都会出来针对秒杀对应的软件,这类软件会模拟不断向后台服务器发起请求,一秒几百次都是很常见的,如何防止这类软件的重复无效请求,防止不断发起的请求也是需要我们针对性考虑的。

1.4:秒杀url

对于普通用户来讲,看到的只是一个比较简单的秒杀页面,在未达到规定时间,秒杀按钮是灰色的,一旦到达规定时间,灰色按钮变成可点击状态。这部分是针对小白用户的,如果是稍微有点电脑功底的用户,会通过F12看浏览器的network看到秒杀的url,通过特定软件去请求也可以实现秒杀。或者提前知道秒杀url的人,一请求就直接实现秒杀了。这个问题我们需要考虑解决。

1.5:数据库设计

秒杀有把我们服务器击垮的风险,如果让它与我们的其他业务使用在同一个数据库中,耦合在一起,就很有可能牵连和影响其他的业务。如何防止这类问题发生,就算秒杀发生了宕机、服务器卡死问题,也应该让他尽量不影响线上正常进行的业务。

1.6:大量请求问题

按照1.2的考虑,就算使用缓存还是不足以应对短时间的高并发的流量的冲击。如何承载这样巨大的访问量,同时提供稳定低时延的服务保证,是需要面对的一大挑战。我们来算一笔账,假如使用的是redis缓存,单台redis服务器可承受的QPS大概是4W左右,如果一个秒杀吸引的用户量足够多的话,单QPS可能达到几十万,单体redis还是不足以支撑如此巨大的请求量。缓存会被击穿,直接渗透到DB,从而击垮mysql.后台会将会大量报错。

二:秒杀系统的设计和技术方案

2.1:秒杀系统数据库设计

针对1.5提出的秒杀数据库的问题,因此应该单独设计一个秒杀数据库,防止因为秒杀活动的高并发访问拖垮整个网站。这里只需要两张表,一张是秒杀订单表,一张是秒杀货品表

其实应该还有几张表,商品表:可以关联goods_id查到具体的商品信息,商品图像、名称、平时价格、秒杀价格等,还有用户表:根据用户user_id可以查询到用户昵称、用户手机号,收货地址等其他额外信息,这个具体就不给出实例了。

2.2:秒杀url的设计

为了避免有程序访问经验的人通过下单页面url直接访问后台接口来秒杀货品,我们需要将秒杀的url实现动态化,即使是开发整个系统的人都无法在秒杀开始前知道秒杀的url。具体的做法就是通过md5加密一串随机字符作为秒杀的url,然后前端访问后台获取具体的url,后台校验通过之后才可以继续秒杀。

2.3:秒杀页面静态化

将商品的描述、参数、成交记录、图像、评价等全部写入到一个静态页面,用户请求不需要通过访问后端服务器,不需要经过数据库,直接在前台客户端生成,这样可以最大可能的减少服务器的压力。具体的方法可以使用freemarker模板技术,建立网页模板,填充数据,然后渲染网页

2.4:单体redis升级为集群redis

秒杀是一个读多写少的场景,使用redis做缓存再合适不过。不过考虑到缓存击穿问题,我们应该构建redis集群,采用哨兵模式,可以提升redis的性能和可用性。

2.5:使用nginx

nginx是一个高性能web服务器,它的并发能力可以达到几万,而tomcat只有几百。通过nginx映射客户端请求,再分发到后台tomcat服务器集群中可以大大提升并发能力。

2.6:精简sql

典型的一个场景是在进行扣减库存的时候,传统的做法是先查询库存,再去update。这样的话需要两个sql,而实际上一个sql我们就可以完成的。可以用这样的做法:update miaosha_goods  set stock =stock-1 where goos_id ={#goods_id} and  version = #{version} and sock>0;这样的话,就可以保证库存不会超卖并且一次更新库存,还有注意一点这里使用了版本号的乐观锁,相比较悲观锁,它的性能较好。

2.7:redis预减库存

很多请求进来,都需要后台查询库存,这是一个频繁读的场景。可以使用redis来预减库存,在秒杀开始前可以在redis设值,比如redis.set(goodsId,100),这里预放的库存为100可以设值为常量),每次下单成功之后,Integer stock = (Integer)redis.get(goosId); 然后判断sock的值,如果小于常量值就减去1;不过注意当取消的时候,需要增加库存,增加库存的时候也得注意不能大于之间设定的总库存数(查询库存和扣减库存需要原子操作,此时可以借助lua脚本)下次下单再获取库存的时候,直接从redis里面查就可以了。

2.8:接口限流

秒杀最终的本质是数据库的更新,但是有很多大量无效的请求,我们最终要做的就是如何把这些无效的请求过滤掉,防止渗透到数据库。限流的话,需要入手的方面很多:

2.8.1:前端限流

首先第一步就是通过前端限流,用户在秒杀按钮点击以后发起请求,那么在接下来的5秒是无法点击(通过设置按钮为disable)。这一小举措开发起来成本很小,但是很有效。

2.8.2:同一个用户xx秒内重复请求直接拒绝

具体多少秒需要根据实际业务和秒杀的人数而定,一般限定为10秒。具体的做法就是通过redis的键过期策略,首先对每个请求都从String value = redis.get(userId);如果获取到这个

value为空或者为null,表示它是有效的请求,然后放行这个请求。如果不为空表示它是重复性请求,直接丢掉这个请求。如果有效,采用redis.setexpire(userId,value,10).value可以是任意值,一般放业务属性比较好,这个是设置以userId为key,10秒的过期时间(10秒后,key对应的值自动为null)

2.8.3:令牌桶算法限流

接口限流的策略有很多,我们这里采用令牌桶算法。令牌桶算法的基本思路是每个请求尝试获取一个令牌,后端只处理持有令牌的请求,生产令牌的速度和效率我们都可以自己限定,guava提供了RateLimter的api供我们使用。以下做一个简单的例子,注意需要引入guava

public class TestRateLimiter {
public static void main(String[] args) {//1秒产生1个令牌final RateLimiter rateLimiter = RateLimiter.create(1);for (int i = 0; i < 10; i++) {//该方法会阻塞线程,直到令牌桶中能取到令牌为止才继续向下执行。double waitTime= rateLimiter.acquire();System.out.println("任务执行" + i + "等待时间" + waitTime);}System.out.println("执行结束");}
}

上面代码的思路就是通过RateLimiter来限定我们的令牌桶每秒产生1个令牌(生产的效率比较低),循环10次去执行任务。acquire会阻塞当前线程直到获取到令牌,也就是如果任务没有获取到令牌,会一直等待。那么请求就会卡在我们限定的时间内才可以继续往下走,这个方法返回的是线程具体等待的时间。执行如下;

可以看到任务执行的过程中,第1个是无需等待的,因为已经在开始的第1秒生产出了令牌。接下来的任务请求就必须等到令牌桶产生了令牌才可以继续往下执行。如果没有获取到就会阻塞(有一个停顿的过程)。不过这个方式不太好,因为用户如果在客户端请求,如果较多的话,直接后台在生产token就会卡顿(用户体验较差),它是不会抛弃任务的,我们需要一个更优秀的策略:如果超过某个时间没有获取到,直接拒绝该任务。接下来再来个案例:

public class TestRateLimiter2 {public static void main(String[] args) {final RateLimiter rateLimiter = RateLimiter.create(1);for (int i = 0; i < 10; i++) {long timeOut = (long) 0.5;boolean isValid = rateLimiter.tryAcquire(timeOut, TimeUnit.SECONDS);System.out.println("任务" + i + "执行是否有效:" + isValid);if (!isValid) {continue;}System.out.println("任务" + i + "在执行");}System.out.println("结束");}
}

其中用到了tryAcquire方法,这个方法的主要作用是设定一个超时的时间,如果在指定的时间内预估(注意是预估并不会真实的等待),如果能拿到令牌就返回true,如果拿不到就返回false.然后我们让无效的直接跳过,这里设定每秒生产1个令牌,让每个任务尝试在

0.5秒获取令牌,如果获取不到,就直接跳过这个任务(放在秒杀环境里就是直接抛弃这个请求);程序实际运行如下:

只有第1个获取到了令牌,顺利执行了,下面的基本都直接抛弃了,因为0.5秒内,令牌桶(1秒1个)来不及生产就肯定获取不到返回false了。

这个限流策略的效率有多高呢?假如我们的并发请求是400万瞬间的请求,将令牌产生的效率设为每秒20个,每次尝试获取令牌的时间是0.05秒,那么最终测试下来的结果是,每次只会放行4个左右的请求,大量的请求会被拒绝,这就是令牌桶算法的优秀之处。

2.9:异步下单

为了提升下单的效率,并且防止下单服务的失败。需要将下单这一操作进行异步处理。最常采用的办法是使用队列,队列最显著的三个优点:异步、削峰、解耦。这里可以采用rabbitmq,在后台经过了限流、库存校验之后,流入到这一步骤的就是有效请求。然后发送到队列里,队列接受消息,异步下单。下完单,入库没有问题可以用短信通知用户秒杀成功。假如失败的话,可以采用补偿机制,重试。

3.0:服务降级

假如在秒杀过程中出现了某个服务器宕机,或者服务不可用,应该做好后备工作。之前的博客里有介绍通过Hystrix进行服务熔断和降级,可以开发一个备用服务,假如服务器真的宕机了,直接给用户一个友好的提示返回,而不是直接卡死,服务器错误等生硬的反馈。

三:总结

秒杀流程图

这就是我设计出来的秒杀流程图,当然不同的秒杀体量针对的技术选型都不一样,这个流程可以支撑起几十万的流量,如果是成千万破亿那就得重新设计了。比如数据库的分库分表、队列改成用kafka、redis增加集群数量等手段。通过本次设计主要是要表明的是我们如何应对高并发的处理,并开始尝试解决它,在工作中多思考、多动手能提升我们的能力水平,加油!如果本篇博客有任何错误,请麻烦指出来,不胜感激。

作者:Yrion

地址:https://www.cnblogs.com/wyq178/

编辑:大白

猜你喜欢

阿里巴巴闲鱼架构负责人王树彬:万亿交易规模技术架构实践 2020-09-05

58转转技术总监骆俊武:监控系统选型?必读本篇! 2020-09-04

蚂蚁集团高级架构师郭援非:分布式数据库是金融机构数字化转型的最佳路径 2020-09-03

工行高级经理林承军:工行基于 MySQL 构建分布式架构的转型之路 2020-09-02

知道创宇总监姚昌林:敏捷开发-如何打破研发交付过程中的“墙” 2020-08-28

微博技术专家陈波:百亿级访问量的应用如何做缓存架构设计 2020-08-27

天弘基金首席架构师李鑫:微服务接口限流的算法及架构实现 2020-08-25

飞天茅台超卖P0事故:请慎用Redis分布式锁! 2020-08-17

   END
#接力技术,链接价值#

点分享点点赞点在看

大神手把手教你设计秒杀架构模型相关推荐

  1. 我的世界服务器地图文件丢失,我的世界地图被毁了或找不到了 ? 大神手把手教你奇迹恢复...

    Hello everybody,我是沙盒帝,这次要讲的是技术性的问题. 很多玩家有时候会问我,地图被毁了或者存档找不到了,要么就是被熊孩子给熊了,很难过什么的.其实我对这方面的问题也是处于一个小白的水 ...

  2. 外国大神手把手教你如何设计软件架构

    介绍 软件系统的架构对其成功至关重要.它决定了不同部分如何组合在一起,并影响系统开发和维护的速度.良好的架构设计还可以帮助您避免在开发过程中出现代价高昂的错误,例如组件过多或无法创建健壮的逻辑.这篇文 ...

  3. 大神手把手教源码阅读的方法、误区以及三种境界

    丁威 中间件兴趣圈 读完需要 1 分钟 速读仅需 1 分钟 在技术职场中普遍存在如下几种现象: 对待工作中所使用的技术不需要阅读源码,只需在开发过程中能够熟练运用就行 看源码太费时间,而且容易忘记,如 ...

  4. 写不出好的策划方案?大神手把手教你写策划

    每个企业都少不了要写策划方案和执行计划,但是策划人员又都是天天在办公室坐着的,怎样才能写出符合市场的策划方案呢?易创作为一个多年的老司机,现在就开始带你飞,让你感受一下大牛是如何写策划方案的. 那么关 ...

  5. 这本读者期待的芯片书《手把手教你设计CPU——RISC-V处理器》终于出版!

    点击关注异步图书,置顶公众号 每天与你分享IT好书 技术干货 职场知识 ​ ​点击图片购书​ 参与文末话题讨论,每日赠送异步图书 --异步小编 在摩尔定律减缓的今天,一味比拼硬件性能的技术竞赛变得越发 ...

  6. 我的中国“芯”,手把手教你设计CPU

    ​点击关注异步图书,置顶公众号 每天与你分享 IT好书 技术干货 职场知识​ ​ ​参与文末话题讨论,每日赠送异步图书. --异步小编 永恒的热点--CPU 灯,等灯等灯-- -- Intel 如果要 ...

  7. 手把手教你设计接口自动化测试用例:根据接口信息设计测试用例

    目录 01 设计主测试用例的字段 02 设计配置信息的字段 03 设计执行结果记录的字段 04 设计主测试用例内容并解决关联关系 05 设计配置信息的内容 06 执行结果记录的内容 07 参考建议 0 ...

  8. 软件测试菜鸟还是大神,一道测试用例设计笔试题就能测出!

    软件测试菜鸟还是大神,一道测试用例设计笔试题就能测出!为什么这么说呢?是因为你们的思路有着天壤之别,来看吧: 相信不少朋友在笔试的时候都遇到过测试用例设计的笔试题.通常是一个登陆页面,上面有用户名,密 ...

  9. RISC-V学习资料:《手把手教你设计CPU——RISC-V处理器》

    <手把手教你设计CPU--RISC-V处理器> 单击此处样章试读 作者:胡振波 出版社: 人民邮电出版社 ISBN:9787115480521 版次:1 商品编码:12360850 包装: ...

最新文章

  1. 绝悟之后再超神,腾讯30篇论文入选AI顶会ACL
  2. oracle 数据库跨库查询方法
  3. Linux-PAM PAM-MySQL的总结
  4. Enterprise Library Step By Step系列(一):配置应用程序块——入门篇
  5. 《T-SQL性能调优秘笈——基于SQL Server 2012 窗口函数》——1.2 使用窗口函数的解决方案简介...
  6. Java IO流练习题-获取文本上每个字符出现的次数
  7. 图像分割中dc_loss忽视标签实现
  8. 70+优秀的前端工具(转)
  9. 各地少先队深入开展红领巾心向党主题教育-少先队-红领巾心向党-主题教育
  10. ubuntu oracle 工具,ubuntu上安装Oracle InstantClient
  11. CROW-5 WEB APP引擎商业计划书(HTML5方向)-微信网页版微信公众平台登录-水仙谷...
  12. springboot集成es实现搜索功能
  13. 如何做到阿里云 Redis 开发规范中的拒绝 bigkey
  14. oracle中的start with
  15. 达叔的游戏框架(二) 得到其他模块的方式
  16. 已分区的硬盘如何重新合并, 分出去的盘怎么重新合并
  17. 利用Photoshop进行快速切图
  18. 微信小程序开发—背景图片全屏(无白条)
  19. 【解决方案】快递代收点部署视频监控,EasyCVR视频融合平台来助力
  20. manjaro折腾手记

热门文章

  1. c语言autoi函数如何使用,C++的auto声明、memset函数
  2. jsp mysql书店源码_使用jsp数据库mysql实现网上购物书店课程设计
  3. android底层oem,Android中如何实现OEM
  4. php 用积分兑换_PHP积分兑换接口demo
  5. (软件工程复习核心重点)第一章软件工程概论-第二节:软件工程
  6. (计算机组成原理)第七章输入和输出系统-第三节:I/O接口
  7. Linux系统编程8:入门篇之简单明了说明如何在Linux中Git提交代码
  8. 动态库的链接和链接选项-L,-rpath-link,-rpath
  9. XShell 将Linux文件上传、下载到Windows下(rz上传、sz下载)
  10. 如何在虚拟机中安装kali linux