-     前言     -

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

-     秒杀应该考虑哪些问题     -

1、超卖问题

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

2、高并发

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

3、接口防刷

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

4、秒杀 url

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

5、数据库设计

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

6、大量请求问题

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

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

1、秒杀系统数据库设计

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

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

2、秒杀 url 的设计

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

3、秒杀页面静态化

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

4、单体 Redis 升级为集群 Redis

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

5、使用 Nginx

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

6、精简SQL

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

7、Redis 预减库存

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

8、接口限流

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

(1)前端限流

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

(2)同一个用户 xx 秒内重复请求直接拒绝

具体多少秒需要根据实际业务和秒杀的人数而定,一般限定为 10 秒。具体的做法就是通过 Redis 的键过期策略,首先对每个请求都从 String value = redis.get(userId);如果获取到这个 value 为空或者为 null,表示它是有效的请求,然后放行这个请求。如果不为空表示它是重复性请求,直接丢掉这个请求。如果有效,采用 redis.setexpire(userId,value,10).value 可以是任意值,一般放业务属性比较好,这个是设置以 userId 为 key,10 秒的过期时间(10秒后,key对应的值自动为null)。

(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 个左右的请求,大量的请求会被拒绝,这就是令牌桶算法的优秀之处。

9、异步下单

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

10、服务降级

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

-     总结     -

秒杀流程图:

这就是我设计出来的秒杀流程图,当然不同的秒杀体量针对的技术选型都不一样,这个流程可以支撑起几十万的流量,如果是成千万破亿那就得重新设计了。

比如数据库的分库分表、队列改成用 Kafka、Redis 增加集群数量等手段。通过本次设计主要是要表明的是我们如何应对高并发的处理,并开始尝试解决它,在工作中多思考、多动手能提升我们的能力水平,加油!如果本篇博客有任何错误,请麻烦指出来,不胜感激。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

双十一秒杀架构模型设计实践!相关推荐

  1. 双十一秒杀架构模型设计

    公众号回复'架构'获取架构师电子书及视频课程 秒杀系统相信很多人见过,比如京东或者淘宝的秒杀,小米手机的秒杀,那么秒杀系统的后台是如何实现的呢?我们如何设计一个秒杀系统呢?对于秒杀系统应该考虑哪些问题 ...

  2. 泥瓦匠:秒杀架构设计实践思路(一)

    2019独角兽企业重金招聘Python工程师标准>>> 企业级一站式软件研发协作平台   摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSo ...

  3. Java实战系列之关于双十一秒杀架构模型的一点思考

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

  4. 京东秒杀架构升级优化实践

    点击"开发者技术前线",选择"星标" 让一部分开发者看到未来 前言 京东秒杀是京东最大的营销频道,近年来随着业务的高速发展,频道商品数量和用户流量都呈现出迅猛增 ...

  5. 技术干货(2 of 5):电商秒杀架构分析

    在上一篇"分析与实战:微信红包体系的整理"文章中,我们对微信红包的体系结构进行了非常深入详细的剖析,并且着重讨论了微信红包在高并发环境下所面临的问题及应对的策略. 说到高并发,其实 ...

  6. 秒杀系统架构分析与实战,一文带你搞懂秒杀架构!

    作者丨猿码道 jianshu.com/p/df4fbecb1a4b 1.秒杀业务分析 正常电子商务流程 (1)查询商品: (2)创建订单: (3)扣减库存: (4)更新订单: (5)付款: (6)卖家 ...

  7. 黄东旭:Cloud-Native 的分布式数据库架构与实践

    19 日,我司 CTO 黄东旭同学在全球云计算开源大会上,发表了<Cloud-Native 的分布式数据库架构与实践>主题演讲,以下为演讲实录~~ 大家好,今天我的题目是 Cloud-Na ...

  8. Linux运维与架构工程实践

    1 本章概览 介绍Linux系统Linux运维与架构工程实践的方方面面 2 Linux系统基础入门 认识计算机和服务器核心硬件 Linux发展历史.系统组成.应用领域以及发行版 搭建运维环境:VMWa ...

  9. 高并发、高性能下的 会员系统[同程艺龙] — 高可用架构设计实践

    目录 会员系统[同程艺龙] - 高可用架构设计实践 ES高可用方案 ES双中心主备集群架构 ES流量隔离三集群架构 ES集群深度优化提升 会员Redis缓存方案 Redis双中心多集群架构 高可用会员 ...

最新文章

  1. 题目 1470:【蓝桥杯】【入门题】【基础练习VIP】时间转换
  2. matplotlib 设置坐标轴单位
  3. SAP Retail Execution Overview
  4. 矩阵-----对称矩阵及其压缩存储稀疏矩阵
  5. 怎样才算精通Python?
  6. python星空画法教程_对比几段代码,看看你是 Python 菜鸟还是老鸟
  7. es算法matlab编程,matlab练习程序(演化策略ES)
  8. prestashop 隐藏 index.php,删除PrestaShop中的供应商和制造商页面
  9. pythonfor循环是迭代器吗_[Python] 迭代器是什么?你每天在用的for循环都依赖它!...
  10. C++进阶—— helper function 的设计与实现
  11. 国军标GJB150可靠性试验检测服务第三方检测机构报告
  12. 英语总结系列(九):百忙中依然坚持的九月
  13. 【转载】「微信小程序」有哪些冲击与机会?
  14. Java实现计算矩阵的伴随矩阵、逆矩阵
  15. Python爬虫入门教程03:二手房数据爬取
  16. MySQL安装配置步骤
  17. 跨境电商虾皮值不值得做?你了解多少
  18. undefined reference to `__stack_chk_guard‘ .. undefined reference to `__stack_chk_fail‘
  19. 西华大学计算机科学与技术学院,王秀华 - 西华大学 - 计算机与软件工程学院
  20. Java 对象排序完整版

热门文章

  1. 简单介绍.Net性能测试框架Crank的使用方法
  2. Linux 中创建 USB 启动盘来拯救 Windows 用户
  3. mysql 释放错误连接_JSP连接MySQL后数据库链接释放的错误
  4. 【Java】排序算法之 冒泡排序
  5. 背包问题(多重背包+0-1背包)
  6. 服务器维护简单的备份方案,服务器备份方案计划.doc
  7. appium java类库下载,appium 简明教程 (4)——appium client 的安装
  8. c语言错误2064,VC错误: error C2064: term does not evaluate to a function
  9. SWPU OnlingJudge 在线评测平台 使用教程
  10. c++ vector简单使用