博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何设计一个秒杀系统 -- 架构原则
阅读量:3896 次
发布时间:2019-05-23

本文共 3020 字,大约阅读时间需要 10 分钟。

秒杀系统本质上就是一个满足大并发,高性能和高可用的分布式系统。

架构原则:4 要 1 不要

1、数据要尽量少

首先是指用户请求的数据能少就少。请求的数据包括上传给系统的数据和系统返回给用户的数据。(网页)

为什么?因为首先这些数据在网络上传输需要时间,其次不管是请求数还是返回数据都需要服务器做处理,而服务器在写网络时通常都要做压缩和字符编码,这些都非常消耗CPU,所以减少传输的数据量可以显著减少CPU的使用。例如,可以简化秒杀页面的大小,去掉不必要的页面装饰效果等。

其次,“数据要尽量少”还要求系统依赖的数据能少就少,包括系统完成某些业务逻辑需要读取和保存的数据,这些数据一般是和后台服务以及数据库打交道的。调用其他服务会涉及数据的序列化和反序列化,这也是CPU的一大杀手,同时也会增加延时。而且,数据库本身也会成为一个瓶颈,所以和数据库打交道越少越好,数据越简单,越小越好。

2、请求数要尽量少

用户请求的页面返回后,浏览器渲染这个页面还要包括其他的额外请求,比如说,这个页面依赖的CSS/JS,图片,以及Ajax请求等都定义为“额外请求”,这些额外请求应该尽量少。

因为浏览器每发出一个请求,多少都会有一些消耗,例如建立连接需要三次握手,有的时候有页面依赖或连接数限制,一些请求(JS)还要串行加载。另外不同请求的域名不一样的话,还涉及这些域名的DNS解析,可能会耗时更久。所以减少请求书可以显著减少以上这些因素导致的资源消耗。

