1、分离用户头像到独立服务器中,并且取消通过程序读取和显示头像。
在以往的实现中,是通过DisplayPicture.aspx页面的 Response.WriteFile(fullname);
读取用户的头像然后显示出来的,前且还通过URL重写,提供友好的URL,例如http://profile.csdn.net/billok/picture/1.jpg
在这里面会有一定的性能和资源损耗,例如URL重写和通过asp.net读图片到内存然后又回收,这样的损耗其实没有太大的必要。所以在这次的性能优化中就直接显示为真实的地址,例如: http://avatar.profile.csdn.net/1/c/5/1_billok.jpg
其中 "/1/c/5/"是MD的散列的一部分,目的是为了文件在磁盘能够更加均匀的分布。
通过这样的修改就会取消ASP.NET的托管,提高影响的能力和减少不必要的性能损耗。同时,把avatar站点迁移到另外一台负荷更少一点的服务器上,就能大大减少IO读取量,当然上传头像和其它服务调用也要一并迁移了。
2、改良文件缓存组件。
系统中最重要的缓存方式是文件缓存,所以文件缓存的调用率是十分具大的,只要有一点的改善所带来的好处就是很大的了。
缓存组件是一个通用的缓存组件,可能通过配置来切换不同的缓存类型(如:文件、MemCached、ASP.NET等),不过这里主要用到的就是文件形式,因为有点复杂这里就先不拿出来讲了,不过在下篇文章里我会把 文件缓存的核心提取出一个简化版本以便大家纠一下错。这里只介绍本次优化中做了些什么事。
#资源的释放。
在检查代码的过程中发现计算MD5后竟后没有清理资源,在计算后须要调用一下Clear()操作。
- using (MD5 md5 = MD5CryptoServiceProvider.Create())
- {
- byte[] buf = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(k));
- x = BitConverter.ToString(buf).Replace("-", string.Empty);
- md5.Clear();
- }
- private static string ConvertToString(object objectToConvert)
- {
- string str = null;
- using (MemoryStream stream = new MemoryStream())
- {
- using (XmlTextWriter xmlWriter = new XmlTextWriter(stream, Encoding.UTF8))
- {
- XmlSerializer serializer = new XmlSerializer(objectToConvert.GetType());
- serializer.Serialize(xmlWriter, objectToConvert);
- byte[] buffer = new byte[stream.Length];
- stream.Position = 0;
- stream.Read(buffer, 0, (int)stream.Length);
- str = Encoding.UTF8.GetString(buffer);
- xmlWriter.Close();
- }
- stream.Close();
- }
- return str;
- }
- private static object ConvertToObject(string xml, Type objectType)
- {
- if (objectType == null)
- return null;
- object obj = null;
- try
- {
- byte[] buffer = Encoding.UTF8.GetBytes(xml);
- using (MemoryStream stream = new MemoryStream(buffer, 0, buffer.Length))
- {
- stream.Position = 0;
- using (XmlTextReader reader = new XmlTextReader(stream))
- {
- XmlSerializer serializer = new XmlSerializer(objectType);
- obj = serializer.Deserialize(reader);
- reader.Close();
- }
- stream.Close();
- }
- }
- catch { }
- return obj;
- }
#改善Get方法的逻辑,减少IO读取次数。
改动的地方不大,但是每次读缓存时减少了一次IO,这样对IO的改善就很大了。这里讲一下一个细节就是在判断缓存有没有过期的时候需要知道文件的最后更新 时间,我们可以通过File.GetLastWriteTime(fileName)方法获取到,但是如果fileName不存在的话就会返 回"1601/1/1 8:00:00"这样的时间值,所以可以通过if (dt == null || dt.Value.Year == 1601)来判断缓存是否已经存在了,这样就减少了一次的IO读取。
3、缓存默认头像。
头像服务中原来是通过 Response. WriteFile(Server.MapPath(fileName)) 方法从文件系统中读取头像然后显示出来。但是目前的情况是有头像的用户实际上是少数的,大部分的用户都是没有上传头像的,而且默认头像也就只有5个,所以 如果每次访问默认头像都要读取一个IO就实在有点浪费。所以改用Response.BinaryWrite方法来从byte[]中读取显示头像,并且对头 像的数据进行内存缓存,这样IO读取量就会大大减少了。下面是修改后的代码:
- protected void Page_Load(object sender, EventArgs e)
- {
- string url = Request.Url.ToString();
- string mime = "image/x-icon";
- string fileName = "noimg_default.ico";
- if (!url.EndsWith(".ico"))
- {
- mime = "image/jpg";
- fileName = string.Format("noimg_default_{0}.jpg", url.Substring(url.LastIndexOf('/') + 1, 1));
- }
- //Response.Buffer = true;
- //Response.Clear();
- Response.BufferOutput = true;//将服务器创建的响应进行缓存
- Response.ClearHeaders();
- Response.AddHeader("Content-Type", mime);
- string filePath = Server.MapPath(fileName);
- if (filePath.IndexOf("noimg_default.ico") > -1 ||
- filePath.IndexOf("noimg_default_1.jpg") > -1 ||
- filePath.IndexOf("noimg_default_2.jpg") > -1 ||
- filePath.IndexOf("noimg_default_3.jpg") > -1 ||
- filePath.IndexOf("noimg_default_4.jpg") > -1)
- {
- //Response.WriteFile(Server.MapPath(fileName));
- string cacheKey = filePath;
- byte[] bytes = Cache.Get(cacheKey) as byte[];
- if (bytes == null)
- {
- bytes = GetImageBinary(filePath);
- Cache.Insert(cacheKey, bytes, null, DateTime.MaxValue, TimeSpan.Zero,
- CacheItemPriority.High, null);
- }
- Response.BinaryWrite(bytes);
- }
- //Response.Flush();
- Response.End();
- }
- private byte[] GetImageBinary(string filePath)
- {
- byte[] bytes = null;
- using (Stream stream = new FileStream(filePath, FileMode.Open,
- FileAccess.Read, FileShare.ReadWrite))
- {
- using (BinaryReader br = new BinaryReader(stream))
- {
- for (Int64 x = 0; x < (br.BaseStream.Length / 10000 + 1); x++)
- {
- bytes = br.ReadBytes(10000);
- }
- br.Close();
- }
- stream.Close();
- }
- return bytes;
- }
注意:
# 这里不配置Response.Flush();否则可能会出现错误"0x80072746"
#提高Web园的数目到4个,以提高该应用程序池处理请求的性能。
4、排除异常捕捉组件捕获的异常。
5、垃圾收集器的类型有两种,即工作站和服务器。选择不同的垃圾引集器类型对性能有一定的影响,默认选项为工作站形式,通过处改配置文件可以指定不同的收集器类型,对于多核服务器来说,需要修改成以服务器垃圾收集器的形式进行工作,最大限度的提高垃圾收集的效率。
- <runtime>
- <gcServer enabled="true"/>
- </runtime>
后续:
#HttpWebRequest潜在的问题。
- HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://xxx.csdn.net/xxx.ashx");
- request.Timeout = 1000;
- string content = null;
- try
- {
- using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
- {
- if (response.StatusCode == HttpStatusCode.OK)
- {
- using (Stream stream = response.GetResponseStream())
- {
- using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
- {
- content = reader.ReadToEnd();
- reader.Close();
- }
- stream.Close();
- }
- }
- response.Close();
- }
- }
- catch (System.Net.WebException)
- {
- }
- finally
- {
- if (request != null)
- {
- request.Abort();
- request = null;
- }
- }
这样的调用应该做到了足够的资源释放措施,但是事实上并没有很好的解决刚才提到的问题,资源还是会不断增长。
当然这样的增长可能也是正常的,不过过快地进行应用程序回收并不是一个很好方法,所以可以考虑把修改对内容服务的调用方式。
内容服务:它提供一个公共展示内容(html或者模板)的获取点,但它的内容可以来源于不同的数据源, 调用方可以根据自已的需要获取自已页面所需要内容项,如果所请求的内容项在内容服务中不存在,则会自动根据配置文件从数据源(隐藏的)中自动获取并进行缓 存。内容服务的更新有两种方式,一种是拉的方式,就是刚才提到的根据配置文件向数据源接口自动提取;一种是推的方式,数据源方如果有数据更新会通过 MSMQ的形式传递给内容服务。通过这两种方式可以保正内容服务的内容的时效性,并且对于内容服务来说,它也是一个经过优化的统一的缓存服务,能分散IO 流量。具体内容服务的框架设计会在以后的文章中详细说明。
内容服务使用WCF(MSMQ+SVC)技术,但是在内容获取上为了兼容不同平台的调用需要使用了ashx页面返回JSON内容的方式,所以就出现了刚才使用HttpWebRequest来请求内容服务的问题,其实从调用性能角度来看完全可以把这样调用也通过WCF(TCP)来提供调用接口,这样既可以提高调用性能,也可能会解决内存增长过快的问题。