架构 之四 瞬时响应:网站的高性能架构

网站的性能是客观的指标,可以具体体现到响应时间、吞吐量等技术指标,同时也是主观的感受,不同的用户感受不同。

4.1 网站性能测试

不同视角下的网站性能有不同的标准,也有不同的手段。

4.1.1 不同视角下的网站性能

1. 用户视角的网站性能

从用户角度,网站性能就是用户在 浏览器上直观感受到的网站响应速度快慢,包括用户计算机和网站服务器通信的时间、网站服务器处理的时间、用户浏览器构造请求解析响应数据的时间,如下图所示:
用户视角网站性能
在实践中,使用一些前端架构优化手段,通过优化页面HTML式样、利用浏览器的并发和异步特性、调整浏览器的缓存策略、使用CDN服务、反向代理等手段,使浏览器尽快的展示用户的内容。

2. 开发人员的网站性能

开发人员关注的主要是应用程序本身以及相关子系统的性能,包括响应延迟、系统吞吐量、并发处理能力、系统稳定性等指标。主要使用的优化手段有使用缓存加速数据读取,使用集群提高系统吞吐能力,使用异步消息加快请求响应及实现削峰,使用代码优化改善程序性能。

3. 运维视角的网站性能

运维人员更关注基础设施性能和资源利用率,如网络运营商的宽带能力、服务器硬件配置、数据中心网络架构、服务器和网络带宽的资源利用率等。主要优化手段有建设优化骨干网、使用高性价比定制服务器、使用虚拟化技术优化资源利用率等。

4.1.2 性能测试指标

网站性能测试指标主要有响应时间、并发数、吞吐量、性能计数器等。

1. 响应时间

指执行一个操作所需要的时间,包括从发出请求开始到最后响应数据所需要的时间。实践中通常采用的是重复请求,得到单次请求的响应时间。

2. 并发数

指系统能够同时处理请求的数目,对于网站而言,并发数即并发用户数,同时提交请求的用户数目。
与网站并发用户数相对应的还有网站在线用户数、网站系统用户数,数量比关系为:网站系统用户数>>网站在线用户数>>网站并发用户数。
测试程序通过多线程模拟并发用户的办法来测试系统的并发处理能力,为了真实模拟用户行为,测试程序并不是启动多线程后不停的发送请求,而是在两个请求之间加入一个随机等待时间,称作思考时间。

3. 吞吐量

单位时间内系统处理的请求数目,体现系统的整体处理能力。
在系统并发数由小增大的过程中,系统吞吐量先是逐渐增加,达到一个极限后,随着并发数的增加反而下降,系统资源耗尽,吞吐量为0。
在这个过程中,响应时间先保持小幅上升,达到吞吐量极限后,快速上升,达到系统崩溃点后,系统失去响应。
网站性能优化的目的,除了改善用户体验的响应时间,还要提高系统吞吐量,最大限度利用服务器资源。

4. 性能计数器

指描述服务器或操作系统性能的一些数据指标,包括 System Load ,对象与线程数,内存使用,CPU使用,磁盘与网络I/O等指标。
System Load 即系统负载,指当前正在被CPU执行和等待被CPU执行的进程数目总和,是反应系统忙闲程度的重要指标。多核CPU情况下,完美情况是所有CPU都在使用,没有进程在等待处理,所以Load理想值是CPU数目。在Linux系统中使用 top 命令查看,该值是三个浮点数,表示最近1分钟、5分钟、15分钟的运行队列的平均进程数。

4.1.3 性能测试方法

性能测试是一个总称,具体可分为性能测试、负载测试、压力测试、稳定性测试。

1. 性能测试

以系统设计初期规划的性能指标为预期目标,对系统不断施加压力,验证系统在资源可接受范围内,是否能达到性能预期。

2. 负载测试

对系统不断增加并发请求以增加系统压力,直到系统的某项或多项性能指标达到安全临界值,这时对系统继续施加压力,系统的处理能力不升反降。

