淘宝前台系统优化实践“吞吐量优化”


1 淘宝前台系统优化实践 “吞吐量优化” —— 淘宝网,蒋江伟 —— xiaoxie@taobao.com 1 淘宝业务增加迅猛 2 0 200,000,000 400,000,000 600,000,000 800,000,000 1,000,000,000 1,200,000,000 1,400,000,000 2007-05-08 2008-5-8 2009-5-8 2010-5-8 pv pv 0 5,000,000 10,000,000 15,000,000 20,000,000 25,000,000 30,000,000 35,000,000 40,000,000 2007-05-08 2008-5-8 2009-5-8 2010-5-8 uv uv 3 0 50 100 150 200 250 300 2009-1-X 2009-3-x 2009-6-x 2010-2-x 2010-5-x 某前台系统服务器数量 服务器 服务器总量超过15000 100000 Choice 持续增加的服务器数量 VS 持续提升单机吞吐量 运维、管理 成本持续增加 增加单机吞吐量1倍,服务器数量减少1倍 在服务器数量达到一定级别的时候,非常合算 4 • 目的 – 控制服务器增长数量 • 主题 – 提升淘宝前台系统单服务器的QPS 5 主要内容 • QPS(吞吐量)三要素 • 优化模板 – 至少提升50% • 优化大数据的处理 – 至少提升5% • 优化jvm参数 – 合理配置young区的大小(0%~100%) – 减少GC的总时间 • 保持优化的成果 – Daily load running – Daily hotspot code analysis 6 QPS的3要素 • 线程 • 响应时间 • 瓶颈资源 7 线程 • 设置多少线程合适? – 设置过少 – 设置过多 • 线程过多导致QPS下降 8 有个系统:线程数量在12~20之间的时候QPS几乎稳定在120左 右,但是一旦线程数量超过30时候,FGC开始频繁,由于FGC导致 的线程被挂起时间变成了整个系统的瓶颈,QPS下降,随着线程数 量的增加,QPS下降非常明显,线程数量在100的时候,QPS只有 60左右 1、对象生命周期 2、内存占用总量 设置多少线程合适? • CPU+1 • CPU-1 9 有这样一个模块 •cpu计算时间18ms(running) •查询数据库,网络io时间80ms(waiting) •解析结果2ms 如果服务器2CPU,大家看看这里多少线程合适? 18 80 2 充分利用CPU资源: 线程数量=100/20 * 2 =10 • 所以从CPU角度而言 线程数量=((CPU时间+CPU等待时间) / CPU时间) * CPU数量 10 • 线程数量的设置就是由CPU决定的? 有这样一个模块: •线程同步锁(数据库事务锁)50ms •cpu时间18ms •查询数据库,网络io时间80ms •解析结果2ms 如果服务器有2个CPU,这个模块线程多少 合适? 18 50 2 80 lock unlock • 以CPU计算为瓶颈,计算线程数量 – 线程数=(18 + 2 + 50 + 80) / 20 * 2 = 15 • 以线程同步锁为瓶颈,计算线程数 – 线程数=(50 + 18 + 2 + 80) / 50 * 1/1 = 3 11 公式1: 线程数量=(线程总时间/瓶颈资源时间) * 瓶颈资源的线程并行数 准确的讲 瓶颈资源的线程并行数=瓶颈资源的总份数/单次请求占用瓶颈资源的份数 约束: 在计算的时候,对同一类资源的消耗时间进行合幵 18 50 2 80 lock unlock QPS的3要素2 • 响应时间 – QPS = 1000/响应时间 – QPS = 1000/响应时间 * 线程数量 – 响应时间决定QPS? 12 分析数据(10) 搜索商城 (50) 搜索产品(25) 搜索商品(100) 处理结果(15) cpu waiting waiting waiting cpu QPS = 线程数量 * 1000/响应总时间 QPS = 32 * 1000/(10 + 50 + 25 + 100 + 15 ) = 160 线程数量=线程总时间/瓶颈资源时间 * 瓶颈资源幵行数 线程数量=(10 + 50 + 25 + 100 + 15 )/ (10 + 15) * 4 = 32 改进 分析数据(10) 搜索商城 (50) 搜索产品(25) 搜索商品(100) 处理结果(15) 13 分析数据(10) 搜索商城 (50) 搜索产品(25) 搜索商品(100) 处理结果(15) cpu waiting waiting waiting cpu 线程数量=线程总时间/瓶颈资源时间 瓶颈资源幵行数 线程数量=(10 + 100 + 15)/ (10 + 15) * 4= 20 QPS = 20 * 1000 / (10 + 100 + 15) = 160 总体响应时间变化 200ms—125ms 线程数量=线程总时间/瓶颈资源时间 * 瓶颈资源幵行数 QPS = 线程数量 * 1000/线程总时间 14 公式2: QPS = 1000/瓶颈资源时间 * 瓶颈资源并行数 汇总 • 公式1: 线程数量= 线程必须总时间/瓶颈资源时间 * 瓶颈资源幵行数 • 约束: 在计算的时候,对同一类资源的消耗时间进行合幵 • 公式2: QPS = 1000/瓶颈资源时间 * 瓶颈资源幵行数 15 QPS的3要素3 • 瓶颈资源 – 淘宝的前台系统的瓶颈资源是什么? 16 CPU • 淘宝前台系统特点 – 劢态页面渲染输出 – 页面非常大 – 数据来自多个远程服务 – 除了日志几乎没有对磁盘的读写 – 相对一些后台服务QPS低 17 系统 搜索 引擎 商品 中心 交易 中心 用户 中心 DB 数据 模板 渲染 影响系统QPS的瓶颈 18 CPU 内存GC hold Threads limit Thread Syn lock Remoting Sys QPS limit Disk IO Net IO • AB 压测系统,系统的CPU基本上都跑到了85%以上 19 模板渲染65% 搜索结果解析18% 优化模板2式 • 第1式:char to byte 20 测试例子1: private static String content = “…94k…”; protected doGet(…){ response.getWrite().print(content); } 测试例子2: private static String content = “…94k…”; Private static byte[] bytes = content.getBytes(); protected doGet(…){ response.getOutputStream().write(bytes); } 21 • 压测结果: 系统 页面大小(K) 最高QPS 测试例子1 Servlet print 94 1800 测试例子2 Servlet byte 94 3500 Char to byte • Why? – 1、通过,java.lang. StringCoding进行encode – 2、找到挃定的编码 Charset ,默认ISO8859-1 – 3、 利用CharsetEncoder的实现类,对每个Char转成byte 22 ”只做一次的事情丌要每次都做,可以预先 做的事情,预先处理”,一旦底层代码没有遵 循这个原则,那么影响是多么的深远 StringCoding.java static byte[] encode(String charsetName, char[] ca, int off, int len) throws UnsupportedEncodingException { StringEncoder se = (StringEncoder)deref(encoder); String csn = (charsetName == null) ? "ISO-8859-1" : charsetName; if ((se == null) || !(csn.equals(se.requestedCharsetName()) || csn.equals(se.charsetName()))) { se = null; try { Charset cs = lookupCharset(csn); if (cs != null) se = new StringEncoder(cs, csn); } catch (IllegalCharsetNameException x) {} if (se == null) throw new UnsupportedEncodingException (csn); set(encoder, se); } return se.encode(ca, off, len); } ------------StringEncoder.class byte[] encode(char[] ca, int off, int len) { int en = scale(len, ce.maxBytesPerChar()); byte[] ba = new byte[en]; if (len == 0) return ba; ce.reset(); ByteBuffer bb = ByteBuffer.wrap(ba); CharBuffer cb = CharBuffer.wrap(ca, off, len); try { CoderResult cr = ce.encode(cb, bb, true); if (!cr.isUnderflow()) cr.throwException(); cr = ce.flush(bb); if (!cr.isUnderflow()) cr.throwException(); } catch (CharacterCodingException x) { // Substitution is always enabled, // so this shouldn't happen throw new Error(x); } return safeTrim(ba, bb.position(), cs); } } ISO_8859_1.java private CoderResult encodeArrayLoop(CharBuffer src, ByteBuffer dst) { char[] sa = src.array(); int sp = src.arrayOffset() + src.position(); int sl = src.arrayOffset() + src.limit(); assert (sp <= sl); sp = (sp <= sl ? sp : sl); byte[] da = dst.array(); int dp = dst.arrayOffset() + dst.position(); int dl = dst.arrayOffset() + dst.limit(); assert (dp <= dl); dp = (dp <= dl ? dp : dl); try { while (sp < sl) { char c = sa[sp]; if (c <= '\u00FF') { if (dp >= dl) return CoderResult.OVERFLOW; da[dp++] = (byte)c; sp++; continue; } if (sgp.parse(c, sa, sp, sl) < 0) return sgp.error(); return sgp.unmappableResult(); } return CoderResult.UNDERFLOW; } finally { src.position(sp - src.arrayOffset()); dst.position(dp - dst.arrayOffset()); } } 淘宝对Velocity进行了重构 • 利用 char to byte (100%) • 解析执行改成了编译后执行 (10% ) 23 50% 疑问 • 解析执行转编译后执行的效果 24 1、循环 2、条件判断 3、渲染取值 0 50 100 150 200 250 300 350 400 450 java&jsp velocity mvel 综合 综合 25 "Hello, my name is ${name.toUpperCase()}, " "#foreach($user in $group.users) - ${user.id} - ${user.name} #end " "Hello, my name is ${name.toUpperCase()}, “ " …5k…" "#foreach($user in $group.users) - ${user.id} - ${user.name} #end " 0 500 1000 1500 2000 2500 3000 3500 4000 4500 jsp velocity mvel 综合测试 综合测试 总结 • Char 2 byte • 规模效益 – 取决于脚本(判断,循环,比较,赋值)占整个模板的比例 26 优化模板2式 • 第2式:减少模板大小 27 35% 页面从170K下降到110K • 减少模板大小的方法 – 压缩模板的空白字符 – 重复数据合幵 – 异步渲染 28 • 压缩空白字符 – 压缩哪些 – 何时压缩 – 工具 29 • 重复数据合幵 – 对一些系统非常有效,凡是代码里涉及到了循环,幵且里面有静 态内容输出均可以采用此方法 30 信用卡 …. 2个线程进行压测: ab –c1 -n10000 "http://localhost/perf.jsp" 20个线程进行压测: ab –c20 -n10000 "http://localhost/perf.jsp" 3个线程进行压测: ab –c3 -n10000 "http://localhost/perf.jsp" 编写GC有好的代码 { StringBuffer a = new StringBuffer(“……”); a.append(“…..”); … Object c = SearchManager.search(b); // waiting 100ms ….. } 改进之后 { StringBuffer a = new StringBuffer(“……”); a.append(“…..”); ….. a = null; Object c = SearchManager.search(b); // waiting 100ms ….. } 47 1、对象a的生命周期=方法的生命周期 2、被gc的时间至少>100ms 1、对象a的生命周期在a=null之后结束 2、可以被随时回收 3、一般认为一个耗时的方法之前的对象尽可能对GC优化 这里触发gc的概率99%以上 总结 • 调整yong区的大于 – 大于每次请求的消耗内存的100倍 • 减少GC的总时间 – 最佳实践,在一些远程调用方法之前,尽量释放掉对象的引用 48 保持优化成果 • Daily load running • Daily hotspot code analysis 49 50 60Kbyte 6Mbyte 600Mbyte <100倍 >100倍 淘宝前台系统的一些规律 51 xiaoxie@taobao.com 附录:平均单次请求内存消耗计算方法 • 每个请求占用的内存= Eden /(QPS * minorGC的平均 间隔时间(秒)) 52 杭州站 · 2011年10月20日~22日 www.qconhangzhou.com(6月启动) QCon北京站官方网站和资料下载 www.qconbeijing.com
还剩52页未读

继续阅读

pdf贡献者

jyx781004

贡献于2011-08-24

下载需要 20 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf