淘宝前台系统优化实践“吞吐量优化”——淘宝网,蒋江伟——xiaoxie@
淘宝业务增加迅猛pv1,400,000,0001,200,000,0001,000,000,000800,000,000600,000,000pv400,000,000200,000,00002007-05-082008-5-82009-5-82010-5-8uv40,000,00035,000,00030,000,00025,000,00020,000,000uv15,000,00010,000,0005,000,00002007-05-082008-5-82009-5-82010-5-82
某前台系统服务器数量300250200100000150服务器100服务器总量超过150005002009-1-X2009-3-x2009-6-x2010-2-x2010-5-x3
Choice持续增加的服务器数量VS 持续提升单机吞吐量运维、管理成本持续增加增加单机吞吐量1倍,服务器数量减少1倍在服务器数量达到一定级别的时候,非常合算4
•目的–控制服务器增长数量•主题–提升淘宝前台系统单服务器的QPS5
主要内容•QPS(吞吐量)三要素•优化模板–至少提升50%•优化大数据的处理–至少提升5%•优化jvm参数–合理配置young区的大小(0%~100%)–减少GC的总时间•保持优化的成果–Daily load running–Daily hotspot code analysis6
QPS的3要素•线程•响应时间•瓶颈资源7
线程•设置多少线程合适?–设置过少1、对象生命周期–设置过多2、内存占用总量•线程过多导致QPS下降有个系统:线程数量在12~20之间的时候QPS几乎稳定在120左右,但是一旦线程数量超过30时候,FGC开始频繁,由于FGC导致的线程被挂起时间变成了整个系统的瓶颈,QPS下降,随着线程数量的增加,QPS下降非常明显,线程数量在100的时候,QPS只有60左右8
设置多少线程合适?•CPU+1•CPU-1有这样一个模块•cpu计算时间18ms(running)•查询数据库,网络io时间80ms(waiting)•解析结果2ms如果服务器2CPU,大家看看这里多少线程合适?18802充分利用CPU资源:线程数量=100/20 * 2 =109
•所以从CPU角度而言线程数量=((CPU时间+CPU等待时间) / CPU时间)* CPU数量•线程数量的设置就是由CPU决定的?有这样一个模块:•线程同步锁(数据库事务锁)50ms •cpu时间18ms•查询数据库,网络io时间80ms•解析结果2ms 如果服务器有2个CPU,这个模块线程多少合适?5018802lockunlock10
5018802lockunlock•以CPU计算为瓶颈,计算线程数量–线程数=(18 + 2 + 50 + 80) / 20 * 2 = 15•以线程同步锁为瓶颈,计算线程数–线程数=(50 + 18 + 2 + 80) / 50 * 1/1 = 3公式1:线程数量=(线程总时间/瓶颈资源时间)* 瓶颈资源的线程并行数准确的讲瓶颈资源的线程并行数=瓶颈资源的总份数/单次请求占用瓶颈资源的份数约束:在计算的时候,对同一类资源的消耗时间进行合幵11
QPS的3要素2•响应时间–QPS = 1000/响应时间–QPS = 1000/响应时间* 线程数量–响应时间决定QPS?分析数据(10)搜索商城(50)搜索产品(25)搜索商品(100)处理结果(15)cpuwaitingwaitingwaitingcpu线程数量=线程总时间/瓶颈资源时间* 瓶颈资源幵行数线程数量=(10 + 50 + 25 + 100 + 15 )/ (10 + 15) * 4 = 32QPS = 线程数量* 1000/响应总时间QPS = 32* 1000/(10 + 50 + 25 + 100 + 15 )= 16012
改进分析数据(10)搜索商城(50)搜索产品(25)搜索商品(100)处理结果(15)cpuwaitingwaitingwaitingcpu分析数据(10)搜索商城(50)总体响应时间变化搜索产品(25)200ms—125ms搜索商品(100)处理结果(15)线程数量=线程总时间/瓶颈资源时间瓶颈资源幵行数线程数量=(10 + 100 + 15)/ (10 + 15) * 4= 20QPS = 20 * 1000 / (10 + 100 + 15) = 16013
线程数量=线程总时间/瓶颈资源时间* 瓶颈资源幵行数QPS = 线程数量* 1000/线程总时间公式2:QPS = 1000/瓶颈资源时间* 瓶颈资源并行数14
汇总•公式1:线程数量= 线程必须总时间/瓶颈资源时间* 瓶颈资源幵行数•约束:在计算的时候,对同一类资源的消耗时间进行合幵•公式2:QPS = 1000/瓶颈资源时间* 瓶颈资源幵行数15
QPS的3要素3•瓶颈资源–淘宝的前台系统的瓶颈资源是什么?CPU16
•淘宝前台系统特点–劢态页面渲染输出–页面非常大–数据来自多个远程服务–除了日志几乎没有对磁盘的读写搜索引擎–相对一些后台服务QPS低商品系统中心交易中心DB用户中心数据渲染模板17
影响系统QPS的瓶颈Net IO Disk IOCPUThread Synlock内存GC holdRemotingSys QPS limitThreads limit18
•AB 压测系统,系统的CPU基本上都跑到了85%以上搜索结果解析18%模板渲染65%19
优化模板2式•第1式:char to byte测试例子1:private static String content = “…94k…”;protected doGet(…){().print(content);}测试例子2:private static String content = “…94k…”;Private static byte[] bytes = ();protected doGet(…){().write(bytes);}20
•压测结果:系统页面大小(K)最高QPS测试例子1941800Servletprint测试例子2943500Servletbyte21
StringEncoderse = (StringEncoder)deref(encoder);String csn= (charsetName== null) ? "ISO-8859-1" : charsetName;if ((se == null) || !((())Cha|| rcsn. equtalso( rsebtNamey()))) {se = null;try {Charsetcs= lookupCharset(csn);te if (cs!= null)se = new StringEncoder(cs, csn);} catch (IllegalCharsetNameExceptionx) {}if (se == null)throw new UnsupportedEncodingException(csn);•Whyset?(encoder, se);}return (ca, off, len);–1}、通过,. StringCoding进行encode–2----、--------找String到Enco挃定ass的编码Charset,默认ISO8859-1byte[] encode(char[] ca, intoff, intlen) {–3i、nten =利 scale用(len, (n));byte[] ba= new byte[en];coder的实现类,对每个Char转成byteif (len== 0)return ba;();ByteBufferbb = (ba);CharBuffercb= (ca, off, len);try {CoderResultcr= (cb, bb, true);if (!())();cr= (bb);”if 只(!做nderflo一w())();} catch (CharacterCo次dingEx的ception事x) {情丌要每次都做,可以预先做// S的ubstitut事ion is a情lways e,nabled,// so this shouldn't happenthrow new Error(x);预先处理”,一旦底层代码没有遵循这}return个 safeT原rim(ba则, ,sition那(), cs);}么影响是多么的深远} CoderResultencodeArrayLoop(CharBuffersrc,ByteBufferdst){char[] sa= ();intsp = () + ();intsl= () + ();assert (sp <= sl);sp = (sp <= sl ? sp : sl);byte[] da= ();22intdp= () + ();intdl = () + ();assert (dp<= dl);dp = (dp <= dl ? dp : dl);
淘宝对Velocity进行了重构•利用char to byte (100%)•解析执行改成了编译后执行(10%)50%23
疑问•解析执行转编译后执行的效果综合4504001、循环3502、条件判断3003、渲染取值250200综合150100500java&jspvelocitymvel24
"Hello, my name is ${()}, ""#foreach($user in $) -${} -${} #end ""Hello, my name is ${()}, “" …5k…""#foreach($user in $) -${} -${} #end "综合测试450040003500300025002000综合测试150010005000jspvelocitymvel25
总结•Char 2 byte •规模效益–取决于脚本(判断,循环,比较,赋值)占整个模板的比例26
优化模板2式•第2式:减少模板大小35%页面从170K下降到110K27
•减少模板大小的方法–压缩模板的空白字符–重复数据合幵–异步渲染28
•压缩空白字符–压缩哪些–何时压缩–工具29
•重复数据合幵–对一些系统非常有效,凡是代码里涉及到了循环,幵且里面有静态内容输出均可以采用此方法<a href=" title="信用卡" class="creditcard" target="_blank">信用卡</a>….工具的支持?<a class="creditcard“ >信用卡</a>….+ javascript30
•异步渲染–将页面中静态幵且相对丌重要的内容抽取出来–利用专用服务器的优势异步加载结合专用ServerCDN化,Cache化31
总结•减少模板大小–压缩空白字符–合幵相同数据–异步渲染,利用专用服务器的优势•10%~100%以上内容的节省•QPS的提升10%~80%32
搜索结果解析模板渲染33
•优化大数据的处理–搜索结果解析案例•Byte 2 char–序列化方式的选择•thrift,protobuffervsjava序列化20W用户数据的测试时间PB比java序列化,性&能消耗减少2/3size34
优化jvm的参数•Yong区的比例调整•减少GC的总时间35
•某系统操作系统32位升级到64位•Yong区从500M增加到-Xmn2560m ==10070%36
•对其他系统进行yong区调整,QPS幵没有有效的提升根据每日的压测报表信息,统计得到一些规律:系统平均平均每次Young页面大小请求消耗内存区大小A(效果明显)110K12M500MB(丌明显)(丌明显)
•申请3Mbyte内存,-XX:NewSize=78M~978M,统计QPS–NewSize越大QPS越高–NewSize越大GC时间越短NewSize(M)QPS(3M)QPS(4M)QPS(5M)QPS(6M)QPS(7M)GC(count)GC(real)
•每个点,理论上响应时间应该是一样,波劢,说明线程被hold了1200328/3=1091000378/4=94800QPS(3M)528/5=105600QPS(4M)QPS(5M)QPS(6M)678/6=113400QPS(7M)778/7=11120003978128178228278328378428478528578628678728778828878928978
GC时间包括Full GCGC总时间GC总时间(毫秒)时间和minorGC时间1601401201008060GC(real)40200横轴:young区(M)40
小结•Yong区大小至少要大于每次请求内存消耗的100倍–Old区被挤占的问题–单次minorgc是否会变长的问题41
Jvm参数优化-减少GC总时间•减少GC总时间PERMEDENOLDS0S1对于淘宝前台系统而言,年老代区一般存放的内容是什么?42
垃圾回收step•1、对象在Eden区完成内存分配•2、当Eden区满了,再创建对象,会因为申请丌到空间,触发minorGC,进行young(eden+1survivor)区的垃圾回收•3、minorGC时,Eden丌能被回收的对象被放入到空的survivor(Eden肯定会被清空),另一个survivor里丌能被GC回收的对象也会被放入这个survivor,始终保证一个survivor是空的•4、当做第3步的时候,如果发现survivor满了,则这些对象被copy到old区,或者survivor幵没有满,但是有些对象已经足够Old,也被放入Old区XX:MaxTenuringThreshold•5、当Old区被放满的之后,进行完整的垃圾回收43
•数据进入年老代的3个途径–直接进入Old区•超过挃定size的数据•比较少见,一般一下子申请大片缓冲区–minorGC触发时,交换分区S0或者S1放丌下•缓存数据•因为线程执行周期缓慢导致未释放的对象的量太多了–足够老的数据,在交换区拷贝次数超过了上限(XX:MaxTenuringThreshold=15)•缓存数据•因为线程执行周期缓慢导致未释放的对象44
example1•-Xmx256m -Xmn15m -XX:SurvivorRatio=6–Eden –S0 –Old 241MJSP<%intsize = (int)(1024 * 1024 * m1);、申请1M内存byte[] buffer = new byte[size];、等待10秒钟(s);%>1个线程进行压测:ab–c1 -n1000 "http://localhost/ -n1000 "http://localhost/ -n1000 “http://localhost/
example2JSP<%intsize = (int)(1024 * 1024 * );byte[] buffer = new byte[size];buffer = null;(10);%>2个线程进行压测:ab–c1 -n10000 "http://localhost/"3个线程进行压测:ab–c3 -n10000 "http://localhost/"20个线程进行压测:ab–c20 -n10000 "http://localhost/"46
编写GC有好的代码{StringBuffera = new StringBuffer(“……”);(“…..”);…Object c = (b); // waiting 100ms这里触发gc的概率99%以上…..1、对象a的生命周期=方法的生命周期}2、被gc的时间至少>100ms改进之后{StringBuffera = new StringBuffer(“……”);(“…..”);…..1、对象a的生命周期在a=null之后结束2、可以被随时回收a = null;3、一般认为一个耗时的方法之前的对象尽可能对GC优化Object c = (b); // waiting 100ms…..}47
总结•调整yong区的大于–大于每次请求的消耗内存的100倍•减少GC的总时间–最佳实践,在一些远程调用方法之前,尽量释放掉对象的引用48
保持优化成果•Daily load running•Daily hotspot code analysis49
淘宝前台系统的一些规律60Kbyte6Mbyte600Mbyte<100倍>100倍50
xiaoxie@
附录:平均单次请求内存消耗计算方法•每个请求占用的内存= Eden /(QPS * minorGC的平均间隔时间(秒))52