《构建高性能web站点》笔记--应用程序篇

本文探讨了提高Web服务性能的方法,重点介绍了吞吐率的概念及其影响因素,并详细分析了客户端、反向代理、Web服务器及应用程序四个层面的缓存设计策略。

 

概念

吞吐率:一个衡量web服务性能的指标,表征每秒处理请求的次数。该指标受到3方面的因素影响:并发用户数、总请求数、请求资源的类型。有时在请求总数一定的情况下,并发用户越多,吞吐率反而越高;另外,请求一个几kb的文件和请求一个几m的文件,最终完成处理的时间显然是不一样的。因此,吞吐率是一个比较综合的指标,并不是指并发能力。

 

概览图

下图简单描述了用户浏览器,代理服务器,web服务器和应用程序服务器的典型关系,当然通常应用程序服务器同时包含在web服务器中,这里为了区分职责而分开画了。

image

 

缓存

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

 

客户端缓存

可以利用客户端浏览器的缓存机制,来减少浏览器对服务端的请求次数(当然在服务端进行图片等资源合并,并结合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标记可以明确的告知浏览器过期形式,浏览器会彻底减少请求的次数。

 

反向代理缓存

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

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

 

Web服务器缓存

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

  1. 动态程序会可能变得依赖于特定的web服务器
  2. 注意编写面向HTTP缓存友好的动态程序,会使你的动态程序更有生命力

web 服务器还具有缓存文件描述符(类似句柄)的能力,这样可以减少文件的open操作,同样是一种减少系统调用的措施,这对于一些小文件有些效果,因为文件越小花在open上的开销将越来越占有重要的比例。  

 

应用程序缓存

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

  • 动态脚本缓存:每次脚本解析都需要消耗一定的时间,为了加快这个速度,一些服务器端脚本语言都支持动态脚本的预编译,比如我们熟悉的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可以支持直接从文件内核缓冲区复制到网卡内核缓冲区。

此文档一共两部分,此下载链接为第1部分。 第1章 绪论 1.1 等待的真相 1.2 瓶颈在哪里 1.3 增加带宽 1.4 减少网页中的HTTP请求 1.5 加快服务器脚本计算速度 1.6 使用动态内容缓存 1.7 使用数据缓存 1.8 将动态内容静态化 1.9 更换Web服务器软件 1.1 页面组件分离 1.11 合理部署服务器 1.12 使用负载均衡 1.13 优化数据库 1.14 考虑可扩展性 1.15 减少视觉等待 第2章 数据的网络传输 2.1 分层网络模型 2.2 带宽 2.3 响应时间 2.4 互联互通 第3章 服务器并发处理能力 3.1 吞吐率 3.2 CPU并发计算 3.3 系统调用 3.4 内存分配 3.5 持久连接 3.6 I/O模型 3.7 服务器并发策略 第4章 动态内容缓存 4.1 重复的开销 4.2 缓存与速度 4.3 页面缓存 4.4 局部无缓存 4.5 静态化内容 第5章 动态脚本加速 5.1 opcode缓存 5.2 解释器扩展模块 5.3 脚本跟踪与分析 第6章 浏览器缓存 6.1 别忘了浏览器 6.2 缓存协商 6.3 彻底消灭请求 第7章 Web服务器缓存 7.1 URL映射 7.2 缓存响应内容 7.3 缓存文件描述符 第8章 反向代理缓存 8.1 传统代理 8.2 何为反向 8.3 在反向代理上创建缓存 8.4 小心穿过代理 8.5 流量分配 第9章 Web组件分离 9.1 备受争议的分离 9.2 因材施教 9.3 拥有不同的域名 9.4 浏览器并发数 9.5 发挥各自的潜力 第10章 分布式缓存 10.1 数据库的前端缓存区 10.2 使用memcached 10.3 读操作缓存 10.4 写操作缓存 10.5 监控状态 10.6 缓存扩展 第11章 数据库性能优化 11.1 友好的状态报告 11.2 正确使用索引 11.3 锁定与等待 11.4 事务性表的性能 11.5 使用查询缓存 11.6 临时表 11.7 线程池 11.8 反范式化设计 11.9 放弃关系型数据库 第12章 Web负载均衡 12.1 一些思考 12.2 HTTP重定向 12.3 DNS负载均衡 12.4 反向代理负载均衡 12.5 IP负载均衡 12.6 直接路由 12.7 IP隧道 12.8 考虑可用性 第13章 共享文件系统 13.1 网络共享 13.2 NFS 13.3 局限性 第14章 内容分发和同步 14.1 复制 14.2 SSH 14.3 WebDAV 14.4 rsync 14.5 Hash 14.6 分发还是同步 14.7 反向代理 第15章 分布式文件系统 15.1 文件系统 15.2 存储节点和追踪器 15.3 MogileFS 第16章 数据库扩展 16.1 复制和分离 16.2 垂直分区 16.3 水平分区 第17章 分布式计算 17.1 异步计算 17.2 并行计算 第18章 性能监控 18.1 实时监控 18.2 监控代理 18.3 系统监控 18.4 服务监控 参考文献 索引
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值