TFS 在中国 (第二部分)

更新:Adam Cogan 发布了本篇博客的英文版

让我们继续这个真实的项目故事,上一篇中的两个经验都是关于非技术因素的,下面的这些可能技术人员会更感兴趣。

经验3:为你的TFS数据创建索引

TIP: 参考SSW Rules to Better SQL Server Databases

使用数据索引是SQL数据库性能优化的基本方法之一。不过,大家应该知道对于TFS的数据库来说,直接对其中的数据结构进行修改是不推荐的方式,所以大多数人不会意识到其实我们可以对TFS数据库建立索引。

tfstuneindex图:对TFS数据建立索引后的性能提升非常明显

对于我们的应用来说,有2个定制的工作项字段非常频繁的被搜索,因此我们在这2个字段上添加了索引,这使得相关操作的整体性能提高了3倍左右。

以下是对工作项字段添加索引的命令:
witadmin indexfield /collection:CollectionURL /n:Name /index:on|off
witadmin是TFS所提供的工作项类型的后台操作命令,可以通过以下MSDN页面找到更多的信息:
http://msdn.microsoft.com/en-us/library/dd236909.aspx

经验4:在高性能系统上应该尽量避免使用虚拟化

在虚拟化火热的今天,大家都会认为使用Hyper-V来优化服务器基础架构是件好事,但是我们发现对于TFS的数据层服务器来说,由于大量的磁盘I/O操作,虚拟化会对服务器性能造成严重的影响,应该尽量避免。

在我们的项目中,将TFS数据层服务器迁移至硬件是我们在基础架构上所采取的首选措施,仅仅这一项就将相关操作的整体性能提升了2倍左右。请参考以下链接了解更多的信息:
http://msdn.microsoft.com/en-us/library/ms143506%28v=sql.105%29.aspx

说明:在迁移至硬件服务器之前,我们也对Hyper-V Pass-through磁盘进行了测试,但是结果并不让人满意,虽然Pass-through磁盘相对普通的VHD(动态或静态)来说确实提供了更好的I/O性能,但是仍然无法满足我们的性能要求。关于Pass-through磁盘的性能测试请参考:
http://clusteringformeremortals.com/2009/09/25/hyper-v-pass-through-disk-performance-vs-fixed-size-vhd-files-and-dynamic-vhd-files-in-windows-server-2008-r2/

经验5:将数据库的日志文件单独放置于不同的物理磁盘

这个建议并不新鲜,任何的SQL性能调优的指导都会建议将SQL数据库的 数据/日志/临时文件 分别放置于不同的物理磁盘;这样SQL Server可以将I/O请求通过不同的硬件完成,从而提升性能。参考资料:
http://www.codeproject.com/Articles/43629/Top-10-steps-to-optimize-data-access-in-SQL-Server

经验6:在IIS 上激活Web Garden (maxProcesses) 设置

警告:一般来说,这一设置对于99%的应用来说并不推荐,但我们很幸运的属于那1%。其中的原因比较复杂,大家感兴趣可以参考:http://blogs.iis.net/chrisad/archive/2006/07/14/1342059.aspx

对于IIS来说,默认的设置会对同时处理的并发请求数量进行限制,并将无法处理的请求放入队列中等待,这意味着如果单个请求的事务处理时间过长,我们会有很长的等待队列。按照以上引用的IIS博客中的说法,Web Garden功能的设计目的只有一个

“为那些不存在计算依赖,但是会运行很长时间的请求提供扩展能力,从而避免消耗掉所有的工作进程。”

(这个话确实很难理解,你可能需要好好琢磨一下,另外看看我下面对TFS应用层的分析也会帮助你理解。)

现在的问题是,为什么TFS属于这幸运的1%而有别与一般的Web应用程序?
首先大家要明白的是,虽然TFS看上去很复杂,其实对于用户来说它就是一堆的Web Service而已。与一般应用不同的是TFS使用了大量的“动态数据结构”。所谓动态数据结构,就是在应用设计完成以后,仍然允许用户根据需要扩展更多地数据域。在TFS中大量使用了这种技术,这也是为什么我们可以对工作项字段进行定制的原因。不过,这种灵活性造成的结果是在SQL Server上所执行的查询操作会复杂得多,而且会随着你所添加的定制量的增加而变得越发复杂。想象一下:你添加了定制字段的工作项如果要被提取出来,那么TFS应用层所生成的查询需要被动态生成,而在SQL Server中所需要的行列转换的复杂度也会随之增加,如果考虑Query Plan的动态计算等因素,这将是怎样一个漫长的查询过程?

iisgarden-1

上面这张截图中大家看到的就是在IIS在收到大量并发操作时候所生成的队列。一般来说,任何>50ms的请求都已经是慢得要死了,而我们的队列中竟然有125ms的天文数字。另外大家需要注意的是所有这些请求都处于ExecuteRequest状态,意味着IIS正在等待后端程序的处理完成。

经过以上分析后,我们得出的结论是,在满足以下2个条件的情况下你可以使用Web Garden来改进性能:
1. 你的应用没有使用 InProcess Session
2. 你的应用是无状态的(可能你会觉得和第1点重复,其实不完全是)

幸运的是,TFS应用层的Web服务完全满足以上条件,这也是为什么我们可以在TFS上很容易的实现NLB(网络负载均衡),而不需要额外的设置的原因。

决定使用Web Garden以后,下一个问题是我们应该使用多少用户进程(Worker Process)?
其实这个问题很难回答,在实际中我们也是不停地试出来的,影响的因素其实很多,比如:硬件本身的配置,应用被调用的场景等等。不过一个相对通用的建议是,不要超过你的CPU物理核的数量,这是因为CPU用来调度不同的工作进程的开销会抵消掉可能获得的性能提升。

因此,一般的建议是maxprocess = (CPU物理核数量) -1
(看到经验3里面的Worker Process数量为7了么?那是因为我们使用了8核的CPU)

IIS Worker Process configuration

经验7:如何找到你应用中的性能瓶颈?

TIP:关于性能调优,SSW的黄金规则是永远不要在未经评估的请下进行任何调整,请参考:
http://rules.ssw.com.au/Management/RulesToSuccessfulProjects/Pages/NoPerformanceWithoutMetrics.aspx

你可能注意到了,目前为止我们还没有对应用本身进行任何的修改,无论是TFS本身的Web服务或者是定制的代码,都没有进行过任何修改。

当客户提出性能问题的时候,一般开发人员的反应是:难道我的代码有问题,然后马上根据自己臆想出来的最佳方式修改代码,这是绝对错误的。在这种情况下,我们首先要考虑的是那些调整是事半功倍的,以上的调整,无论是服务器基础架构,SQL Server还是IIS;我们的原则都是自下而上,尽量不对代码进行修改,因为这样我们才能最大化我们的调优结果,而且避免由于代码修改而造成的额外测试以及可能衍生的bug。

这就是我们所说的ROI原则,对于我们的项目而言,添加索引可能改进10x的性能,但是你很难搞到10x于现在的硬件,所以SQL 数据库调优是一定的首选。

不幸的是,在尝试了以上所有的可能性后,我们仍然无法达到指标;是时候对代码动手术了。我们使用了Visual Studio 中内置的Performance Wizard来进行性能评估,如下图:

perwizrdvs2010
图:VS 2010和VS 2012中的Performance Wizard

perwizrdvs2013
图:VS 2013中这个功能叫做 Performance and Diagnostics

以下是我们获取的第一份评估报告

tfsapi
图:你可以很容易的分辨出我们的瓶颈在于NewWorkItem()这个API操作

对NewWorkItem()这个API我们进行了进一步分析,发现其中最耗时的操作其实是承载对象的实例化操作。这时问题来了,对这个操作我们无能为力,因为我们必须要这个承载对象才能执行需要的操作。经过了一些调研后,我们决定使用“对象池”的方式来尽量多的缓存一些预先实例化好的对象,需要的时候直接取出使用,完成后再放回去。最终这个对象池为又将性能提升了22%左右。

关于对象池技术,请参考:http://msdn.microsoft.com/en-us/library/ms751482.aspx

经验8:如何发现那些最耗时的Web Service操作?

既然TFS是基于Web Service的,那么发现最耗时的Web Service调用也是进行调优的评估内容之一,这里我们激活了TFS的客户端Web Service跟踪,获得了以下报告:

tfstracing
上图中我们发现最耗时的操作是GetMetadataEx2[WorkItemTracking]操作,这其实就是NewWorkItem()中实例化对象的一个主要操作;这一分析再一次证实对象池的方向是正确的。对于这个GetMetadataEx操作,大家可能并不了解,其实这与TFS的操作机制有关:TFS的对象模型会在本地保存一个缓存,其中包括了工作项定制内容的定义(XML格式),这些数据其实非常庞大,因此造成了实例化操作的缓慢。

以下为激活跟踪的配置:

tfstracecocnf

关于客户端跟踪配置请参考:
http://blogs.msdn.com/b/buckh/archive/2010/04/23/how-to-see-the-tfs-server-calls-made-by-the-client.aspx

关于服务器端跟踪配置请参考:
http://blogs.msdn.com/b/edhintz/archive/2007/03/30/tfs-client-tracing.aspx

经过了这一系列的评估后,我们尝试了2种代码调优方案:
1. 使用对象池技术
2. 直接调用Web Service (这样也可以避免metadata的缓存操作)

对2种方案测试后我们发现结果很接近,经过一些讨论后,我们决定使用方案1,因为方案2中我们必须使用未经微软公开的API,这样长远来看存在维护风险。

故事到这里也就结束了,我们的客户对结果很满意,我的团队也学到了很多。希望这些分享也能对你有帮助。

2 Comments:

  1. 经验8:如何发现那些最耗时的Web Service操作?
    这个里面用的监测WebService性能的工具是啥?

Comments are closed