3. 压力测试

超过安全负载的情况下,对系统继续施加压力,直到系统崩溃或不能处理任何请求,以此获得系统最大压力承受能力。

4. 稳定性测试

被测试系统在特定硬件、软件、网络环境条件下,给系统加载一定业务能力,使系统运行一段较长时间,一检测系统是否稳定。

4.1.4 性能优化策略

如果性能测试不满足设计或业务需求,就需要找系统瓶颈,分而治之,逐步优化。

1. 性能分析

排查一个网站的性能瓶颈方法:检查请求处理的各个环节的日志,分析那个环节响应时间不合理,超过预期;然后检查监控数据,分析影响性能的主要因素使内存、磁盘、网络还是CPU,是代码问题还是设计不合理,或系统资源却是不足。

2. 性能优化

定位具体的原因后,就需要进行性能优化。根据网站分层架构,可分为Web前端性能优化、应用服务器性能优化、存储服务器性能优化等。


4.2 Web前端性能优化

一般来说,Web前端指网站业务逻辑之前的部分,包括浏览器加载,网站视图模型,图片服务、CDN服务等,主要优化手段有优化浏览器访问、使用反向代理、CDN等。

4.2.1 浏览器访问优化

1. 减少http请求

HTTP协议是无状态的应用层协议,意味着每一次HTTP请求都要建立通信链路,进行数据传输,而在服务器端,每个HTTP都需要启动独立的线程去处理,这些通信服务的开销都很昂贵,减少HTTP请求的数目可有效提高访问性能。
减少HTTP的主要手段有合并CSS、合并Javascript、合并图片。将浏览器一次访问的CSS、Javascript合并成一个文件,这样只需要一次请求。

2. 使用浏览器缓存

可以将网站的静态文件缓存在浏览器中,可以极好的改善性能。通过设置HTTP头中Cache-Control和Expires属性,可设置浏览器缓存。

3. 启用压缩

在服务器端对文件进行压缩,在浏览器端对文件解压缩,可有效减少通信传输的数据量。

4. css文件放在文件最上面、javascript放在页面最下面

浏览器在下载完全部的css之后才会对页面进行渲染,因此最好的做法是将css放在页面的最上面。

5. 减少cookie传输

一方面,Cookie包含在每次请求和响应中会严重影响数据传输;另一方面,对于一些静态资源的访问,发送Cookie没有意义,可以考虑静态资源使用独立域名。

4.2.2 CDN加速

CDN(Content Distribute Network,内容分发网络),本质是一个缓存,将数据缓存在离用户最近的地方,使用户以最快速度获取。
CDN能够缓存的一般是静态资源,如图片,文件等,但是这些文件访问频度很高,缓存在CDN可极大改善网页的打开速度。

4.2.3 反向代理

反向代理服务器位于网站机房一侧,代理网站Web服务器接收HTTP请求。
反向代理服务器有保护网站安全的作用,相当于在Web服务器和可能的网络攻击间建了一个屏障。
反向代理服务器也可以通过配置缓存功能加速Web请求。
反向代理服务器可以实现负载均衡功能,提供系统的总体处理能力。


4.3 应用服务器性能优化

应用服务器指处理网站业务的服务器,优化手段主要有缓存、集群、异步等。

4.3.1 分布式缓存

网站性能优化第一定律:有限考虑使用缓存优化性能。

1. 缓存的基本原理

缓存指将数据存储在相对较高访问速度的存储介质中,以供系统处理。一方面缓存访问速度快,可减少数据访问的时间;另一方面缓存还起到较少计算时间的作用。
缓存的本质是一个内存hash表,数据缓存以key,value的形式存储在内存hash表中。
缓存主要用来存放那些读写比很高、很少变化的数据。应用程序读取数据时,先从缓存中取数据,如果读取不到或已经失效,再访问数据库,并将数据写入缓存。
网站数据访问通常遵循二八定律,即80%的访问落在20%的数据上,因此利用hash表和内存的高速访问特性,将这20%数据缓存起来,可很好的改善系统性能,提高读取速度,降低存储访问压力。

