秒杀系统相信不少人见过,好比京东或者淘宝的秒杀,小米手机的秒杀,那么秒杀系统的后台是如何实现的呢?咱们如何设计一个秒杀系统呢?对于秒杀系统应该考虑哪些问题?如何设计出健壮的秒杀系统?本文咱们就来探讨一下这个问题。php
秒杀应该考虑哪些问题html
超卖问题前端
分析秒杀的业务场景,最重要的有一点就是超卖问题,假如备货只有100个,可是最终超卖了200,通常来说秒杀系统的价格都比较低,若是超卖将严重影响公司的财产利益,所以首当其冲的就是解决商品的超卖问题。redis
高并发算法
秒杀具备时间短、并发量大的特色,秒杀持续时间只有几分钟,而通常公司都为了制造轰动效应,会以极低的价格来吸引用户,所以参与抢购的用户会很是的多。短期内会有大量请求涌进来,后端如何防止并发太高形成缓存击穿或者失效,击垮数据库都是须要考虑的问题。数据库
接口防刷后端
如今的秒杀大多都会出来针对秒杀对应的软件,这类软件会模拟不断向后台服务器发起请求,一秒几百次都是很常见的,如何防止这类软件的重复无效请求,防止不断发起的请求也是须要咱们针对性考虑的。浏览器
秒杀url缓存
对于普通用户来说,看到的只是一个比较简单的秒杀页面,在未达到规定时间,秒杀按钮是灰色的,一旦到达规定时间,灰色按钮变成可点击状态。这部分是针对小白用户的,若是是稍微有点电脑功底的用户,会经过F12看浏览器的network看到秒杀的url,经过特定软件去请求也能够实现秒杀。或者提早知道秒杀url的人,一请求就直接实现秒杀了。这个问题咱们须要考虑解决。服务器
数据库设计
秒杀有把咱们服务器击垮的风险,若是让它与咱们的其余业务使用在同一个数据库中,耦合在一块儿,就颇有可能牵连和影响其余的业务。如何防止这类问题发生,就算秒杀发生了宕机、服务器卡死问题,也应该让他尽可能不影响线上正常进行的业务。
大量请求问题
按照「高并发」的考虑,就算使用缓存仍是不足以应对短期的高并发的流量的冲击。如何承载这样巨大的访问量,同时提供稳定低时延的服务保证,是须要面对的一大挑战。咱们来算一笔帐,假如使用的是Redis缓存,单台Redis服务器可承受的QPS大概是4W左右,若是一个秒杀吸引的用户量足够多的话,单QPS可能达到几十万,单体Redis仍是不足以支撑如此巨大的请求量。缓存会被击穿,直接渗透到DB,从而击垮MySQL,后台会将会大量报错。
秒杀系统的设计和技术方案
秒杀系统数据库设计
针对「数据库设计」提出的秒杀数据库的问题,所以应该单独设计一个秒杀数据库,防止由于秒杀活动的高并发访问拖垮整个网站。这里只须要两张表,一张是秒杀订单表,一张是秒杀货品表。
其实应该还有几张表,商品表:能够关联goods_id查到具体的商品信息,商品图像、名称、平时价格、秒杀价格等,还有用户表:根据用户user_id能够查询到用户昵称、用户手机号,收货地址等其余额外信息,这个具体就不给出实例了。
秒杀url的设计
为了不有程序访问经验的人经过下单页面url直接访问后台接口来秒杀货品,咱们须要将秒杀的url实现动态化,即便是开发整个系统的人都没法在秒杀开始前知道秒杀的url。具体的作法就是经过md5加密一串随机字符做为秒杀的url,而后前端访问后台获取具体的url,后台校验经过以后才能够继续秒杀。
秒杀页面静态化
将商品的描述、参数、成交记录、图像、评价等所有写入到一个静态页面,用户请求不须要经过访问后端服务器,不须要通过数据库,直接在前台客户端生成,这样能够最大可能的减小服务器的压力。具体的方法可使用freemarker模板技术,创建网页模板,填充数据,而后渲染网页。
单体Redis升级为集群Redis
秒杀是一个读多写少的场景,使用Redis作缓存再合适不过。不过考虑到缓存击穿问题,咱们应该构建Redis集群,采用哨兵模式,能够提高Redis的性能和可用性。
使用Nginx
Nginx是一个高性能Web服务器,它的并发能力能够达到几万,而Tomcat只有几百。经过Nginx映射客户端请求,再分发到后台Tomcat服务器集群中能够大大提高并发能力。
精简SQL
典型的一个场景是在进行扣减库存的时候,传统的作法是先查询库存,再去update。这样的话须要两个SQL,而实际上一个SQL咱们就能够完成的。能够用这样的作法:update miaosha_goods set stock =stock-1 where goos_id ={#goods_id} and version = #{version} and sock>0;这样的话,就能够保证库存不会超卖而且一次更新库存,还有注意一点这里使用了版本号的乐观锁,相比较悲观锁,它的性能较好。
Redis预减库存
不少请求进来,都须要后台查询库存,这是一个频繁读的场景。可使用Redis来预减库存,在秒杀开始前能够在Redis设值,好比redis.set(goodsId,100),这里预放的库存为100能够设值为常量,每次下单成功以后,Integer stock = (Integer)redis.get(goosId); 而后判断sock的值,若是小于常量值就减去1;不过注意当取消的时候,须要增长库存,增长库存的时候也得注意不能大于之间设定的总库存数(查询库存和扣减库存须要原子操做,此时能够借助lua脚本)下次下单再获取库存的时候,直接从Redis里面查就能够了。
接口限流
秒杀最终的本质是数据库的更新,可是有不少大量无效的请求,咱们最终要作的就是如何把这些无效的请求过滤掉,防止渗透到数据库。限流的话,须要入手的方面不少:
前端限流
首先第一步就是经过前端限流,用户在秒杀按钮点击之后发起请求,那么在接下来的5秒是没法点击(经过设置按钮为disable)。这一小举措开发起来成本很小,可是颇有效。
同一个用户xx秒内重复请求直接拒绝
具体多少秒须要根据实际业务和秒杀的人数而定,通常限定为10秒。具体的作法就是经过Redis的键过时策略,首先对每一个请求都从String value = redis.get(userId);若是获取到这个value为空或者为null,表示它是有效的请求,而后放行这个请求。若是不为空表示它是重复性请求,直接丢掉这个请求。若是有效,采用redis.setexpire(userId,value,10).value能够是任意值,通常放业务属性比较好,这个是设置以userId为key,10秒的过时时间(10秒后,key对应的值自动为null)。
令牌桶算法限流
接口限流的策略有不少,咱们这里采用令牌桶算法。令牌桶算法的基本思路是每一个请求尝试获取一个令牌,后端只处理持有令牌的请求,生产令牌的速度和效率咱们均可以本身限定,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个左右的请求,大量的请求会被拒绝,这就是令牌桶算法的优秀之处。
异步下单
为了提高下单的效率,而且防止下单服务的失败。须要将下单这一操做进行异步处理。最常采用的办法是使用队列,队列最显著的三个优势:异步、削峰、解耦。这里能够采用RabbitMQ,在后台通过了限流、库存校验以后,流入到这一步骤的就是有效请求。而后发送到队列里,队列接受消息,异步下单。下完单,入库没有问题能够用短信通知用户秒杀成功。假如失败的话,能够采用补偿机制,重试。
服务降级
假如在秒杀过程当中出现了某个服务器宕机,或者服务不可用,应该作好后备工做。以前的博客里有介绍经过Hystrix进行服务熔断和降级,能够开发一个备用服务,假如服务器真的宕机了,直接给用户一个友好的提示返回,而不是直接卡死,服务器错误等生硬的反馈。
总结
秒杀流程图:
这就是我设计出来的秒杀流程图,固然不一样的秒杀体量针对的技术选型都不同,这个流程能够支撑起几十万的流量,若是是成千万破亿那就得从新设计了。好比数据库的分库分表、队列改为用Kafka、Redis增长集群数量等手段。经过本次设计主要是要代表的是咱们如何应对高并发的处理,并开始尝试解决它,在工做中多思考、多动手能提高咱们的能力水平,加油!若是本篇博客有任何错误,请麻烦指出来,不胜感激。
原文连接:https://www.cnblogs.com/wyq178/p/11261711.html
Kubernetes管理员认证(CKA)培训
本次CKA培训将于11月20到22日在北京开课,培训基于最新考纲,经过线下授课、考题解读、模拟演练等方式,帮助学员快速掌握Kubernetes的理论知识和专业技能,并针对考试作特别强化训练,让学员能从容面对CKA认证考试,使学员既能掌握Kubernetes相关知识,又能经过CKA认证考试,学员可屡次参加培训,直到经过认证。点击下方图片或者阅读原文连接查看详情。