靠谱 的软件外包伙伴

您的位置:首页 > 新闻动态 > 高性能网站系统开发架构案例解密

高性能网站系统开发架构案例解密

2016-04-11 19:42:58

服务器如何发送数据?

  1. 服务器程序将需要发送的数据写入该程序的内存空间中;

  2. 服务器程序通过操作系统的接口向内核发出系统调用;

  3. 系统内核将用户态内存空间中的数据复制到内核缓冲区中去,然后通知网卡过来取;此后CPU转而做其他处理;

  4. 网卡到CPU指定的内核缓冲区中将数据复制到网卡缓冲区中;

  5. 网卡将字节转换成二进制位,再以电信号的形式输出至网络。

注意:数据在计算机内部的复制是按照总线的宽度来复制的。比如在32位的操作系统中,数据每次都复制32位。

总线就像是一条32/64车道的马路,数据在计算机中是以0/1的形式存储,每次复制每条车道只能走一个0/1,因此每次只能同时复制32个0/1.

数据在网线中的速度

网络传输介质有光缆和铜缆,在光缆中电信号的传输速度为2.3x10^8m/s,在铜缆中传输速度为2.0x10^8m/s。

光的传播速度为3.0x10^8m/s,但由于光缆采用反射机制传播,并不是直射,因此电信号实际走的路程要比直线长很多,因此在光缆中的传播速度只有2.0x10^8m/s。

什么是带宽?

带宽的定义

带宽的定义:数据的发送速率。

带宽的单位

100Mbps = 100M bit per second

平时所说的100M带宽指的是100M比特每秒,

100Mbps = 12.5MBps

注意:我们平时所说的“100M”指的是100MB,而带宽的单位是Mb,而1MB = 8Mb。因此,运营商所说的“百兆宽带”其实是“12.5兆宽带”,呵呵。

什么影响了数据发送速度(带宽)?

  1. 数据的发送速度由接收方的接收速度决定。在数据链路层中,为了确保数据在接收过程中不发生丢失,因此接收方要告诉发送方目前发送速度是否合理。若接收方来不及收,就会告诉发送方,让它慢点发。因此,数据的发送速度(即带宽)由接收方的接收速度决定。

  2. 与传播介质的并行度有关。传输介质可以看成是多车道马路,数据由0/1组成,每股车道每次只能容纳一个0/1。因此,如果马路的车道增多,那么每次发送的0/1也就增多,从而提高了发送速度(即带宽).

运营商为什么要限制带宽?

我们的服务器会通过一个交换机连入互联网,互联网由无数个路由器和主机构成,路由器负责数据包的存储转发,将数据包根据目的地址途径一个个路由器,最终投递到目的主机中。

由于一个交换机往往有多个服务器接入,服务器们都会将需要发送的数据首先发给交换机,再由交换机发给路由器,这些数据先存储在路由器的缓存中,然后路由器根据先后顺序逐个转发。所以,如果服务器发送数据的速度过快,路由器缓存满了,那接下来的数据就会丢失,因此需要限制服务器向路由器发送数据的速度,即限制服务器的带宽。而这个限制由接入服务器的交换机完成。通过上文可知,交换机只要控制接收速度,就能限制服务器的发送速度。

什么是共享带宽?什么是独享带宽?

1.独享带宽

如果一个路由器的出口带宽为100Mbps,并且同一个广播域内有10台主机,交换机只要将每台主机的最大出口带宽限制为10Mbps,那么不管在任何情况下每台主机的最大出口带宽为10Mbps。这就是独享带宽。独享带宽不会受到同一个广播域内其他主机的影响,任何时候最大出口带宽均为10Mbps。

2.共享带宽

假设一个路由器的出口带宽仍为100Mbps,但运营商为了挣更多钱,使同一个广播域内有多于10个主机接入,那么每台主机的平均最大带宽就小于10Mbps,此时即使交换机仍然将每台主机的最大出口带宽限制为10Mbps,但当主机都有较大的网络通信时,就无法保证每台主机都有10Mbps的最大带宽,此时就会相互竞争带宽。

综上所述,独享10M带宽能保证服务器的最大出口带宽在任何情况下都为10Mbps,不会受到同一广播域内的其他主机影响;而共享10M带宽只能保证在同一广播域内的其他主机通信空闲时,才能达到最大10Mbps的出口带宽。