2. 合理使用缓存

频繁修改的数据

一般来说,数据的读写比在2:1以上,缓存才有意义。实践中,这个读写比通常非常高,比如新浪微博的热门微博,混存后可能会被读取数百万次。

没有热点的访问

缓存使用内存作为存储,内存资源宝贵有限,只能将最新访问的数据缓存起来。如果应用系统访问没有热点,不遵循二八定律,那么缓存就没有意义,因为大部分的数据没有被再次访问就已经被挤出缓存了。

数据不一致与脏读

一般会对缓存的数据设置失效时间,超过失效时间,就要从数据重新加载。因此要容忍一段时间的不一致,但具体应用需慎重对待。还有一种策略再数据更新时立即更新缓存,不过会带来更多系统开销和事务一致性的问题。

缓存可用性

缓存是为提高数据读取性能的,缓存数据丢失或者缓存不可用不会影响应用程序的处理--可从数据库直接获取。但随着业务的发展,缓存会承担大部分数据访问的压力,当缓存服务器崩溃时,数据库会因为完全承受如此大的压力而宕机,进而网站不可用。称作缓存雪崩。
实践中,有的网站通过缓存热备等手段提高缓存的可用性。通过分布式缓存服务器集群,将缓存数据分布到集群多台服务器上可在一定程度上改善缓存的可用性。

缓存预热

缓存中存放的是热点数据,热点数据又是缓存系统利用LRU最近最久未用算法对不断访问的数据筛选淘汰出来的,这个过程需要花费较长时间。新启动的缓存系统如果没有任何缓存数据,在重建缓存数据的过程中,系统的性能和数据库的负载都不太好,最好的方法就是缓存预热。对于一些元数据可以在启动时加载数据库中全部数据到缓存进行预热。

缓存穿透

如果因为不恰当的业务,或恶意攻击持续高并发的请求某个不存在的数据,由于缓存没有该数据,所有请求都会落到数据库上,对数据库造成很大的压力,甚至奔溃。一个简单的对策就是将不存在的数据也缓存起来,value值为null。

3.分布式缓存架构

分布式缓存指缓存部署在多个服务器组成的集群中,以集群方式提供缓存服务,其架构方式有两种,一种是以 JBoss Cache 为代表的需要更新同步的分布式缓存,一种是以 Memcached 为代表的不互相通信的分布式缓存。
JBoss Cache 的分布式缓存在集群中所有服务器保存相同的数据,当某台服务器有缓存数据更新的时候,会通知集群中其它机器更新或清除缓存数据。JBoss Cache 通常将应用程序和缓存部署在同一台服务器上,应用程序可以从本地快速获取缓存数据,但是这种方式带来的问题是缓存的数量受限于大一服务器的内存空间,而且当集群规模较大的时候,缓存更新信息需要同步到集群所有机器,代价很大。所以很少在大型网站使用。
Memcached 采用集中式缓存集群处理,也成为互不通信分布架构方式。缓存与应用分离部署,缓存系统部署在一组专门的服务器上,应用程序通过一致性 Hash 等路由算法选择缓存服务器访问缓存数据,缓存服务器之间互不通信,具有很好的可伸缩性。

4. Memcached

Memcached 曾一度是分布式缓存的代名词,被大量网站使用。简单的设计,优异的性能,互不通信的服务器集群,海量数据可伸缩的架构令网站架构师趋之若鹜。

简单的通信协议

远程通信设计需要两方面的因素:一是通信协议,选择TCP、UDP或HTTP,一是通信序列化协议,数据通信的两端,必须使用彼此可识别的序列化方法才能使得通信完成,如JOSN、 XML。