例如,减少请求数最常用的一个实践就是合并 CSS 和 JavaScript 文件,把多个 JavaScript 文件合并成一个文件,在 URL 中用逗号隔开(https://g.xxx.com/tm/xx-b/4.0.94/mods/??module-preview/index.xtpl.js,module-jhs/index.xtpl.js,module-focus/index.xtpl.js)。这种方式在服务端仍然是单个文件各自存放,只是服务端会有一个组件解析这个 URL,然后动态把这些文件合并起来一起返回。

3、路径要尽量短

所谓“路径”,就是用户发出请求到返回数据这个过程中,需要经过的中间节点数。

通常,这些节点可以表示为一个系统或者一个新的Socket连接(比如代理服务器只是创建一个新的Socket连接来转发请求)。每经过一个节点,一般都会产生一个新的Socket连接。

然而,每增加一个连接都会增加新的不确定性。从概率统计上来说,加入一次请求经过5个节点,每个节点的可用性为99.9%的话,那么整个请求的可用性就是:99.9%的5次方,约等于99.5%。

所以缩短请求路径不仅可以增加可用性,同样可以有效提升性能(减少中间节点可以减少数据的序列化和反序列化),并减少延时(可以减少网络传输耗时)。

要缩短访问路径有一种办法,就是多个相互强依赖的应用合并部署在一起,把远程过程调用(RPC)变成JVM内部之间的方法调用。

4、依赖要尽量少

所谓依赖,指的是要完成一次用户请求必须依赖的系统或服务,指的是强依赖。

举个例子,比如说你要展示秒杀页面,而这个页面必须强依赖商品信息,用户信息,还有其他如优惠券,成交列表等这些对秒杀不是非要不可的信息(弱依赖),这些弱依赖在紧急情况下就可以去掉。

要减少依赖,我们可以给系统进行分级,比如0级系统,1级系统,2级系统,3级系统。如果0级系统是最重要的系统,那么0级系统强依赖的系统也同样是最重要的系统。

注意,0级系统要尽量减少对1级系统的强依赖,防止重要的系统被不重要的系统拖垮。例如支付系统是0级系统,而优惠券是1级的话,在极端情况下可以把优惠券降级,防止支付系统被优惠券这个1级系统给拖垮。

5、不要有单点

系统中的单点可以说是系统架构上的一个大忌,因为单点意味着没有备份,风险不可控,我们设计分布式系统最重要的原则就是“消除单点”。

那如何避免单点呢?我认为最关键的点是避免将服务的状态和机器绑定,即把服务无状态化,这样服务就可以在机器中随意移动。

如何把服务的状态和机器解耦呢?这里也有很多实现方式,例如把和机器相关的配置动态化,这些参数可以通过配置中心来动态推送,在服务启动时动态拉取下来,我们在这些配置中心设置一些规则来方便的改变这些映射关系。

应用无状态化是有效避免单点的一种方式,但是像存储服务本身很难无状态化,因为数据要存储在磁盘上,本身就要和机器绑定,那么这种场景一般要通过冗余多个备份的方式来解决单点问题。

架构是一种平衡的艺术,而最好的架构一旦脱离了它所适应的场景,一切都将是空谈。

不同场景下的不同架构案例:

如果快速搭建一个简单的秒杀系统,只需要把你的商品购买页面增加一个“定时上架”的功能,仅在秒杀开始时才让用户看到购买按钮,当商品的库存卖完了也就结束了。

但随请求量从1w/s达到10w/s的量级,这个简单的架构很快就遇到了瓶颈,因此需要做架构改造来提升性能:

  1. 把秒杀系统独立出来单独打造一个系统,这样可以有针对性地做优化,例如这个独立出来的系统就减少了店铺装修的功能,减少了页面的复杂度。
  2. 在系统部署上也独立做一个机器集群,这样秒杀的大流量就不会影响到正常的商品购买集群的机器负载。
  3. 将热点数据(库存数据)单独放到一个缓存系统中,以提高“读性能”。
  4. 增加秒杀答题,防止有秒杀器抢单。

在这里插入图片描述

然而这个架构支持不了超过100w/s的请求量,所以进一步升级系统:
5. 对页面进行彻底的动静分离,使得用户秒杀时不需要刷新整个页面,而只需要点击购买按钮,借此把页面刷新的数据降到最少。
6. 在服务端对秒杀商品进行本地缓存,不需要再调用依赖系统的后台服务获取数据,甚至不需要去公共的缓存集群中查询数据,这样不仅可以减少系统调用,而且能够避免压垮公共缓存集群。
7. 增加系统限流保护,防止最坏情况发生。

在这里插入图片描述

在这里,我们对页面进行了进一步的静态化,秒杀过程中不需要刷新整个页面,而只需要向服务端请求很少的动态数据。而且,最关键的详情和交易系统都增加了本地缓存,来提前缓存秒杀商品的信息,热点数据库也做了独立部署,等等。

一些疑问?

1、不同的QPS量级瓶颈是不一样的,10w的瓶颈可能在数据读取上,通常增加缓存就能解决。100w可能服务端的网络都会是瓶颈,所以要把大部分的静态数据放到CDN上甚至缓存在浏览器里。

2、本地缓存用什么实现好?通过什么方式向本地cache写数据?

本地cache一般就是用内存实现,用Java集合类型就可以了。
用订阅的方式,在初始化时加载到内存。

3、秒杀系统的及时性非常高,把库存写进cache,怎么及时更新?

有两种方法,一是定时更新取3秒,二是主动更新,数据库字段更新后发消息更新缓存,这个需要用到一个组件阿里叫metaq,就是数据库字段更新会产生一条消息。另外cache里库存不需要100%和数据库一致,这个在后面的文章也有介绍。

4、秒杀的时间怎么控制的,各个客户端,服务器之间如何精确同步?

以服务端的时间为准,服务端的时间同步需要依赖一个时间同步组件完成,如ntp。当然还是有一定延时,影响不大。

5、库存不会放在本地缓存localcache,localcache只放静态数据,如商品描述这类。

库存是放在独立的缓存系统里,如redis,库存是采用主动失效的方式来失效缓存。

转载地址:http://bwken.baihongyu.com/

你可能感兴趣的文章
Java中普通代码块,构造代码块,静态代码块区别及代码示例
查看>>
iOS 第4课 UILabel
查看>>
[已解决]junit.framework.AssertionFailedError: No tests found in
查看>>
“服务器端跳转”和“客户端跳转”的区别
查看>>
Datatables基本初始化——jQuery表格插件
查看>>
Servlet监听器——实现在线登录人数统计小例子
查看>>
Oracle笔记——简单查询语句 Oracle入门
查看>>
基于Hibernate和Struts2的用户管理系统小案例
查看>>
打开.class文件的方法
查看>>
基于windows平台Git+GitHub+Hexo搭建个人博客(一)
查看>>
基于windows平台Git+GitHub+Hexo搭建个人博客(二)
查看>>
Windows平台下SVN安装配置及使用
查看>>
python简便的编辑工具:jupyter notebook
查看>>
使用pip安装的时候出现 ModuleNotFoundError: No module named ‘pip‘
查看>>
Selenium自动化测试(八)之上传文件
查看>>
Selenium UI自动化(Java篇)
查看>>
使用Fiddler模拟弱网进行测试
查看>>
使用POI读取Excel测试用例
查看>>
记一次数据推送的异常解决端口解决
查看>>
linux、mysql、nginx、tomcat 性能参数优化
查看>>