在满足相同用户业务、功能需求外,很多用户或许会选择“最好”的服务提供商,可见在一个提供互联服务的web网站,用户体验、网站性能是多么的重要。本文结合自身的性能优化实践经历,做下简要的总结,当然性能优化的范围很广,本文未能提供过于明细的介绍某些优化如何实现以及具体的优化方案,仅做简要介绍,提供简要思路供各位参考;另外当然性能优化在不同类型的网站、应用中关键点有所不同,以下只针对WEB应用型站点做简要描述。
§、性能相关
性能的范畴可以放大到所有的计算机软硬件、虚拟资源中,即所有的硬件基础设施、基础网络以及具有一定可执行能力的软件都有可能存在着性能的因素,因此考虑到一个应用、一套服务的性能问题时也是如此。从Web应用性能优化的角度来说,一般会考虑到以下几个方面:
一、硬件
毋庸置疑,硬件为应用支撑,作为业务实施的基础,应尽可能提升其吞吐、Process效率与承载,当然也要视各业务自身实际情况,来决定是否进行必要的提升。
二、网络
网络环境影响各应用层的通信与稳定,影响请求响应与数据交换的效率与质量。一般来说,需要考虑选择稳定、通信良好的机房环境。
三、架构
从高层架构的角度,性能优化一般会考虑到:负载均衡、统一Cache、CDN、双通网络等等,都是可以考虑作为改善性能的方案。对其中每一种方案细分,则可以有很多分支&实现方式;举个简单的例子,在做数据库层面的负载均衡时,我们可将所有产品作为一体,统一完成数据处理的负载与同步机制,另外也可以将产品进行细分,按产品类型来做负载,即每一类或每几类产品作为一个负载组……,当然结合业务、应用的实际情况作出取舍。
四、软件
抛开前面所说的几大类相关因素外,余下大部分工作则是应用层面的tuning,大致可分以下几个方面:
1、客户端
资源加载顺序:HTML、DOM的Render,JS、Css、Image等等资源文件的加载顺序,加载顺序的不当可能会直接影响到页面感观,比如出现短暂空白、短暂停留以及不同引擎的浏览器出现不同程度的兼容性等问题。
资源下载量:在保证必需资源的情况下应尽量减少下载量,eg: 单个资源文件的下载量不可过大,应通过压缩和缓存的方式减少下载。
连接请求数:请求连接数往往是Web请求的一个瓶颈,大家可能往往会看到,访问一个站点出现连接数过多或系统过于繁忙的提示,铁道部的站点就是一个很好的事例哈,当然不能说它就做的不好;究其原因,不同的浏览器在并发连接请求数上皆有不同的上限,且服务器端对请求连接数也有一定程度的上限承载。避免连接数过多的有效方法是适当合并资源文件,利用缓存机制。诸如:背景图片合并、资源合并等
脚本执行效率:废话不多说,脚本执行的效率同样影响到页面的展现与响应,这与脚本的写法、实现密切相关。可以适当的结合异步处理方式可“改善”脚本的线性响应方式。
页面布局:页面布局与资源顺序有些相关,涉及到如何组织HTML,决定哪些元素预先加载,决定页面呈现的块效果。HTML元素以及布局的不合理也会增加解析器的解析时间。
其他:
(1)优化HTML元素、代码脚本、实现与结构(大文件拆分)
(2)页面脚本、样式放置位置
(3)不用大的table、div容器(全部加载)
(4)启用客户端缓存
(5)参考YSlow、Firebug
(6)img script -> *.js
2、服务端
处理后台事务,抛开架构层面的因素,性能瓶颈一般在于代码的执行效率,以及事务处理的效率(尽可能提高并发和吞吐);关于代码执行效率,也有很多基础参考书籍,eg: Effective C#等,另外有很多技巧性的问题也起到了关键的作用,例如静态变量实例的缓存、异步处理模型,循环处理改为批处理等等,都是需要在实践中多多的总结。
其他:
(1)灵活的应用缓存策略(远程、分发、数据缓存、页面缓存……)
(2)结合静态数据、配置文件(不必要DB操作的尽可通过静态数据文件实现)
(3)ViewState(不需要用的页面、控件禁用掉)
(4)页面(尽可能手动压缩、自动压缩页面文件、元素大小,JS、CSS、图片、HTML代码等,另尽可采用DIV + CSS)
(5)数据分页获取(根据必要首页可做缓存)
(6)少用服务期端的控件
3、数据库
关于数据库优化的内容非常多,请参考相关资料。
一般来说,主要考虑到SQL语句的执行效率、数据分区、索引等信息,以及负载、故障集群。
§、应用案例:某充值、支付网站首页
提供用户充值、支付、转账、账户管理等多类型业务的综合平台,并提供对外充值、支付服务接口。
首页特征:服务产品展示与引导、公告信息、公共导航菜单等,另外含有动态内容,每用户登录后,显示内容不同。性能上要求保证访问速度在XXms内。
§、优化思路
1、 缓存
涉及到动态和静态内容的结合,用户的登录、账户信息等因素,不便于对整个页面做共享缓存、硬件缓存处理,所以页面采用局部缓存来处理。当然如果所处理的事务不多,也可通过AJAX的方式来解决。另外可利用304未提交修改的机制,减少服务请求。
2、 资源延后加载
CSS、JS放置页面尾部加载,也可考虑动态的方式来加载CSS、JS。
对于Iframe类资源,可以考虑延后设置src,原因不解释。
3、 减少元素输出
比如禁用ViewState、动态批处理元素属性、事件,减少HTML元素输出等。
4、 页面压缩
页面压缩可以在服务器端由IIS或网络访问控制层处理,也可以在应用站点中处理。
有关此信息网上有很多解决方案可供参考。通过应用站点的压缩可以控制压缩不同资源的灵活度较高,但是服务器端处理来说性能相对较低一些。
如果你无法应用这类压缩方式,也可尽可能压缩页面大小(减少空格、减少元素输出)。
5、 资源压缩
资源压缩一般考虑到JS、CSS,对JS的压缩可以采用JSA(推荐)、YUI Compressor压缩工具,对CSS的压缩网络上也有不少的工具。此类压缩在应用部署上有一定的繁琐性,但如部署时有辅助的解决方案,效果较佳。关于压缩后增加了CPU的处理复旦,个人觉得可以忽略不计。
6、 请求连接数
(1) 考虑将JS、CSS资源进行合并,再进行压缩,这样大大的减少了资源数量,提高了压缩的比率。
(2) 合并背景图片、合并资源图片
(3) 可适当考虑将公用部分或需异步处理的内容采用IFRAME的方式来嵌入。
7、 静态化
静态化、配置化站点都会使应用站点更灵活定制, 我说的静态化不是类似后台把页面生成后输出为HTML,eg: 原本可以通过服务端交互展现产品列表&信息,但也可以通过静态化为客户端数据后,异步来处理。如果做不到全页面的静态化,那也可以对页面局部做静态化处理。
8、 其他
(1) 通过增加不同类型静态资源的静态服务器可以优先考虑,方便统一策略的控制与应用。
(2) 很多时候可以考虑DIV+CSS的布局方式,层次清晰且便于控制,
(3) 应避免类似找不到文件或者空src的情形
(4) 增加客户端Tracker,便于记录操作时长、错误日志等行为信息。
9、 测试、数据收集
对站点的测试,一般可以采用第三方产品,例如基调网络(eg:
www.networkbench.com),做到在不同点数据准确的监测与采集(详细的客户端信息、页面执行情况,出错概率、用户网络等等), 便于进一步采取相应的优化措施。
§、总结
本文介绍的多是基本概念与基础切入点,企业应用产品开发多数时候也是如此,正确的思想与切入点,才有可行的实践方案。本文可能描述的不是很全面(只能点到为止),在实际项目中远不止这些,具体应用时再做实际分析与研究,欢迎各位补充。时间仓促,措辞不当之处。
1、相关Tools
YSlow、FireBug、JSA脚本压缩工具、Yahoo脚本压缩工具,
2、推荐相关书籍
《构建高性能可扩展ASP.NET网站》Richard Kiessig著, 余昭辉译
《构建高性能Web站点》 郭欣著
§、引用参考
提高网站的呈现速度其实重点在页面上,改进方法如下:
一、我们需要解决的麻烦
* 减少HTTP请求数. 减少HTTP请求数有什么好处
o 降低服务器跟客户端的建立和消除HTTP请求和响应Header的开销
o 减少服务器为HTTP连接的进程和线程的开销,如果可能,还会包括GZIP压缩的CPU开销.
* 减小被请求文件大小, 减少请求数据占用的网络带宽.
* 让用户更快的看到想要的结果.
* 提高客户端渲染速度.
* 让浏览器同时能请求更多的数据.
* 提高服务器相应速度.
* 通过版本化控制客户端Cache.
二、如何解决我们的麻烦
A.如何减少HTTP请求数
1. 合并JS文件跟CSS文件。
2. 合并框架图片以及相对变动较少的图片成一张,通过CSS背景切割来完成渲染,比如:加速图片显示。
3. 合理使用本地Cache来缓存JS/CSS/IMAGE。
4. 合理使用UserData缓存JS文件,对于FF用户可以单独请求服务器,这样能解决80%用户的问题.代码可以蓉儿(meizz)的js framework1(标注1)。
5. 把JS跟CSS合并成一个文件
B.减小被请求文件大小,减少请求数据占用的网络带宽
1. 压缩JS体积:删除JS中空白换行,注释,混淆把长变量换成短变量;
2. 压缩CSS体积:删除CSS注释、写法尽量用简写;
3. 使用(X)HTML+CSS方式搭建网站结构,提高CSS重用性,来减少(X)HTML文件大小;
4. 使用服务器端GZIP压缩JS/ CSS文件,缩小传输文件大小。附注:Apache1跟Apache2的GZIP的效率跟方式不一样的,根据需要自行选择。
嗷嗷补充说明:压缩、合并JS和CSS都由程序处理。而不是自己手动去缩删,不然不利于后期维护。
C.让用户更快的看到想要的结果
用户对于一个站点的白页的忍受时间根据统计是8-12秒。白页的产生可能由于各种原因引起,我们能做的就是怎么让用户能变的稍微能等待更久。
1. 方案1:多做一个引导页,让用户体会其中的变化
案例:mail.aol.com中的loading引导页
2. 方案2:优先载入页面结构以及结构图片,后一步载入当前页面数据,再后一步载入Iframe,Flash等数据.让用户尽早的看到被打开页面的希望.
D.提高客户端渲染速度
这个问题就比较泛泛了,影响客户端的渲染速度有多方面的,主要目的都是提高程序方面的效率.
1. 对于大索引的结构,尽可能的少用索引访问,能用访问兄弟节点的方式尽可能用访问兄弟节点的方式.
2. 字符串拼接尽可能用数组方式
3. 大规模添加节点数据,请不要使用appendChild方式,尽量使用类似innerHTML的insertAdjacentHTML方式,FF下需修正(标注1)
E.让浏览器同时能请求更多的数据.
浏览器默认只是支持单域名同时有两个HTTP请求,使用多域名将能把请求数提高,在网络条件优良的情况下,能更快的下载数据,呈现结果.
F.提高服务器相应速度
对于需快速响应的文件,把其放入快速响应的服务器,应该是不错的方案,优化方案请系统储备组提供.
G.通过版本化控制客户端Cache
通常js/css这类文件改动比较频繁,但是为了加载速度变快,我们有可能需要设定这类文件的过期时间为几天后,这样我们碰到的问题就是,如何及时更新这些在cache的文件?
通过一个简单的配置,通过修改JS的版本来及时告诉浏览器,这些文件必须重新请求了,不要继续使用浏览器cache中的数据. 方案有好几个:
1. 手动改这些js的文件名
2. 手动改这些js的路径
3. 通过URL Rewrite方式来改重定位js路径
4. 通过一个在高响应服务器上的一个js配置告知页面,这个页面该链接哪些JS文件
5. 大版本不变,小版本不断追加,等一定时间后,统一更新,高效利用cache
标注
1. meizz的js framework还没出正式版,有兴趣在CSDN的页面翻一下
2. Firefox修正方式
function addHTML(oParentNode, sHTML) {
if(window.addEventListener) {// for MOZ
var oRange = oParentNode.ownerDocument.createRange();
oRange.setStartBefore(oParentNode);
var oFrag = oRange.createContextualFragment(sHTML);
oParentNode.appendChild(oFrag);
}
else {// for IE5+
oParentNode.insertAdjacentHTML("BeforeEnd", sHTML);
}
}
装上述方法重构页面,可以提高200—400%的呈现速度!记住减少“http握手数”,控制在11个握手数之内,可以用firefox 的firebug插件的Net功能查看http请求数!非页面逻辑的JS放在页面尾部!
2、second
提高ASPX服务器性能的几大狠招
∆ 第0招依靠测试工具,以下根据ACT test测试结果,整理。【全部招数凶狠度的依据】
所谓性能优化,必须是建立在测试的基础之上的,ACT Test是比较爽的测试工具,比Load Runner方便,比Web Stress直观,支持脚本编程和录制登陆到注销全过程。
所有优化都要进行对比测试,才是评判的数字依据。
所以,个人认为:不做压力测试,优化是可以做,但是没数据支持,是不严谨的。
∆ 第一招生成静态。【凶狠度排名第一:性能RPS提升两个数量级(提高速度百倍)】
以下是截取Response的Stream生成文件的代码
protected override void Render(HtmlTextWriter writer)
{
StringWriter stringWriter = new StringWriter();
HtmlTextWriter htmlTextWriter = new HtmlTextWriter(stringWriter);
base.Render(htmlTextWriter);
if (本页允许生成静态HTML)
{
//根据aspx的get参数构造出的HTML文件名,如:aaa.aspx?id=1 转化成 aaa_id_1.html
string filePath = Server.MapPath(htmlFileName);
StreamWriter streamWriter = new StreamWriter(filePath , false, Encoding.UTF8);
streamWriter.Write(HTML);
streamWriter.Close();
htmlTextWriter.Close();
}
}
生成后,以后先判断是否有这个文件,如果有就跳过去。
本页允许生成静态HTML,可以在后台做一个钩选和更新,并且在内容发生变化后删除静态HTML。
∆ 第二招,图片文件分流服务器。【凶狠度排名第二:性能RPS提升1个数量级(提高速度十倍)】
1,web 1台或多台。
2,图片1台或多台。
3,文件1台或多台。
4,数据库1台或多台。
不同机房要采用Remoting分发文件是个不错的主义,就是工作量大。
以下是同机房多服务器之间可以通过web读写分发文件的关键代码。
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.IO;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Security.Principal;
using System.Runtime.InteropServices;
namespace WebApplication1
{
public partial class _Default : System.Web.UI.Page
{
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;
WindowsImpersonationContext impersonationContext;
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int LogonUser(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
private bool impersonateValidUser(String userName, String domain, String password)
{
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
if (LogonUser(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
WindowsIdentity tempWindowsIdentity;
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
if (impersonationContext != null)
return true;
else
return false;
}
else
return false;
}
else
return false;
}
private void undoImpersonation()
{
impersonationContext.Undo();//回退为未更改前账户
}
protected void Page_Load(object sender, EventArgs e)
{
//临时更改为 跟 网络硬盘相同用户名密码的账户(此账户必须在网络盘有写入权限)本机也需要同样帐号密码的帐户
if (impersonateValidUser("administrator", "192.168.1.102", "kuqu123456"))
{
Response.Write(System.IO.File.Exists(@"//192.168.1.102/share/C#高级编程/C#高级编程(第四版).pdf"));
undoImpersonation();
}
else
{
Response.Write("登陆失败");
}
}
}
}
∆ 第三招,各种缓存。【凶狠度排名第三:性能RPS提升1个数量级(提高速度十倍级别)】
缓存代码好找,就不写了,道理不用多讲。
∆ 第四招,减少数据库往返。【凶狠度排名第四:性能RPS提升和往返次数有关】
能一次取的数据,就不要多次,尤其在绑定的行里多次执行存储过程。
∆ 第五招,少用对象。【凶狠度排名第五:性能RPS提升取决于对象数量】
用Static函数执行SQL,绑定数据或者增删查改,来代替 new一堆对象去绑定List或者设置属性。
∆ 第六招,多用Ajax和客户端HTML代码,少用服务器控件,减少刷新和ViewState。【凶狠度排名第六:性能RPS提升取决于具体界面功能】
道理不用多讲,服务器控件垃圾比较多。
∆ 第七招,采用好的数据结构容器和算法。。【凶狠度排名第七:性能提升取决于算法复杂度,普通Web开发这种情况不多见,但是特殊开发要重视!~~】
哈希类性能最好,查找复杂度是O(1),例如:Dictionnary
二叉树查找复杂度是O(Log n),排序O(n*Log n),List和ArraList自带的排序速度是接近(n*Log n),
数组类的线性查找O(n),如果你用冒泡排序O(n*n),那么你不是优秀程序员。
StringBuilder 性能好于 string+=,不过提升的有限,不是本质的数量级别的提升。