Web性能优化实践——应用层性能优化
隨著公司項目的進(jìn)一步推廣,用戶數(shù)量的增加,已經(jīng)面臨著單臺服務(wù)器不能負(fù)載的問題。
這次的優(yōu)化由于時間關(guān)系主要分兩步走,首先優(yōu)化應(yīng)用層代碼以提高單臺服務(wù)器的負(fù)載和吞吐率。之后再進(jìn)行分表,引入隊列、MemCached等分布式應(yīng)用。
項目背景:這是一個在線競賽的項目(http://race.gwy.591up.com),在競賽的時間段內(nèi)數(shù)據(jù)庫的寫入壓力很大。
當(dāng)前問題:1、服務(wù)器帶寬壓力。2、數(shù)據(jù)庫壓力。
下圖是Web服務(wù)器CPU使用率報表。
總體上應(yīng)用層服務(wù)器的CPU使用率不高。
下圖是Web服務(wù)器帶寬報表。
從這個報表可以看到,每塊競賽帶寬的占用都會出現(xiàn)一個很高的峰值。
我們再看數(shù)據(jù)庫服務(wù)器的帶寬報表。
同樣的,數(shù)據(jù)庫服務(wù)器同樣的在競賽的時間點流量超大,很明顯這種情況是不正常的,查詢肯定是有問題的。
面對這樣的問題,確定了第一期主要做以下的優(yōu)化。
1、用flash storage做用戶做題斷點記錄。(做題斷點:類似程序斷點,用戶做到第N題時退出做答,下次進(jìn)入時依然定位到第N題。)這里原來是用數(shù)據(jù)庫存儲的,但 用戶每做一題都會執(zhí)行一條UPDATE語句,而數(shù)據(jù)庫是MySQL的MyISAM引擎,更新操作經(jīng)常被堵塞。
2、更改系統(tǒng)交卷行為。原來系統(tǒng)在用戶做完提交競賽后,會執(zhí)行一條UPDATE語句更新用戶提交試卷的時間點。同樣的這個UPDATE也是在同一時間段內(nèi)執(zhí)行,和產(chǎn)品經(jīng)理溝通后,確認(rèn)在最后一分鐘的時候可以不用再執(zhí)行這個更新,允許用戶的作答時間有1分鐘的誤差。
3、調(diào)整數(shù)據(jù)庫的更新語句為插入語句,這個優(yōu)化點因為時間問題,推遲到第二個優(yōu)化階段再處理。
4、調(diào)整應(yīng)用服務(wù)器以支持LVS集群。對當(dāng)前系統(tǒng)進(jìn)行分析后,暫時可以不用調(diào)整代碼直接部署集群,問題是在多臺服務(wù)器內(nèi)都會存在相同的進(jìn)程內(nèi)緩存,這種情況暫時是可以接受的,后期需要改到MemCached集中管理緩存。
5、等待成績頁面同一時間跳轉(zhuǎn)的壓力問題。在線競賽的提交時間點很集中,用戶做答完題目后,會統(tǒng)一跳轉(zhuǎn)到一個頁面等待答案(這時后臺的 Windows 服務(wù)在進(jìn)行競賽統(tǒng)計),這里服務(wù)器的并發(fā)、帶寬壓力都非常大。因此,優(yōu)化這里不進(jìn)行跳轉(zhuǎn),而是在當(dāng)前的頁面等待,并且會自動給不同的用戶分配不同的等待時 間,以避免占滿服務(wù)器的帶寬。
6、每場完整的公務(wù)員考試試卷,題目資源有150K-200K,因為作答和查看解析是在不同的頁面,之前的實現(xiàn)會造成題目的多次加載,嚴(yán)重的浪費了 帶寬資源。于是這里優(yōu)化成Handler輸入靜態(tài)資源加載,從服務(wù)器加載一次之后,后面所有的地方調(diào)用到題目都可以從瀏覽器的本地緩存中加載帶省服務(wù)器帶 寬。同時,服務(wù)器上只對靜態(tài)資源服務(wù)器開啟了GZip壓縮,對動態(tài)文件進(jìn)行壓縮會浪費服務(wù)器的CPU資源,而只對Handler輸出的題目進(jìn)行GZip壓 縮,一方面節(jié)省了CPU,另一方面把150K-200K的題目資源壓縮到了50K左右。
7、數(shù)據(jù)庫性能優(yōu)化。調(diào)整了代碼中查詢的各個條件的位置,使查詢語句能夠更多的使用到索引。同時把原來每次一條的插入操作修改為一次插入多條等一些數(shù)據(jù)庫查詢優(yōu)化。
任何一個優(yōu)化都要針對已經(jīng)存在的問題,從服務(wù)器監(jiān)控的報表可以看到我們這個網(wǎng)站應(yīng)用服務(wù)器帶寬壓力、數(shù)據(jù)庫服務(wù)器帶寬壓力都很大,應(yīng)用服務(wù)器的CPU使用率不高,因此,主要的優(yōu)化是對應(yīng)用服務(wù)器帶寬和數(shù)據(jù)庫服務(wù)器的寫入壓力做的優(yōu)化,因為目的很明確,效果也是比較明顯的。
文中提到了用Handler來輸出靜態(tài)資源讓瀏覽器緩存,附上這個代碼,其它的優(yōu)化針對性很高,就不再啰嗦了,主要的還是記錄下這次優(yōu)化的工作方式和工作方法。
Handler輸出的靜態(tài)資源使用了.NET流壓縮,于是我們聲明一個壓縮器接口。
using System.IO;namespace ND.Race.Compressor
{/// <summary>/// 壓縮器接口/// </summary>public interface ICompressor{string EncodingName { get; }bool CanHandle(string acceptEncoding);void Compress(string content, Stream targetStream);}
}GZip壓縮器實現(xiàn)這個接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29using System.IO;
using System.IO.Compression;
using System.Text;namespace ND.Race.Compressor
{public sealed class GZipCompressor : ICompressor{public string EncodingName{get { return "gzip"; }}public bool CanHandle(string acceptEncoding){return !string.IsNullOrEmpty(acceptEncoding) &&acceptEncoding.Contains("gzip");}public void Compress(string content, Stream targetStream){using (var writer = new GZipStream(targetStream, CompressionMode.Compress)){var bytes = Encoding.UTF8.GetBytes(content);writer.Write(bytes, 0, bytes.Length);}}}
}
同樣的Deflate壓縮器也實現(xiàn)這個接口。
using System.IO;
using System.IO.Compression;
using System.Text;namespace ND.Race.Compressor
{public sealed class DeflateCompressor : ICompressor{public string EncodingName{get { return "deflate"; }}public bool CanHandle(string acceptEncoding){return !string.IsNullOrEmpty(acceptEncoding) &&acceptEncoding.Contains("deflate");}public void Compress(string content, Stream targetStream){using (var writer = new DeflateStream(targetStream, CompressionMode.Compress)){var bytes = Encoding.UTF8.GetBytes(content);writer.Write(bytes, 0, bytes.Length);}}}
}
如果瀏覽器不支持流壓縮,那我們只能直接輸出內(nèi)容了,因此我們還需要一個不進(jìn)行壓縮的處理類。
using System.IO;
using System.Text;namespace ND.Race.Compressor
{public sealed class NullCompressor : ICompressor{public string EncodingName{get { return "utf-8"; }}public bool CanHandle(string acceptEncoding){return true;}public void Compress(string content, Stream targetStream){using (targetStream){var bytes = Encoding.UTF8.GetBytes(content);targetStream.Write(bytes, 0, bytes.Length);}}}
}
現(xiàn)在我們就可以開始編碼我們的Handler了。
public class QuestionCacheHandler : IHttpHandler
{#region 靜態(tài)變量/// <summary>/// 資源過期時間/// </summary>private static readonly int durationInDays = 30;/// <summary>/// 流壓縮接口/// </summary>private static readonly ICompressor[] Compressors = {new GZipCompressor(),new DeflateCompressor(),new NullCompressor()};#endregion#region 私有變量/// <summary>/// 內(nèi)存流壓縮類/// </summary>private ICompressor compressor;/// <summary>/// ETAG/// </summary>private string eTagCacheKey;/// <summary>/// 競賽場次Id/// </summary>private long raceId;#endregionpublic void ProcessRequest(HttpContext context){if (context == null) return;long.TryParse(context.Request.QueryString["raceId"], out raceId);if (raceId == 0) return; var acceptEncoding = context.Request.Headers["Accept-Encoding"];compressor = Compressors.First(o => o.CanHandle(acceptEncoding));eTagCacheKey = string.Concat(raceId, "/etag");if (IsInBrowserCache(context, eTagCacheKey)) return;SendOutputToClient(context, true, eTagCacheKey);}/// <summary>/// 發(fā)送內(nèi)容到客戶端/// </summary>/// <param name="context"></param>/// <param name="insertCacheHeaders"></param>/// <param name="etag"></param>private void SendOutputToClient(HttpContext context, bool insertCacheHeaders, string etag){string content = "";MemoryStream memoryStream = new MemoryStream();compressor.Compress(content, memoryStream);byte[] bytes = memoryStream.ToArray();HttpResponse response = context.Response;if (insertCacheHeaders){HttpCachePolicy cache = context.Response.Cache;cache.SetETag(etag);cache.SetOmitVaryStar(true);cache.SetMaxAge(TimeSpan.FromDays(durationInDays));cache.SetLastModified(DateTime.Now);cache.SetExpires(DateTime.Now.AddDays(durationInDays)); // HTTP 1.0 的瀏覽器使用過期時間cache.SetValidUntilExpires(true);cache.SetCacheability(HttpCacheability.Public);cache.SetRevalidation(HttpCacheRevalidation.AllCaches);cache.VaryByHeaders["Accept-Encoding"] = true;}response.AppendHeader("Content-Length", bytes.Length.ToString(System.Globalization.CultureInfo.InvariantCulture));response.ContentType = "text/plain";response.ContentType = "application/x-javascript";response.AppendHeader("Content-Encoding", compressor.EncodingName);if (bytes.Length > 0)response.OutputStream.Write(bytes, 0, bytes.Length);if (response.IsClientConnected)response.Flush();}/// <summary>/// 是否瀏覽器已經(jīng)緩存/// </summary>/// <param name="context"></param>/// <param name="etag"></param>/// <returns></returns>private bool IsInBrowserCache(HttpContext context, string etag){string incomingEtag = context.Request.Headers["If-None-Match"];if (String.Equals(incomingEtag, etag, StringComparison.Ordinal)){context.Response.Cache.SetETag(etag);context.Response.AppendHeader("Content-Length", "0");context.Response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;context.Response.End();return true;}return false;}public bool IsReusable{get{return false;}}
}
服務(wù)器端代碼通過Http請求Header的Accept-Encoding來判斷是否支持流壓縮,再通過Header的etag來判斷瀏覽器中是否已經(jīng)有緩存副本。
轉(zhuǎn)自:http://blog.moozi.net/archives/web-performance-optimization-practice-application-optimization.html
轉(zhuǎn)載于:https://www.cnblogs.com/xiaopohou/archive/2011/09/20/2182811.html
總結(jié)
以上是生活随笔為你收集整理的Web性能优化实践——应用层性能优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 专柜纽百伦574多少钱
- 下一篇: WCF - Session 剖析