丰富的客户端程序

Memcached 通信协议非常简单,只要支持该协议的客户端都可以和 Memcached 服务器通信。

高性能的网络通信

Memcached 服务器端通信模块基于 Libevent ,一个支持事件触发的网络通信程序。Libevent 的设计和实现有许多值得改善的地方,但它在稳定的长链接方面的表现正是 Memcached 需要的。

高效的内存管理

Memcached 使用了一个非常简单的办法--固定空间分配。Memcached 将内存空间分为一组 slab ,每个 slab 又包含一组 chunk ,同一组 slab 里每个 chunk 大小是固定的,拥有相同大小 chunkslab 被组织在一起,叫做 slab_class 。存储数据时根据数据的Size大小,找一个大于Size的最小 chunk 将数据写入。这种内存管理方式避免了内存碎片管理的问题,内存的分配和释放都是以 chunk 为单位的。Memcached 采用 LRU 算法释放最近最久未被访问的数据占用的空间,释放的 chunk 被标记为未用,等下一个合适大小数据的写入。

互不通信的服务器集群架构

正是由于集群内服务器互不通信使得集群可以做到几乎无限制的线性伸缩。

4.3.2 异步操作

使用消息队列将异步化,可改善网站的扩展性和性能。
在不使用消息队列的情况下,用户的请求数据直接写入数据库,在高并发的情况下,会对数据库造成压力,同时也使得响应延迟加剧。在使用消息队列后,用户的请求发送给消息队列后立即返回,再由消息队列消费者进程获取数据,异步写入数据库。因为消息队列服务器处理速度远快于数据库,因此用户的响应延迟可得到很大改善。
消息队列具有很好的削峰作用--即通过异步调用,将短时间高并发事务消息存储再消息队列中,从而削平高峰期的并发事务。

4.3.3 使用集群

在网站高并发的情况下,使用负载均衡技术为一个应用构建一个由多台服务器组成的服务器集群,将并发访问请求分发到多台服务器上处理,避免单一服务器因负载压力过大而响应缓慢,使用户请求具有更好的响应延迟特性。

4.3.4 代码优化

网站业务逻辑实现代码主要部署在应用服务器上,合理优化业务代码,可以很好的改善网站性能。

1. 多线程

多用户并发访问使网站的基本需求,CGI 编程时代,每个用户请求都会创建一个独立的系统进程去处理。由于线程比进程更轻量,更少占有系统资源,所以目前的 Web 应用服务器采用多线程方式响应并发用户请求,因此网站开发天然就是多线程开发。
从资源利用的角度看,使用多线程的原因:IO 阻塞与多 CPU 。当前线程进行 IO 处理的时候,会被阻塞释放 CPU 等待 IO 操作完成,由于 IO 操作通常都需要较长时间,这时 CPU 可以调度其它的线程进行处理。另一个原因是服务器有多个 CPU ,要想最大限度的使用这些 CPU, 必须启动多线程。
多线程一个需要注意的问题是线程安全问题,即多线程并发对某个数据资源进行修改,导致数据混乱。

解决线程安全注意几点

将对象设计为无状态对象(对象无成员变量,或者成员变量也是无状态对象);
使用局部对象;
并发访问资源时使用锁;

资源复用

系统运行时,尽量减少那些开销很大的系统资源的创建与销毁,比如数据库连接、网络通信连接、线程、复杂对象等。从编程角度看,资源复用主要有两种方式:单例对象池
虽然单例是GoF经典设计模式中较多诟病的一个模式,但目前Web开发中主要使用贫血模式,从Service到Dao都是使用无状态对象,无需重复创建,使用单例模式就自然而然了。
对象池模式通过复用对象实例,减少对象创建和资源消耗。对于数据库连接对象,每次创建连接,数据库服务器端都需要创建专门的资源以应对,因此频繁的创建关闭数据库连接,对数据库服务器而言是灾难性的,因此在实践中,应用程序的数据库连接基本都使用对象池。

