网站负载能力和访问速度优化的常用技术总结
author:一佰互联 2019-05-02   click:168

组件分离
DNS负载均衡
不同的web内容分布到不同的服务器上,并划分子域,利用DNS将请求自然转移到不同的服务器上。主要可以分为两大内容:
1.动态内容,CPU、IO密集型
2.静态内容,IO密集型
通过在DNS中配置多个A记录,将请求转移到集群中不同的服务器,这有助于具有地域性问题的大型web站点上,DNS可以使用户就近访问相应的web服务器。像BIND这样的DNS服务软件提供丰富的调度策略。但是,如果集群中的主机出现故障的话,需要更新DNS缓存,这通常需要一定的时间。另外,客户端也可以通过设置host来绕开DNS调度。

跨域共享cookie:将cookie的范围扩大到父域。

HTTP重定向
通过使客户端重定向,来分散和转移请求压力,比如一些下载服务通常都有几个镜像服务器。

分布式缓存
无法使用页面级别缓存时,需要考虑直接缓存数据,比如使用memcached作为缓存。此时,需要考虑并发写memcached的问题。 另外,当memcached横向规模扩大,服务器数量增加时,需要一种对应算法,能够使应用程序知道应该链接哪个memcached服务器(比如,取模运算)。分布式缓存能够自动重建缓存,不必担心down机。

负载均衡
负载均衡就是将请求分散,这涉及到应当如何设计调度策略,以让集群发挥最大的性能。当集群中的主机能力相当时应当尽量平均调度,能力不均时应当能者多劳。随着问题的复杂,要时刻关注调度的性能,不要让调度成为性能瓶颈。

反向代理负载均衡
反向代理服务器工作在HTTP层,类似代理服务器,与普通的代理服务器不同的是,服务器在代理的后端,而不是客户端在代理的后端,这类似于NAT,只是NAT工作在网络层。同样是负载均衡,反向代理服务器强调“转发”而不是“转移”,因为它不仅要转发客户端的请求,还要转发服务端的响应。可以用作反向代理服务器的软件有Nginx、lighttp、Apache,另外目前也有一些专业的代理转发设备能够工作在应用层,例如A10。

使用代理转发要注意以下问题:

由于反向代理的转发特性,使得代理本身很可能成为性能瓶颈。一般对于CPU密集型请求,使用代理比较合适,如果是IO密集型的话,这种集群方式很可能无法发挥最大性能
在代理上要开启健康检查,及时发现集群中的故障机,从而调整转发策略,这通常比DNS方式实时性更好
黏滞会话:对于启动session保存用户信息,或者后端服务器使用动态内容缓存的应用,必须将用户在一段会话中的的请求保持在同一台服务器上。代理服务器一般支持类似的配置。然而,尽量不要使应用过于本地化,比如可以使用cookie保存用户数据,或者分布式Session或分布式缓存。
IP负载均衡
字面上看,便是利用网络层进行请求转发,类似NAT网关。然而,使用网关转发在带宽上可能出现瓶颈,因为出口只有一个,所以出口的带宽要求较高。Linux中的Netfilter模块可以通过iptables的配置。比如:对外网端口8001的请求转发给内网某台服务器,而对外网端口8002的请求转发给内网另一台服务器。这种方式简单易行,但是无法对调度做太多配置。LVS-NAT同样是Linux中的在网络层进行转发的方式,与Netfilter不同,它支持一些动态调度算法,比如最小链接、带权重的最小链接、最短期望延迟等。

直接路由
直接路由是通过调度器修改数据包的目的MAC地址,转发请求数据包,但是响应数据包可以直接发送给外网的方式。这样做显而易见的好处就是无需担心网关瓶颈,但是实际的服务器和调度服务器都需要链接在WAN交换机上,并且拥有独立的外网IP地址。

这种方式的工作原理略微复杂:

首先每台服务器都需要设置一个IP别名,这个IP别名是面向客户端的一个虚拟IP,只有代理服务器对这个IP别名的ARP请求做出响应,这样客户端发给这个IP的请求包首先会到代理服务器。然后代理服务器将这个请求包的目的MAC地址填写为实际服务器的MAC地址(通过某种调度算法决定目的服务器),由于目标服务器也具有这个IP别名,因此,转发过来的数据包能够被实际的服务器接收并处理。最后由于数据包的源IP地址还是客户端请求的IP地址,因此,实际的服务器将通过交换机直接将响应包转发给客户端而无需通过代理服务器。

Linux下可以通过LVS-DR实现直接路由方式

IP隧道
IP隧道的意思是,调度器将原始的IP数据包封装在新的IP数据包中,以实现调度,实际的服务器可以将响应数据包直接转发给用户端。

共享文件系统
对于一些简单的提供文件下载的服务(包括html中静态资源等),自然要考虑利用集群来减压,但是如何使这些资源在集群中的主机上同步呢。

NFS
一种方案是让这些主机从同一个地方取数据。比如采用NFS(Network File System),基于PRC。这种方式简单易行,但是由于NFS服务器本身的磁盘吞吐率,或者并发处理能力以及带宽等问题,往往很有局限性。