什么是响应时间?

响应时间是指从数据包的第一个0/1离开服务器开始,到最后一个0/1被客户端接收为止的这段时间。

响应时间 = 发送时间+传输时间+处理时间

  • 发送时间:从发送数据包的第一个0/1开始,到发完最后一个0/1为止的这段时间。

    发送时间=数据包比特数/带宽

  • 传输时间:数据在通信线路中的传输时间。

    传输时间=传输距离/传输速度

    (传输速度近似为2x10^8m/s)

  • 处理时间:数据在各个路由器中存储转发的时间。

    处理时间比较难以计算。

响应时间=(数据包比特数/带宽)+(传输距离/传输速度)+处理时间

下载速度=数据的字节数/响应时间

什么是吞吐率?

吞吐率:服务器单位时间内处理请求的个数。

单位:reqs/s

吞吐率用来衡量服务器处理请求的能力。

当请求非常少的时候吞吐率并不高,因为此时服务器的性能还没有体现出来。那么随着请求的不断增多,吞吐率会随之上升,但当并发请求数上升到某一个临界点时,吞吐率不升反降。那个临界点就是服务器吞吐率的最大值,也叫最大吞吐率。

若我们的网站有促销活动前,可以通过上述方法来估计服务器的最大吞吐率,从而能判断服务器能否顶住促销带来的压力。

什么是并发数?什么是并发用户数?

要搞清楚并发数和并发用户数的区别,首先需要了解HTTP协议。

HTTP协议是一种应用层协议,它本身是无连接的,也就是客户端与服务器每完成一次数据交互就需要断开连接,下次通信时重新建立连接。但是HTTP1.1中有一个keep-alive字段,它使得通信双方在完成一次通信后仍然保持一定时长的连接。若该时间内客户端又想与服务器通信,那么无需再创建新的连接,只需重用刚才的连接即可,这样能提高通信的效率,减少额外的开销。

  • 并发数:客户端向服务器请求的次数。不论是否延用已创建的连接,只要客户端向服务器提出请求,就算一个并发数。

  • 并发用户数:创建TCP连接的个数。如果一个浏览器延用了已创建的连接向服务器发送了10次请求,那么只算一个并发用户数。

注意:现在的浏览器支持多线程,可以同时与服务器建立多个TCP连接,因此一个用户可能会导致多个并发用户数。所以“并发用户数”和“用户数”不能完全画等号,这点需要注意!

平均请求等待时间 和 服务器平均请求处理时间

平均请求等待时间:用户从点击一个按钮,到新的页面加载完毕所需的时间。

服务器平均请求处理时间:服务器从等待队列中取出一个请求开始,到处理完该请求所需的时间。

综上所述:平均请求处理时间是站在用户角度,是用来衡量用户体验的好坏的指标。

而服务器平均请求处理时间是衡量服务器性能好坏的指标,其实就是吞吐率的倒数。

注意:平均请求等待时间 和 服务器平均请求处理时间不成正比关系!

平均请求等待时间=请求传输时间+请求等待时间+请求处理时间

服务器平均请求处理时间=请求处理时间

由此可知,在请求数很少的情况下,浏览器发来的请求无需等待,直接被服务器处理,那么请求等待时间和服务器请求处理时间成正比关系;但在请求异常多的时候,请求到来速度远远大于服务器处理请求的速度,那么很多请求将会在等待队列中挤压,此时即使服务器处理请求的能力很强(即服务器平均请求处理时间很短),但用户的等待时间依然很长,此时用户等待时间与服务器请求处理时间不成正比。

使用Apache Bench进行压力测试

我们使用Apache服务器的Apache Bench(简称ab)对网站进行压力测试。ab简单易用,关键可以直接在服务器本地发起测试,这样我们可以获取不包括传输时间的服务器处理时间。通过服务器处理时间就可以知道服务器的性能。

1. 压力测试命令

ab -n100 -c10 http://www.acmcoder.com/index.php1

  • -n100:总并发数

  • -c10:并发用户数

  • http://www.acmcoder.com/index.php:需要测试的页面

2. 测试结果解析

架构高性能网站秘笈

一个网站的URL可能有很多,每个URL对应的处理也不尽相同,某一个URL的测试结果并不具有代表性。因此,我们需要选择一系列有代表性的URL,将测试结果的加权平均数作为网站的综合性能。

