几年前,公司组织了一次技术竞赛。自由组队,每组4人,36小时以内按要求完成设计、代码实现和文档。第一名奖金3万元。前端
一开始不是很想参加,由于要通宵熬夜(如今想一想,不参加竞赛是个明智之举)!不事后来仍是接收了其余部门的邀请,参赛了。可是,在比赛前一天,其余三我的放我鸽子!!!放我鸽子!!!放我鸽子!!!没办法,只能从新组队,最终两个后端、两个前端!可是,比赛当天,另外一个后端去参加歌唱比赛了!!!参加歌唱比赛了!!!参加歌唱比赛了!!!因此变成了一个后端+两个前端。咱们成了当时参赛小组里惟一的三人小组,惟一的有前端的小组,仍是两个前端!!!node
而后比赛的题目是:实现一个秒杀系统!!!web
当时咱们三个就想直接弃权了!可是转念一想,都走到这一步了,仍是试试吧!数据库
最终的结果是,咱们没完成(意不意外?惊不惊喜?)!!!前端在最后时间点才开发完成,测试的时间都没有!后来测试的时候,修复了几个bug,为了公平起见,就没有参与最后的比赛!后端
不过,在全部的参赛队伍中,咱们的吞吐量是最高的,其余队伍的TPS基本在三四千左右!咱们的TPS基本在一万七左右!若是一开始就完成了,基本秒杀其余组!浏览器
咱们的吞吐量是其它组的四倍,究其缘由是由于咱们的人员组成与其余组不一样,致使咱们的设计思路与其余组的设计思路也不一样。缓存
在以前的什么是软件架构一文中,我对软件架构作了一个定义:「架构是特定约束下决策的结果」!服务器
此次技术竞赛的经历,正好能够验证这一观点:即便是在技术不对口、人数不足等劣势条件下,只要作出合适的决策,依然能作出超出预期的架构设计!markdown
有时候,对于合适的架构设计,劣势有时候会成为优点!架构
咱们先来看下,对于秒杀系统来讲,通常的设计思路。
秒杀系统的特色是:
因此秒杀系统须要解决的是「在高并发状况下,用户请求及数据更新的问题」!
通常的设计思路:
具体方式有:
动静分离
对于通常的应用来讲,请求流程大体以下:
当访问量很大的时候,服务器压力会很是的大!解决方案就是动静分离!
作软件开发的都知道要「将变化的内容和不变的内容隔离开」,以便于独立进化。这里其实也是同样的思路。
模板是个静态的内容,部署后通常是不会变化的;而数据是个相对动态的内容,根据请求参数的不一样,数据可能不一样。因此咱们须要将模板与数据分离。
之前的作法是后端事先生成渲染后的页面,缓存起来或直接部署到静态服务器或CDN,请求时直接从缓存(静态服务器/CDN)中获取页面,而动态数据经过AJAX请求的方式获取。服务器再也不须要渲染页面,只须要返回少许的数据便可。既下降了服务器压力,又减小了服务端数据的传输。
而如今很流行的先后端分离就能很容易的解决这个问题。页面独立部署,数据异步获取,页面渲染由浏览器负责。这里和普通的先后端分离还有些差别,须要将相对静态的数据都静态化,以减小动态数据量。
分离后,静态内容和动态内容就能够独立进化。例如静态内容能够部署到CDN上,用户能够从最近的服务器获取到数据。相对热点的动态数据能够作缓存,下降数据库压力,进一步提升服务端响应。
独立部署
「独立部署」其实也能够当作是一种「动静分离」。将秒杀系统这个相对动态的系统,和相对静态的业务系统分开部署。
缘由很好理解,秒杀系统的请求量很大,可能会因为预估不足或系统问题,致使了秒杀系统的负载太高、响应变慢。若是秒杀系统是业务系统的一部分,则会致使业务系统响应变慢,甚至致使系统没有响应。且秒杀是个短时间活动也不是核心业务,而业务系统是须要长期稳定运行的。不能由于一个短时间非核心的活动,而影响了核心的业务系统。
因此秒杀系统最好和业务系统分开独立部署。即便秒杀系统挂了,也不会影响业务系统的正常对外服务。
一样的道理,秒杀系统的数据库也须要和业务系统的数据库独立开。
限流削峰
动静分离,独立部署能提升系统的响应能力和容量。可是可提供的访问量是必定的,当超过了系统所能承受的容量,该怎么办呢?你可能会说,能够扩容啊。的确是能够,可是扩容也是有限度的。假设单机能承受10万的请求量,预计有1亿的请求量,你要扩容1000台服务器?!这会致使严重的浪费。
首先,上面提到了,秒杀是短时间活动,为了秒杀多部署1000台服务器,秒杀结束后这些服务器再销毁?既浪费硬件资源、又浪费人力资源。
其次,秒杀的商品数量其实并很少,可能秒杀赚的那点钱还不够付服务器和带宽的费用。真·花钱赚吆喝!
咱们该如何处理呢?
上面说了,秒杀的商品数量很少,也就是说,其实最后的真实成交量并不大。再进一步讲,不少的请求都是没用的。
其次,在秒杀前,买家会频繁的刷页面,这又额外增长了无用请求的数量。
咱们只要把这些无用的请求提早都过滤掉,最终到达服务端的请求就会少不少,也就不须要这么多的服务器了。这就是限流削峰。具体作法有不少:
服务端优化
上面的「请求排队」,能够作在web服务层,也能够在服务端处理,亦能够两处都处理。除了排队,服务端的优化的核心手段就是缓存,尽可能减小到数据库的数据访问,将热点数据缓存起来。
更极致的优化可能还涉及到:
另外还有扣库存逻辑处理:
上面说的秒杀系统的通常设计思路。下面来看看咱们是怎么钻漏洞的!
因为竞赛规定,只能使用Java和Spring来处理,对其余队来讲,这就致使了一个比较棘手的问题,IO优化。
Java支持两种IO,BIO和NIO。对于秒杀这种场景来讲,确定不能使用BIO。可是,若是使用Java原生NIO的话,须要处理不少问题,例如半包问题。以最终结果来看,选用原生NIO本身手写异步IO框架的,全都以失败了结。
那就只有另一条路,宁肯扣分,也要使用第三方框架,例如Netty。这就是赌,赌使用JavaNIO的队伍没法完成系统了。
而对咱们队来讲,咱们原先的劣势---前端、一会儿就变成了优点。由于前端有node啊(若是没有node,咱们妥妥的弃赛了)。当时node刚火起来,node天生就是个异步IO框架,竞赛规定里,可没有对前端技术作技术限制!这个漏洞,咱们怎么能不钻?!
最终竞赛评比时,我发现一个问题,其余队伍很看重公平!!!即先到先得原则,优先到达的请求,优先排队下单。这就致使,在秒杀结束前或请求被处理前,都须要等待,直到服务器处理后才有返回。
这明显增长了服务端的压力,这也是致使他们的吞吐量限制在4000左右的缘由。但不是根本缘由。
根本缘由是这样作就真的公平吗?!这就要看每一个人对公平的理解了!我认为这世上「没有绝对的公平,只有相对的公平」!
你在秒杀系统里排队,保证先到先得,这就是公平吗?
既然不能,我为何要在服务端保证公平呢?!
秒杀就是拼个运气,只要不暗箱操做,那就是公平的。因此咱们不保证先到达的请求就能先买到商品!客户哪知道他是否是先到的呢(虽然这样说,看起来不公平,但实际确实是这样)。因此咱们放弃了所谓的公平。
咱们使用了两个队列:
大体请求流程以下:
人员、技术、考量点的不一样都会影响架构设计。一个符合当前人员、技术以及适合考量点的架构,可能能获得意想不到的效果。