从9月份来一直忙于开发一个视频系统,系统规模中等,目前日PV几十万这样,系统从开始建设到1.7号,稳定性一直是非常让人头疼的大问题。偶尔公司有些市场推广,系统就撑不住了。那么是什么造成这样的难堪的呢?又是怎样解决的呢?下面我就真实遇到,实践过,及目前能理解范围的内容和大家一起分享。
系统采用WCF作为中间层和Web运用层通信方式。Web运用层通过svcutil.exe方式生成代理类,也是wcf运用开发常规的方式。关于这个方面内容推荐看Bruce Zhang的blog。Web系统已经实现负载均衡;静态页生成;
问题:
web系统内存疯涨,基本半个小时涨600M,被迫回收一次,这样造成的用户体验是非常差的。CUP基本保持100%。而且时不时会出现大量csc.exe进程。
分析:
-
由于系统中使用了大量的缓存,而且都是进程内的。最初认为是缓存使用不当,造成大量缓存碎片造成。于是启用memcache(另一台机器),将部分缓存移出web服务器。
但是这并没有给系统带来太多的好处,memcache稳定后占用内存80M左右。这说明我们缓存压力根本不大。第一次尝试失败!
-
我们知道调用web服务需要进行xml序列化操作,而xml序列化如果使用不当,会有严重的性能问题。
以下是微软支持上的文档:
This problem occurs because an assembly that contains Microsoft intermediate language (MSIL) is created and loaded into memory when you create an XmlSerializer object. You cannot unload the assembly without unloading the application domain that it resides in. Therefore, when you create several XmlSerializer objects, you may notice unexpectedly high memory usage.
这个可以参考:http://support.microsoft.com/kb/886385/en-us。
而wcf可以很好的支持http和tcp,所以马上将proxy调用方式转换成netTcpBinding,同时以windows service作为host。心想这样绕过了xml序列化,而且二进制序列化应该具有更高的效率。但事实却相反,
性能问题没有解决,这种方式更慢了L。对wcf理解不够,初步学习中…
-
序列化:
既然是序列化问题那么咱们改。把系统中最核心的Video服务改。之前的领导(赵同志),给咱们留了个好的demo。就是对象池。先看Video对象通过svcutil.exe生成的代理情况:video服务是以纯wcf创建的,也就是 video.svc 这样的服务形式。
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute()]
public partial class Video : object, System.Runtime.Serialization.IExtensibleDataObject
{
Xxxxx
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class VideoRetrieverClient : System.ServiceModel.ClientBase<VideoRetriever>, VideoRetriever{}
将此VideoRetrieverClient对象池化,这个是系统中最为核心的服务,调用也是最频繁的。直接说结果吧:失败!
关于我们这里的对象池,我后面会介绍;关于对象池更多的了解可以看这个:http://www.cnblogs.com/flyingchen/archive/2008/01/09/1032355.html
失望啊。。
-
Windbg(最近很火的名词)。
在产品环境下,只能通过它来工作了。经过一些资料查阅,基本把问题定在这个xml序列化上。但是我们整个架构都基于wcf的啊,而且最核心的服务都做过池化了,还是没有效果,而序列化又绕不过去了啊L。不抱怨,继续找解决方案!
这时windbg的文章在博客园上大量涌现(http://juqiang.cnblogs.com/) ,来的真是时候。那咱们也dump一把。
系统是win2003server的,也获取dump文件也是件麻烦的事情,主要是服务器不让安装软件。用vista的就方便多了,它在任务管理器里集成了dump功能。不妨你看任务
管理器---进程---右击出来个(Create dump file).
在wind2003上,咱们也可以通过另外一款软件做到,而且功能强大。IIS Diagnostics (32bit) 中的Debug Diagnostics Tool 1.0。由于这个软件在vista下运行有点问题,就不能
展示具体画面了。
Dump出来一个近700M的文件(强啊),这个文件就是w3wp进程这个时候在内存中的一个情况。接下来的工作就是分析这个dump文件。
这是强大的windbg就出来了。Dump的一些配置和命令可以见http://juqiang.cnblogs.com/ 的系列文章。
写这随笔的时候,我已经非常不小心的将非常宝贵的700M的dump文件给删除了L。
所以这里只能列出基本命令和一些思路了:
0:000> !dumpdomain
这个命令会将modules的信息展示出来。正常的情况下,每个dll都应该有其物理地址的,不是C盘就D盘之类的,如
Module Name
0x01f323b8 c:\windows\assembly\gac\system.xml\1.0.5000.0__b77a5c561934e089\system.xml.dll
Assembly: 0x01f336a8 [System.Web.RegularExpressions]
ClassLoader: 0x00165e60
Module Name
0x01f42120 c:\windows\assembly\gac\system.web.regularexpressions\1.0.5000.0__b03f5f7f11d50a3a\system.web.regularexpressions.dll
Domain 1: 000ec108
LowFrequencyHeap: 000ec12c
HighFrequencyHeap: 000ec188
StubHeap: 000ec1e4
Stage: OPEN
SecurityDescriptor: 000ed310
Name: DefaultDomain
Assembly: 000fd938 [C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader: 000fd9d0
SecurityDescriptor: 000f9df8
Module Name
790c2000 C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll
03052380 C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sortkey.nlp
03052010 C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\sorttbls.nlp
Assembly: 0010c918 [C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll]
ClassLoader: 0010bf50
SecurityDescriptor: 00103258
Module Name
6638c000 C:\WINDOWS\assembly\GAC_32\System.Web\2.0.0.0__b03f5f7f11d50a3a\System.Web.dll
Assembly: 00108898 [C:\WINDOWS\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll]
但是如果出现这样的未知Module,如
Module Name
0x01fb9f50 Unknown Module
Assembly: 0x01fbaaa8 [crn6r9yj]
ClassLoader: 0x01fbab70
Module Name
0x01fbae10 Unknown Module
Assembly: 0x01fbb988 [3ivrpfyn]
ClassLoader: 0x01fbba50
Assembly: 05422f90 [n5epbrz9, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]
ClassLoader: 05423028
SecurityDescriptor: 05422ef8
Module Name
05945088 n5epbrz9, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Assembly: 054237b8 [f8vy6kea, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]
ClassLoader: 05423850
SecurityDescriptor: 05423720
Module Name
05949670 f8vy6kea, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Assembly: 05426a08 [fhoyvxdf, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]
ClassLoader: 05426aa0
SecurityDescriptor: 05426908
这些就有可能是动态生成的,生成这些module要耗CPU(难怪那么多csc.exe在跑),更要占用内存。
通过 dda(DumpDynamicAssemblies)命令可以查看这些动态Module的一些信息:
:000> dda
Domain: ....
-------------------
Domain:
-------------------
Domain: DefaultDomain
-------------------
Domain: /LM/w3svc/1/root/MemoryIssues-1-127843748880710932
-------------------
Assembly: 0x1f8e548 [tfwlio3y] Dynamic Module: 0x1f8ce80 loaded at: 0xc391000 Size: 0x3200((null))
Assembly: 0x1f3e338 [4fbkoonb] Dynamic Module: 0x1f3d588 loaded at: 0xc551000 Size: 0x3200((null))
Assembly: 0x1f31870 [d68fddbc] Dynamic Module: 0x1f31068 loaded at: 0xc571000 Size: 0x3200((null))
继续,通过!mpmodule命令可以查看出某一个Module的情况,如:
0:000> !dumpmodule 0594dc58
Name: fhoyvxdf, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Attributes: PEFile
Assembly: 05426a08
LoaderHeap: 00000000
TypeDefToMethodTableMap: 05aa8ba4
TypeRefToMethodTableMap: 05aa8ca8
MethodDefToDescMap: 05aa8d28
FieldDefToDescMap: 05aa932c
MemberRefToDescMap: 05aa9528
FileReferencesMap: 05aa9860
AssemblyReferencesMap: 05aa9864
MetaData start address: 05adc600 (39952 bytes)
再通过
dc
05adc600
05adc600
+
39952
(起始地址 结束地址)
05adf060 00000000 6f4d3c00 656c7564 6866003e .....<Module>.fh
05adf070 7876796f 642e6664 58006c6c 65536c6d oyvxdf.dll.XmlSe
05adf080 6c616972 74617a69 576e6f69 65746972 rializationWrite
05adf090 555f4972 53726573 0070616f 7263694d rI_UserSoap.Micr
05adf0a0 666f736f 6d582e74 65532e6c 6c616972 osoft.Xml.Serial
05adf0b0 74617a69 2e6e6f69 656e6547 65746172 ization.Generate
05adf0c0 73734164 6c626d65 6d580079 7265536c dAssembly.XmlSer
05adf0d0 696c6169 6974617a 65526e6f 72656461 ializationReader
05adf0e0 73555f49 6f537265 58007061 65536c6d I_UserSoap.XmlSe
05adf0f0 6c616972 72657a69 72410031 4f796172 rializer1.ArrayO
05adf100 6a624f66 53746365 61697265 657a696c fObjectSerialize
05adf110 72410072 4f796172 6a624f66 53746365 r.ArrayOfObjectS
05adf120 61697265 657a696c 41003172 79617272 erializer1.Array
05adf130 624f664f 7463656a 69726553 7a696c61 OfObjectSerializ
05adf140 00327265 61727241 4f664f79 63656a62 er2.ArrayOfObjec
05adf150 72655374 696c6169 3372657a 72724100 tSerializer3.Arr
05adf160 664f7961 656a624f 65537463 6c616972 ayOfObjectSerial
05adf170 72657a69 72410034 4f796172 6a624f66 izer4.ArrayOfObj
05adf180 53746365 61697265 657a696c 41003572 ectSerializer5.A
05adf190 79617272 624f664f 7463656a 69726553 rrayOfObjectSeri
05adf1a0 7a696c61 00367265 61727241 4f664f79 alizer6.ArrayOfO
05adf1b0 63656a62 72655374 696c6169 3772657a bjectSerializer7
05adf1c0 72724100 664f7961 656a624f 65537463 .ArrayOfObjectSe
05adf1d0 6c616972 72657a69 72410038 4f796172 rializer8.ArrayO
05adf1e0 6a624f66 53746365 61697265 657a696c fObjectSerialize
05adf1f0 41003972 79617272 624f664f 7463656a r9.ArrayOfObject
05adf200 69726553 7a696c61 30317265 72724100 Serializer10.Arr
05adf210 664f7961 656a624f 65537463 6c616972 ayOfObjectSerial
05adf220 72657a69 41003131 79617272 624f664f izer11.ArrayOfOb
...
05adf930 555f495f 53726573 0070616f 74697257 _I_UserSoap.Writ
05adf940 5f353265 69646f4d 73557966 6e497265 e25_ModifyUserIn
05adf950 57006f66 65746972 4d5f3632 6669646f fo.Write26_Modif
05adf960 65735579 666e4972 7365526f 736e6f70 yUserInfoRespons
05adf970 72570065 32657469 5f495f37 72657355 e.Write27_I_User
05adf980 70616f53 69725700 38326574 646f4d5f Soap.Write28_Mod
05adf990 55796669 42726573 63697361 69725700 ifyUserBasic.Wri
05adf9a0 39326574 646f4d5f 55796669 42726573 te29_ModifyUserB
05adf9b0 63697361 70736552 65736e6f 69725700 asicResponse.Wri
05adf9c0 30336574 555f495f 53726573 0070616f te30_I_UserSoap.
05adf9d0 74697257 5f313365 69646f4d 694e7966 Write31_ModifyNi
05adf9e0 57006b63 65746972 4d5f3233 6669646f ck.Write32_Modif
05adf9f0 63694e79 7365526b 736e6f70 72570065 yNickResponse.Wr
05adfa00 33657469 5f495f33 72657355 70616f53 ite33_I_UserSoap
05adfa10 69725700 34336574 63694e5f 6978456b .Write34_NickExi
05adfa20 57007473 65746972 4e5f3533 456b6369 st.Write35_NickE
05adfa30 74736978 70736552 65736e6f 69725700 xistResponse.Wri
05adfa40 36336574 555f495f 53726573 0070616f te36_I_UserSoap.
05adfa50 74697257 5f373365 6b63694e 73697845 Write37_NickExis
05adfa60 55794274 49726573 72570044 33657469 tByUserID.Write3
05adfa70 694e5f38 78456b63 42747369 65735579 8_NickExistByUse
05adfa80 52444972 6f707365 0065736e 74697257 rIDResponse.Writ
05adfa90 5f393365 73555f49 6f537265 57007061 e39_I_UserSoap.W
05adfaa0 65746972 475f3034 73557465 44497265 rite40_GetUserID
05adfab0 69725700 31346574 7465475f 72657355 .Write41_GetUser
05adfac0 65524449 6e6f7073 57006573 65746972 IDResponse.Write
05adfad0 495f3234 6573555f 616f5372 72570070 42_I_UserSoap.Wr
05adfae0 34657469 65475f33 65735574 6f634972 ite43_GetUserIco
05adfaf0 7257006e 34657469 65475f34 65735574 n.Write44_GetUse
通过之后一列的隐约信息,可以判定问题在那里了;当然你还需要多看几个这样的动态Module才能判定是否真是那个地方发生了问题。
从上面的分析可以明显的看出来,问题在I_UserSoapClient这个对象。那么看看这个对象的特点。
它是通过svcutil.exe http://xxxxx.com/I_User.asmx等都是标记为System.Xml.Serialization代理对象,和之前分析的核心服务Video不同 在于这个服务是一个纯的web服务,也就是wcf之前的asmx形式的web服务。
它生成的代理都是标记为System.Xml.Serialization,这个就是问题所在了。当我们调用这个服务的时候就产生一个动态的程序集,所以频繁的调用就会产生CUP长期过高,内存狂涨的问题了。高兴一把!下面就是这个代理类的样子:
[System.CodeDom.Compiler.GeneratedCodeAttribute("svcutil", "3.0.4506.30")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://tempuri.org/")]
public partial class UserCommon
{
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public int UACUserID
{
get
{
return this.uACUserIDField;
}
set
{
this.uACUserIDField = value;
}
}
}
解决:我们每次调用这个服务的就要动态产生这么多东西,而且都是无状态的,那何不缓存起来呢。我们这里采用的是对象池。正如连接池一样,我们要调用这个服务,建立服务连接通道,就到我们的对象池中去取;用完就归还到池中。当然客户代码调用服务的时候,都必须通过这么一个池去完成,而不能随意的new一个。
public partial class I_UserSoapClient : System.ServiceModel.ClientBase<I_UserSoap>, I_UserSoap,System.IDisposable,
XXX.IWcfClientPoolElement<I_UserSoapClient>
{
private XXX.WcfClientPool<I_UserSoapClient> pool = null;
///实现IDisposable接口后,我们通过using方式访问自然此方法,
///所以也是很方便的
void System.IDisposable.Dispose()
{
Pool.Restore(this);
}
}
客户端调用服务的时候必须从池中取:
using (I_UserSoapClient objClient = pool.GetClient())
{
//……….
}
关于对象池的构建,有很多方式。这里就不列出我们的方法了。但是一定要注意多线程条件下并发问题。
至此,系统不稳定问题得以解决了。大功告捷,系统内存和CPU都保持比较平稳的水平,强行回收已经没有再发生了。
结论:WCF对纯Web服务(.asxm,需要XML序列化)的调用是存在内存泄露问题的。