动态内容缓存

什么是动态内容缓存?

浏览器向服务器发送请求后,服务器会根据浏览器的要求做相应的处理(如:数据库操作),然后将处理后的结果注入JSP页面生成HTML,最后将生成的HTML返回给浏览器显示。我们知道,数据库读取操作是非常耗时的,如果能将每次请求中的数据库处理时间去掉,那服务器的相应速度将会大幅提升。要实现这一点,我们就需要将常用的HTML页面事先生成好,当用户发出请求时,服务器只需从缓存中取出即可,无需再做数据库处理操作。

综上所述:事先生成HTML页面的技术称为动态内容缓存。

什么是“缓存命中率”?

缓存命中率 = 访问缓存的请求数/请求总数

缓存命中率是衡量缓存是否有效的重要指标。如果将所有需要访问的数据均缓存起来,那么缓存命中率是100%。但一半情况下,为了节约存储空间,只将访问频率较高的数据缓存起来,那么这样就会造成有一些请求能够通过缓存访问数据,而有一些请求需要查询数据库访问数据。

我们没有办法确保缓存的命中率100%,但我们需要使用一些算法确保缓冲命中率较高。也只有当缓冲命中率较高的情况下,缓冲才能发挥它真正的价值。

PS:缓存与缓冲的区别?

这里顺便提一下“缓存”和“缓冲”的区别。

  • 缓冲:缓冲是一块临时的存储空间,为了解决不同存储设备之间读写速率不匹配而产生的。

    比如:内存的读写速度远远大于硬盘的读写速度。当内存中的数据向硬盘中写入时,内存会先将数据写入内存缓冲区,再由内存缓冲区向硬盘写入数据。这样内存快速腾空之后就可以做其他事了,提高了效率。

  • 缓存:缓存是在一些为了避免重复计算,而被暂时性存储的数据。

综上所述:缓冲=缓冲区,它是指内存中的一片区域;而缓存指的是数据,一些味了避免重复计算而被临时性存储的数据。

它们都是为了提高处理效率而存在的,都采用了“牺牲空间换取时间”的思想。

缓存的三种存储方式

缓存可以存储在三种不同的地方:

  1. 存储在内存

  2. 存储在IO设备

  3. 存储在内存和IO设备上

  4. 存储在缓存服务器上

对于小型网站,缓存首选的存储位置就是内存。内存相对于IO设备有较快的访问速度,能大大缩短缓存的读取时间。但内存资源比较宝贵,如果没钱买大内存的服务器的话,那就只好考虑第二种办法,将缓存存在IO设备中。

对于一些小型网站,如果需要存储的缓存数据量比较大,而且买不起大内存的服务器的话,那存储在IO设备是不错的选择。但IO设备相对于内存来说有个致命的缺陷,那就是慢!因此,对于资金不充裕的小型网站来说,第三种方式最为合适。

如果既想获得内存的读写速度,又想拥有IO设备的巨大存储空间,那就可以选择两者结合的方式。

内存中开辟一块固定大小的缓冲区,用于存储使用频率较高的缓存数据,而将使用频率较低的缓存数据存储至IO设备。当缓冲区存满时,你需要使用LRU算法将最近最旧未使用的缓存从缓冲区中扔出去,而将最近一段时间IO设备中访问频率最高的缓存放进来,从而保证了当前缓冲区中数据的命中可能性是最大的。

最后,对于大型网站来说,可以使用专门的缓存服务器来存储缓存。这主要有两点好处:

  1. 节约服务器的内存资源,让服务器的内存作更多数据处理的工作,而把数据存储的工作交给缓存服务器;

  2. 缓存服务器具有更好的可扩展性。如果网站需要定期举办一些营销活动,为了应付急剧增加的并发量,缓存服务器是不二之选。

但是,由于Web服务器与缓存服务器采用TCP通信,而建立和释放TCP连接时间开销比较大,因此对于小型网站来说,直接将缓存存在内存无疑是种首选办法。

如何实现缓存?

这里就简单地介绍下基于内存的缓存机制,近期我会单独写一篇“基于内存+IO+LRU算法的缓存机制”的博客,敬请关注。