冗余分发
另一个方案就是在主机上冗余存储资源,这样主机无需访问共享文件系统,只需读取本地磁盘上的资源即可。但是这也带来了一个同步的问题,如何同步这些数据呢:

主动分发式,还分为单级分发和多级分发,分发可以借助SCP、SFTP、HTTP扩展协议WebDAV

单级分发:通过一次分发,就达到目的,这样的方案简单易行,但是性能瓶颈会出现在磁盘压力和网络带宽,难以扩展
多级分发:通过多次分发,才达到目的地,这样的方案能够分散磁盘压力和网络带宽压力,而且容易扩展,坏处是成本高
被动同步式容易理解,可以使用rsync,rsync同步时是根据最后更新时间进行判定是否需要同步的条件的,因此,如果一个文件夹中有的文件数量太多的话,rsync扫描的时间就很长了,可以通过给文件夹设置最后更新时间,并合理的规划文件目录,来加快rsync的扫描时间。即使不使用rsync,自己开发同步程序也可以借助这样的思想来提升性能。

分布式文件系统
分布式文件系统工作在用户进程层面上,它是一个管理文件的平台,内部维护冗余,检索,追踪、调度等工作,通常包含一个物理层面的组织结构和逻辑层面的组织结构。物理层面的组织结构由分布式文件系统自行维护,逻辑层面的组织结构面向用户。其中“追踪器”起到了关键的作用。

MogileFS就是一个开源分布式文件系统,用Perl编写,包含追踪器、存储节点、管理工具,它使用MySQL分布式文件系统的所有信息、使用WebDAV实现文件复制。其他著名的还有Hadoop。

每个文件由一个key定义,需要读取文件时,指定一个key,追踪器会返回一个实际的路径,在访问这个地址即可获得文件。甚至可以将这个key对应的path用分布式缓存缓存起来,这样可以减少追踪器的查询开销,但这样也会失去分布式文件系统的调度策略的优越性。另外,可以利用支持reproxy的反向代理服务器(比如:Perlbal)让路径重定向的工作由反向代理服务器完成。

数据库扩展
1.主从复制,读写分离
这种方式是指利用数据库的复制或镜像功能,同时在多台数据库上保存相同的数据,并且将读操作和写操作分开,写操作集中在一台主数据库上,读操作集中在多台从数据库上,对于读取比写更多的站点适合使用这种方式。如果不想在应用程序层面维护这种分离映射,那么可以使用数据库反向代理来自动完成对读写的分离。

2.垂直分区
对于不需要进行联合查询的数据表可以分散到不同的数据库服务器上,这称为垂直分区;当然每个分区自身也可以使用读写分离。

3.水平分区
将同一个表的记录拆分到不同的表甚至是服务器上,称为水平分区,这往往需要一个稳定的算法来保证读取时能正确从不同的服务器上取得数据,比如简单的对ID取模、范围划分、亦或者是保存映射关系。 也可以使用类似代理的产品spock。

缓存
构建高性能web站点时,抛开基础架构(数据库分区的问题也包括在基础架构中了),在应用程序、编码层面主要要考虑的问题就是缓存的设计,合理的缓存设计可以使提供动态网页服务的网站性能大幅度提高。当然,在架构阶段设计缓存解决方案,绝非简单的技术问题,需要从业务出发,再结合各种技术。下面按照一次HTTP请求的顺序,对每个环节的缓存设计从技术角度进行讨论。

1.客户端缓存
可以利用客户端浏览器的缓存机制,来减少浏览器对服务端的请求次数(当然在服务端进行图片等资源合并,并结合css图片定位技术,也可以减少HTTP请求),利用好HTTP的缓存协商,可以设计出灵活的客户端缓存方案。在HTTP头中下面的内容与缓存协商有关:

Last-Modified:动态页面通过主动推送该值,暗示浏览器在下次请求同一个url的时候,优先使用If-Modified-Since值与服务端进行缓存协商,如果缓存没有过期,那么服务端可以不用重新计算动态网页,通过返回304通知浏览器。网站的静态资源往往使用这种方法。但是该方法有一个缺点:有时,文件的最后更改时间虽然改了,但是内容却没有变,这样无法充分发挥浏览器缓存的能力。

ETag:Web服务器为每个url生成一个散列值,增加在HTTP头的ETag标记中,浏览器会优先使用If-None-Match加上这个散列值来协商缓存过期。通过对静态文件的内容进行md5变换,可以生成散列值,这样可以弥补Last-Modified的不足。但是随之带来的是服务端md5变换的计算开销。

Expires:上述两种方式,虽然可以使服务端多少避免了反复的动态网页解析和计算,但浏览器还是必须通过HTTP请求来进行协商,并没有真正意义上减少请求的次数。通过在HTTP头中添加Expires标记可以明确的告知浏览器过期形式,浏览器会彻底减少请求的次数。

