Web性能优化实践——应用层性能优化
随着公司项目的进一步推广,用户数量的增加,已经面临着单台服务器不能负载的问题。
这次的优化由于时间关系主要分两步走,首先优化应用层代码以提高单台服务器的负载和吞吐率。之后再进行分表,引入队列、MemCached等分布式应用。
项目背景:这是一个在线竞赛的项目(http://race.gwy.591up.com),在竞赛的时间段内数据库的写入压力很大。
当前问题:1、服务器带宽压力。2、数据库压力。
下图是Web服务器CPU使用率报表。
总体上应用层服务器的CPU使用率不高。
下图是Web服务器带宽报表。
从这个报表可以看到,每块竞赛带宽的占用都会出现一个很高的峰值。
我们再看数据库服务器的带宽报表。
同样的,数据库服务器同样的在竞赛的时间点流量超大,很明显这种情况是不正常的,查询肯定是有问题的。
面对这样的问题,确定了第一期主要做以下的优化。
1、用flash storage做用户做题断点记录。(做题断点:类似程序断点,用户做到第N题时退出做答,下次进入时依然定位到第N题。)这里原来是用数据库存储的,但 用户每做一题都会执行一条UPDATE语句,而数据库是MySQL的MyISAM引擎,更新操作经常被堵塞。
2、更改系统交卷行为。原来系统在用户做完提交竞赛后,会执行一条UPDATE语句更新用户提交试卷的时间点。同样的这个UPDATE也是在同一时间段内执行,和产品经理沟通后,确认在最后一分钟的时候可以不用再执行这个更新,允许用户的作答时间有1分钟的误差。
3、调整数据库的更新语句为插入语句,这个优化点因为时间问题,推迟到第二个优化阶段再处理。
4、调整应用服务器以支持LVS集群。对当前系统进行分析后,暂时可以不用调整代码直接部署集群,问题是在多台服务器内都会存在相同的进程内缓存,这种情况暂时是可以接受的,后期需要改到MemCached集中管理缓存。
5、等待成绩页面同一时间跳转的压力问题。在线竞赛的提交时间点很集中,用户做答完题目后,会统一跳转到一个页面等待答案(这时后台的 Windows 服务在进行竞赛统计),这里服务器的并发、带宽压力都非常大。因此,优化这里不进行跳转,而是在当前的页面等待,并且会自动给不同的用户分配不同的等待时 间,以避免占满服务器的带宽。
6、每场完整的公务员考试试卷,题目资源有150K-200K,因为作答和查看解析是在不同的页面,之前的实现会造成题目的多次加载,严重的浪费了 带宽资源。于是这里优化成Handler输入静态资源加载,从服务器加载一次之后,后面所有的地方调用到题目都可以从浏览器的本地缓存中加载带省服务器带 宽。同时,服务器上只对静态资源服务器开启了GZip压缩,对动态文件进行压缩会浪费服务器的CPU资源,而只对Handler输出的题目进行GZip压 缩,一方面节省了CPU,另一方面把150K-200K的题目资源压缩到了50K左右。
7、数据库性能优化。调整了代码中查询的各个条件的位置,使查询语句能够更多的使用到索引。同时把原来每次一条的插入操作修改为一次插入多条等一些数据库查询优化。
任何一个优化都要针对已经存在的问题,从服务器监控的报表可以看到我们这个网站应用服务器带宽压力、数据库服务器带宽压力都很大,应用服务器的CPU使用率不高,因此,主要的优化是对应用服务器带宽和数据库服务器的写入压力做的优化,因为目的很明确,效果也是比较明显的。
文中提到了用Handler来输出静态资源让浏览器缓存,附上这个代码,其它的优化针对性很高,就不再啰嗦了,主要的还是记录下这次优化的工作方式和工作方法。
Handler输出的静态资源使用了.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压缩器实现这个接口。
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压缩器也实现这个接口。
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);}}}
}
如果浏览器不支持流压缩,那我们只能直接输出内容了,因此我们还需要一个不进行压缩的处理类。
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);}}}
}
现在我们就可以开始编码我们的Handler了。
public class QuestionCacheHandler : IHttpHandler
{#region 静态变量/// <summary>/// 资源过期时间/// </summary>private static readonly int durationInDays = 30;/// <summary>/// 流压缩接口/// </summary>private static readonly ICompressor[] Compressors = {new GZipCompressor(),new DeflateCompressor(),new NullCompressor()};#endregion#region 私有变量/// <summary>/// 内存流压缩类/// </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>/// 发送内容到客户端/// </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>/// 是否浏览器已经缓存/// </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;}}
}
服务器端代码通过Http请求Header的Accept-Encoding来判断是否支持流压缩,再通过Header的etag来判断浏览器中是否已经有缓存副本。
转自:http://blog.moozi.net/archives/web-performance-optimization-practice-application-optimization.html
转载于:https://www.cnblogs.com/xiaopohou/archive/2011/09/20/2182811.html
Web性能优化实践——应用层性能优化相关推荐
- 记一次接口性能优化实践总结:优化接口性能的八个建议
前言 最近对外接口偶现504超时问题,原因是代码执行时间过长,超过nginx配置的15秒,然后真枪实弹搞了一次接口性能优化.在这里结合优化过程,总结了接口优化的八个要点,希望对大家有帮助呀~ 数据量比 ...
- 淘特 Flutter 流畅度优化实践
作者:谢伟(韦圣) 不同的业务背景引出不同的技术诉求,"用户体验特爽"是淘特的不懈追求,本文将介绍笔者加入淘特以来在Flutter流畅度方面的诸多优化实践,这些优化不涉及Engin ...
- 淘特 Flutter 流畅度优化实践 · 二期
作者:谢伟(韦圣) "在上一篇<淘特 Flutter 流畅度优化实践>中说到,虽然一期效果较为明显,但距离极致的用户体验仍有不小的差距.去年,淘特端架构联合业务团队共同发起&qu ...
- QQ音乐客户端Web页面通用性能优化实践
导语 | QQ音乐 Android 客户端的 Web 页面日均 PV 达到千万量级,然而页面的打开耗时与 Native 页面相距甚远,需要系统性优化.本文将介绍 QQ 音乐 Android 客户端在进 ...
- QQ音乐Android客户端Web页面通用性能优化实践
QQ音乐 Android 客户端的 Web 页面日均 PV 达到千万量级,然而页面的打开耗时与 Native 页面相距甚远,需要系统性优化.本文将介绍 QQ 音乐 Android 客户端在进行 Web ...
- webview加载的页面和浏览器渲染的页面不一致_QQ音乐Android客户端Web页面通用性能优化实践...
QQ音乐 Android 客户端的 Web 页面日均 PV 达到千万量级,然而页面的打开耗时与 Native 页面相距甚远,需要系统性优化.本文将介绍 QQ 音乐 Android 客户端在进行 Web ...
- web 折线图大数据量拉取展示方案_【第2010期】QQ音乐Android客户端Web页面通用性能优化实践...
前言 今日早读文章由QQ音乐客户端开发工程师@关岳分享,公号:云加社区(ID:QcloudCommunity,腾讯云官方开发者社区)授权分享. 正文从这开始~~ QQ音乐 Android 客户端的 W ...
- 让互联网更快的协议,QUIC在腾讯的实践及性能优化
本文来自腾讯资深研发工程师罗成在InfoQ的技术分享. 本文首发InfoQ公众号,系腾讯技术工程事业群与InfoQ合作的"腾讯技术工程"专栏第二篇文章,新的一年,我们将会为技术人员 ...
- Hadoop YARN:调度性能优化实践【转】
原文地址:https://www.infoq.cn/article/dh5UpM_fJrtj1IgxQDsq 背景 YARN 作为 Hadoop 的资源管理系统,负责 Hadoop 集群上计算资源的管 ...
最新文章
- 【LeetCode从零单排】No121	Best Time to Buy and Sell Stock
- Deep Learning的基础概念
- JAVA-Concurrency之CountDownLatch说明
- AllTray-将办法最小化到琐细托盘
- 1388C. Uncle Bogdan and Country Happiness
- 雷霆战机源代码c语言,C++实现雷霆战机可视化小游戏
- PHP倒序后五个字符串,并显示
- 英特尔王庆连续四年担任OpenStack基金会个人独立董事
- 對比加班時間和考勤時間,得出實際加班時長函数
- Insert Node in Sorted Linked List
- 如何添加虚拟PDF打印机
- 通过IP地址和子网掩码,如何计算出网络地址、广播地址和主机数?
- Transformer时序预测
- 软件测试登陆注册经典测试用例
- 从高德地图获取城市公交线路+站点
- 【Word】论文的章标题以汉字编号,图、表以数字编号的实现
- 《白帽子讲Web安全》浏览器安全
- JAVA基础--java简单知识04(类与对象,封装,继承,多态)
- 偏最小二乘回归(三):身体特征与体能训练结果的 案例分析
- 游戏设计之基于高程图的三维地形绘制
热门文章
- python 申请内存空间、用于创建多维数组_python 申请内存空间,用于创建多维数组的实例...
- 慕课的原型图快速变html,分享一个html转换为pdf 利器 Pechkin
- 如何判断第一位是1_如何快速判断1瓶红酒的价格,防止被坑?
- 服务器主机防御系统,主机入侵防御系统
- 查看显卡显存_强力散热别浪费 显卡超频这样搞
- c语言课程设计的摘要,投票程序设计-C语言课程设计摘要.doc
- matplotlib绘制图表,设置刻度标签、最大最小刻度、字体大小,label位置、刻度轴箭头等
- 红外遥感设计报告论文+电路原理图
- 相机标定 matlab opencv ROS三种方法标定步骤(3)
- 剑指offer:面试题17. 打印从1到最大的n位数