服务器在接收客户端发来的请求后,首先根据请求的URL判断内存中是否有对应的缓存;若有的话先判断该缓存是否过期,若没过期就直接返回缓存数据;若过期了,或者内存中根本就没有该请求的缓存的话,就调用业务逻辑层的相关函数,处理客户端发来的请求,生成客户端需要的数据。然后给生成的数据设置一个有效期,并存入缓存,供下次使用,最后将数据返回给用户。

静态化页面

之前我们介绍的缓存机制仍然需要服务器程序去判断存储空间中是否有缓存,而服务器程序判断的过程也需要时间,如果缓存很多的话,服务器程序需要找半天。那么我们能否把静态页面事先都生成好,让浏览器直接访问静态页面,而无需再通过服务器程序访问缓存。

要知道,浏览器直接访问静态页面的速度比通过服务器程序访问静态页面的速度要快很多很多很多!

要实现这样的想法,我们可以这么做:

  1. 服务器程序启动的时候将生成所有需要访问的HTML页面,存储至服务器外设中;

  2. 浏览器所有a标签中的href都填写这些静态HTML页面URL;

  3. 在服务器中开启一条定时线程,每隔一段时间检查动态数据发生修改的HTML页面,并生成新的HTML页面;

  4. 当数据库发生修改时,立即更新与该数据相关的HTML页面。

浏览器缓存

什么是浏览器缓存?

在《架构高性能网站秘笈(二)——动态内容缓存》中我们知道,当不同用户请求相同数据时,动态内容缓存能够避免服务器的重复计算,从而降低用户的等待时间。但如果是同一个用户请求相同的数据,即使服务器能避免重复计算,但仍需将重复的数据传递给浏览器。若使用浏览器缓存,则同一个用户请求相同的数据时,浏览器只需从自己本地读取,无需从服务器上获取。从而大大降低用户的等待时间,减少了服务器的压力,可谓一箭双雕!

浏览器是如何处理缓存的?

是否启用浏览器缓存是通过HTTP协议控制的。

浏览器在接收服务器传来的页面后,会将页面存入本地缓存。如果响应头中包含了页面的过期时间,那么当用户请求相同的页面时,浏览器会询问服务器是否可以使用缓存页面,若服务器同意使用缓存,则返回304状态码;若服务器不同意,则将新的页面返回给浏览器,并携带200状态码。

此外,如果响应头中未包含启用浏览器缓存属性的话,浏览器仍然会缓存当前页,但下次请求相同页面时浏览器会直接向服务器请求新的页面,而不会询问是否使用缓存页面。

如何实现浏览器缓存?

1. last-modified

如果服务器向浏览器返回的响应头中包含last-modified属性,那么浏览器会将该属性与页面一起存入本地缓存。

当用户请求相同的页面时,浏览器发送的请求头中会携带属性:

If-Modified-Since:XXXXX1

服务器会根据该值判断是否可以使用缓存页面,若可以使用缓存,则返回304状态码,若不可以使用缓存,则返回最新的页面,并携带200状态码。

注意:如果服务器的响应头中未包含last-modified属性,那么用户在请求相同页面时,浏览器中也不会包含If-Modified-Since:XXXXX属性,从而服务器也不会判断是否使用浏览器缓存,而是直接返回页面。

2. 采用ETag属性

ETag属性和Last-Modified属性类似。ETag属性值是一串字符串。

若服务器的响应头中包含了ETag属性,那么浏览器会将页面和ETage属性值一起缓存;当用户请求相同的页面时,浏览器会读区缓存的ETag值,并作为请求头的If-None-Match:”xxxxx”属性发送给服务器;服务器收到该属性后,判断是否允许浏览器使用缓存页面,若允许则返回304状态码,若不允许直接返回新的页面,并携带200状态码。

如何彻底实现浏览器缓存?

刚才的浏览器缓存还会涉及到浏览器与服务器的通信,因为浏览器需要向服务器询问是否使用本地缓存,而这些通信仍然需要消耗较多的用户等待时间。能否避免浏览器的这种询问呢?

1.采用Expires属性

Expires属性表示过期时间。

服务器只需在响应头中添加Expires属性,浏览器会将该属性与页面一起缓存。当用户再次请求相同的页面时,浏览器会将该页面的Expires与当前系统时间进行比较,判断是否过期;若尚未过期,则直接使用本地页面。

但是,如果用户的

  上一篇   [返回首页] [打印] [返回上页]   下一篇