2.反向代理缓存
在web服务器前端,还有反向代理服务器缓存。反向代理服务器本质上就是代理服务器,只是将外网的请求转发给内网的web服务器处理,他们都工作在应用层,能够理解HTTP协议。正向代理服务器具有HTTP缓存、HTTP过滤等功能,反向代理服务器同样具有HTTP缓存的能力,而且还具备一定程度上的安全性。一切HTTP友好的动态程序同样能够很好的在反向代理服务器上实现缓存。重量级的squid、轻量级的varnish、甚至是Nginx这样的web服务器软件,都可以胜任反向代理服务。

上述的代理服务器软件产品,通过各种配置可以缓存基于HTTP协议的web响应。

3.Web服务器缓存
Web服务器有可能支持基于url的缓存(基于key-value对),这类似反向代理缓存。缓存通常可以通过配置存储在内存或磁盘上,在缓存有效期的问题上,通常是基于HTTP协议中的头部信息判断。但是使用这样的机制需要注意:

动态程序会可能变得依赖于特定的web服务器 注意编写面向HTTP缓存友好的动态程序,会使你的动态程序更有生命力 web 服务器还具有缓存文件描述符(类似句柄)的能力,这样可以减少文件的open操作,同样是一种减少系统调用的措施,这对于一些小文件有些效果,因为文件越小花在open上的开销将越来越占有重要的比例。

4.应用程序缓存
应用程序本身可以对动态内容进行缓存,这可以体现在三个层面:

动态脚本缓存:每次脚本解析都需要消耗一定的时间,为了加快这个速度,一些服务器端脚本语言都支持动态脚本的预编译,比如我们熟悉的ASP.NET、JSP都有所谓的中间语言,解析这些中间语言会快很多。
动态脚本框架支持的缓存:一些动态脚本框架支持缓存,同样在选型的时候要关注一下这些框架的缓存机制和原理,比如缓存如何存放,过期如何设置,是否支持局部缓存等。
应用程序自身实现缓存:应用程序根据特定的业务需求独立设计缓存。
在缓存设计的具体技术上有以下几点:

缓存在内存,这种方式的优点就是减少了磁盘的读写,但是内存有限,单机不易扩展。
缓存在分布式缓存,这种方式的优点是减少了磁盘读写,并提高了可靠性和扩展性,但是由于需要网络IO,性能稍逊于单机缓存。
局部页面缓存,对于一些页面无法完整缓存的,可以考虑局部缓存。
静态化,将网站静态化可以极大的提高性能,因为用户的请求不需要动态脚本处理。


服务器系统能力的制约因素:
这部分内容对于所有的服务器(无论是代理服务器、web服务器还是其他),都具有普遍适用的意义。

多进程、多线程的选择和调度:进程切换和线程切换都需要一定的系统开销,通常使用多线程模型的web服务器软件比使用多进程,具备更优的性能。
系统调用:一些需要从用户模式切换到内核模式的函数调用可以称为系统调用,比如:打开文件。系统调用会有一定程度上的开销,减少系统调用是可以加快处理速度的程序设计细节。
TCP链接保持:可以通过保持TCP链接来减少服务端和客户端之间的创建和关闭TCP链接的操作。HTTP中的Connection:Keep-Alive就有这样的功能
IO模型:由于CPU的速度远远比IO快,IO延迟往往成为性能瓶颈,因此,IO模型十分重要。
各种IO模型:

PIO:CPU直接干预磁盘和内存的数据交互,即无论是数据从内存到磁盘还是磁盘到内存都要经过CPU寄存器。这样的模型,可想而知,CPU有很多时间都需要等待慢速设备。

DMA(Direct Memory Access):CPU通过向DMA控制器发送指令来控制处理数据,数据处理完之后通知CPU,这可以很大程度上释放CPU资源。

同步阻塞I/O:对于进程来说,一些系统调用为了同步IO,会不同程度上阻塞进程,比如accept、send、read等。

同步非阻塞I/O:对于进程来说,一些系统调用可以在调用完之后立即返回,告知进程IO是否就绪,避免阻塞进程。

多路I/O就绪通知:对于同步非阻塞I/O的方式,进程仍然需要轮询文件描述符(句柄)来得知哪些IO就绪了,而多路I/O就绪通知将这个过程改成回调通知。

内存映射:将文件与内存的某块地址空间相映射,这样可以想写内存一样写文件。当然这种方式本质上跟写文件没有什么区别。

直接I/O:在用户进程地址空间和磁盘中间通常都会有操作系统管辖的内核缓冲区,当写入文件时,一般是写入这个缓冲区,然后由一些延迟策略来写入磁盘。这样做可以提高写效率。但是对于诸如数据库这样的应用来说,往往希望自己管理读写缓存,避免内核缓冲区的无畏内存浪费。Linux的open函数支持O_DIRECT参数来进行直接IO。

sendfile:如果web服务器想发送一个文件,将会经历如下过程:打开文件,从磁盘中读取文件内容(这通常涉及到内核缓冲区数据复制到用户进程),然后进程通过socket发送文件内容(这通常设计到用户进程数据复制到网卡内核缓冲区),可以看到重复的数据复制是可以避免的。sendfile可以支持直接从文件内核缓冲区复制到网卡内核缓冲区。