数据结构

在不同的场景中合理使用数据结构,灵活组合各种数据结构改善数据读写和计算特性可极大优化程序的性能。

垃圾回收


4.4 存储性能优化

在网站应用中,海量的数据读写对磁盘访问造成巨大压力,虽然可以通过 Cache 缓解一部分数据读压力,但是磁盘仍然是系统最严重的瓶颈。

4.4.1 机械硬盘 VS 固态硬盘

机械硬 盘是目前最常用的一种硬盘,通过马达驱动磁头臂,带动磁头到指定的磁盘位置访问数据,由于每次访问的数据都要移动磁头臂,因此机械硬盘在数据连续访问和随机访问时,由于移动磁头臂的次数相差巨大,性能相差也非常大。
固态硬盘 又称作 SSDFlash 硬盘,这种硬盘数据机械装置,数据存储在可持久记忆的硅晶体上,因此可以像内存一样快速随机访问。
在网站应用中,大部分使应用访问数据搜是随机的,这种情况下固态硬盘具有更好的性能表现。

4.4.2 B+树 vs LSM树

由于传统的机械硬盘具有快速访问读写,慢速随机读写的访问特性,这个特性对磁盘存储结构和算法的选择影响很大。
为了改善数据访问特性,文件系统或数据库系统会对数据排序后存储,加快数据检索速度,这基于需要在不断更新,插入,删除后依然有序,传统关系数据库的做法是 B+树
B+树原理图
B+树 是一种专门针对磁盘存储而优化的N叉排序树,以树节点存储在磁盘中,从根开始查找所需数据所在的节点编号和磁盘位置,将其加载到内存中继续查找,直到查找到所需的数据。
但是由于每次磁盘访问都是随机的,所以传统机械硬盘在数据随机访问是性能较差,目前许多 NoSQL 产品将 LSM树 作为主要结构。
LSM树原理示意图
LSM树 可以看作是一个N阶合并树。数据写操作(包括插入、修改、删除)都在内存中进行,并且都会创建一个新纪录(修改会记录新的数据值,而删除会记录一个删除标志),这些数据在内存中仍然是一个排序树,当数据量超过设定的内存阈值后,会将这棵排序树与磁盘上最新的排序树合并。当这棵排序数的数据量也超过设定的阈值后,和磁盘上下一级的排序数合并。合并过程中,会用最新更新的数据覆盖旧的数据。
在需要进行读操作时,总是从内从中的排序数开始搜索,如果没有找到,就从磁盘上的排序数顺序查找。
LSM树 上进行一次数据更新不需要磁盘访问,在内存即可完成,速度远快于 B+树 。在数据访问写操作时,读操作集中在最近写入的数据上时,使用 LSM 可极大程度的减少磁盘的访问次数,加快访问速度。

4.4.3 RAID vs HDFS

RAID (廉价磁盘冗余阵列)技术主要是为了改善磁盘的访问延迟,增强磁盘的可用性和容错能力。目前服务器级别的计算机都支持插入多块磁盘,通过使用 *** RAID*** 技术,实现数据在多块磁盘上的并发读写和数据备份。
常用的 RAID 技术主要有以下几种:RAID 0RAID 1RAID 10RAID 5RAID 6
RAID技术原理图
假设服务器有 N 块磁盘。

RAID0

数据从内存缓冲区写入磁盘时,根据磁盘数量将数据分成 N 份,这些数据同时并发写入 N 块磁盘,使得数据整体写入速度是一块磁盘的 *** N*** 倍。读取时也一样,因此 RAID0 具有极快的读写速度,但是 RAID0 不做数据备份,N 块磁盘中只要有一块损坏,数据完整性就被破坏,所有磁盘的数据都会损坏。

RAID1

数据在写入磁盘时,将一份数据同时写入两块磁盘,这样任何一块磁盘损坏都不会导致数据丢失,插入一块新磁盘就可以通过复制数据的方式自动修复,具有极高的可靠性。

RAID10

结合 RAID0 和 *** RAID1*** 两种方案,将所有磁盘平均分成两份,数据同时在两份磁盘写入,相当于 RAID1 ,但是每一份磁盘里面的 N/2 块磁盘上,利用 RAID0 技术并发读写,既提高可靠性又改善性能,不过 RAID10 磁盘利用率低,有一半的磁盘用来备份数据。

RAID3

一般情况下,一台服务器不会出现同时损坏两块磁盘的情况,在只损坏一块磁盘的情况下,如果能利用其它磁盘的数据恢复损坏磁盘的数据,这样在保证可靠性和性能的同时,磁盘的利用率也得到大幅提升。
在数据写入磁盘的时候,将数据分成 N-1 份,并发写入 N-1 块磁盘,并在第 N 块磁盘记录校验数据,任何一块磁盘损坏,都可以利用其它 N-1 磁盘的数据修复。
但是在数据修改较多的场景中,修改任何磁盘数据都会导致第 N 块磁盘重写校验数据,频繁的写入的后果是第 N 块磁盘比其它磁盘容易损坏,需要频繁更换,所以 RAID3 很少在实践中使用。

RAID5

相比 RAID3,方案 RAID5 被更多的使用。
RAID5RAID3 很相似,但是校验数据不是写入第 N 磁盘,而是螺旋式的写入所有磁盘中,这样校验数据被平均的分到所有磁盘上,避免 RAID3 频繁写坏一块 磁盘的情况。

RAID6

如果数据需要很高的可靠性,在同时出现损坏两块磁盘的情况下,仍然需要恢复数据,这时使用 RAID6
RAID6RAID5 类似,但是数据只写入 N-2 块磁盘,并螺旋式的在两块磁盘写入校验信息。
RAID 技术可以通过硬件实现,比如专用的 RAID 卡或主板连接支持,也可以通过软件实现。

HDFS 以块(Block)为单位管理文件内容,一个文件被分成若干个 Block ,当应用程序写文件时,每写完一个 BlockHDFS 就将其自动复制到另外两台机器上,保证每个 Block 有三个副本。即使有两台服务器宕机,数据依然可以访问,相当于实现了 RAID1 的数据复制功能。
当对文件进行处理时,通过 MapReduce 计算任务框架,可以启动多个计算子任务(MapReduce Task),同时读取文件中的多个 Block,并发处理,相当于实现了 RAID0 并发访问功能。
HDFS架构原理图
HDFS 中有两种重要的服务器角色:NameNode (名字服务节点)和 DataNode (数据存储节点)。NameNode 在整个 HDFS 中只部署一个实例,提供元数据服务,相当于操作系统中的文件分配表(FAT),管理文件名 Block 的分配,维护整个文件系统的目录树结构。DataNode 则部署在 HDFS 集群中所有其它服务器上,提供真正的数据存储服务。
和操作系统一样,HDFS 对数据存储空间的管理以数据块(Block)为单位,HDFSDataNode 上的磁盘空间分成 N 个这样的块,供应用程序使用。
应用程序需要写文件时,首先访问 NameNode ,请求分配数据块,NameNode 根据管理的 DataNode 服务器的磁盘空间,按照一定的负载均衡策略,分配若干个数据块供应用程序使用。
当应用程序写完一个数据块时,HDFS 会将这个数据块再复制两份存储再其它 DataNode 服务器上,HDFS 默认同一份数据有三个副本,保证数据可靠性。因此在 HDFS 中,即使 DataNode 服务器有多块磁盘,也不需要使用 RAID 进行备份,而是在整个集群上进行复制,而且系统一旦发现某台服务器宕机,会自动利用其它服务器上的数据将这台服务器上存储的数据自动备份一份,从而获得更高的数据可靠性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值