Web应用单点压力测试调优-第6季-阶段性总结

标签: web 应用 压力测试 | 发表时间:2014-03-14 12:24 | 作者:
出处:http://www.iteye.com

 

阶段性总结

 

<!--[if !supportLists]-->1)  <!--[endif]-->应用的测试用例类型其实是简单的随机get几条json信息。并没有复杂的业务逻辑处理(权限验证等等),所以在业务上基本没有可优化的余地,转成异步的队列请求操作也是得不偿失。像注册用户、提交评论、上传图片等等这些耗时、与下一个操作无关联的、又是POST的HTTP请求就可以使用生产者与消费者模式进行(story待改变)。

 

<!--[if !supportLists]-->2)  <!--[endif]-->首先保证http的连接都能够正确的进行接入,tomcat的连接器模块,默认的bio+默认的连接池个数,连接代价过大,并且操作系统需要创建过多的线程(连接)进行维护。至于http的长短连接,基本上随着并发量的增加,可以使用默认的keep-alive。因为采用NIO本身就是为了复用多路通道达到长连接的目的,而随着并发量的增大,原先的看似比较短暂的连接也会变成长连接(时间耗费在:缓存穿透、JVM GC回收、缓存Hash表的扩容、读写锁的阻塞、网络以及磁盘的IO耗时、网络数据的解码-编码、Java程序的运行、Web容器的生命周期流转等等环节)

 

<!--[if !supportLists]-->3)  <!--[endif]-->应用程序日志,为了避免过多的磁盘IO,内存交换、CPU二级缓存的读写,只能将其调整为ERROR模式或者再升一级INFO模式,ERROR为了排查问题、INFO模式基本上是为了分析运行日志,预测网站下一个阶段在哪些功能上有比较高的访问以及使用需求。将运行日志(http访问日志、应用业务日志、耗时日志、MySql操作日志、Linux系统运行日志等等)放到Hadoop中进行建模->分析->出结果。可以预测网站下一步要做哪些事情,才能预防”雪崩”的事件发生。

保证了http的请求后,下面就是根据业务场景进行相关优化。

 



 

主要是代码优化,代码是指从Spring MVC的控制层开始一直到mysql server之前。1-从MVC的控制层进行优化,首先将grid上面访问两次数据库(一次是分页查询数据,一次是查询记录总个数)的业务改为一次。因为互联网对于数据一致性(CAP理论)的要求不是很高。所以,启动Web容器的时候,将所有业务表的记录先select count(*)一遍,将结果存入到一个memory表中,索引类型为hash类型。之后再将其键值对儿(表名:记录个数)缓存到本地内存。每20分钟(可根据后期实际情况变更设置-主要参考点是:数据的时效性)一次查询。只要数据库服务没关闭或者宕掉,memory就会存储这一时段的记录数。

 

如此做有两个目的:第一,减少一次db连接访问数;第二,如果记录结果过大(比如表记录的数量级超过了100w左右)每次的count操作很慢,不如就记录一定时间内的记录个数,得出的记录虽然不准确,但是,也不会影响业务查询处理。

 

2-之后进入到持久层,将JDBC要执行的SQL写成预编译语句,这样mysql底层引擎不必每次都编译类似,甚至相同的sql语句,节省了中间过程,不过要保证mysql的欲编译空间要够。

 

3-下面就是最关键的20%解决了80%的本地内存缓存了。将热点数据缓存到内存中,本次压力测试,假设了新闻记录是热点数据(经常访问的vo list),将其缓存到了本地内存中。配合上JDK的读写锁——ReentrantReadWriteLock。

4-持久层循环,在持久层循环传入预编译参数的时候,可以跳跃性的传递,将循环步长值+2,减少循环的次数。

		Connection connection = null;
		PreparedStatement statement = null;
		ResultSet resultSet = null;
		int length = parms.length;
		try {
			connection = dataSource.getConnection();
			statement = connection.prepareStatement(sql);
			for (int i = 0; i < length; i=i+2) {
				
				if(i!=length-1){
					statement.setObject(i + 1, parms[i]);
					statement.setObject(i + 2, parms[i+1]);
				}else{
					statement.setObject(i + 1, parms[i]);
				}
				
				
			}
			resultSet = statement.executeQuery();
			List list = AnalystResultSet.getBeans(sql, clazz, resultSet);
			return list;
		}

 

 

这样做的好处不仅仅是减少循环,还让connection对象可以快速“还给”资源池。

 

5-内部orm框架缓存表结构,字段结构以及实体分析结果。获取ResultSet的元信息,一次即可,存储到本地缓存中,下次访问直接从缓存里面去取(表名:Bean结构、SQL:字段数组)。将反射后的method对象也要进行缓存,方便后续直接拿出来就用。

 

6-返回json数据替换原始的jackson为fastjson。网上有测试,证明阿里巴巴的fastjson性能高于jackson(http://www.iteye.com/topic/1113183)

 

7-其他代码级别优化,初始化map构造函数、初始化stringbuilder初始化函数、初始化List构造函数,缓存反射实体等等。

 

<!--[if !supportLists]-->1)  <!--[endif]-->JVM相关优化

 

GC方面:采用 UseAdaptiveSizePolicy,将自动调整新生代中eden和sub1、sub2的比例关系,SurvivorRatio=XXXX失效。-server配置后,年轻代采用标记-复制算法(parallel scavenge)、老年代采用标记-整理算法(serial old),保证吞吐量优先,也是-server的默认配置,策略为新生代使用并行清除,年老代使用单线程Mark-Sweep-Compact的垃圾收集器(因为虚拟机是单核CPU)。MaxTenuringThreshold=20,年轻代的年龄为20次,20次未能回收,直接放到老年代。年轻代是并行收集,老年代是串行。因此后续的配置将年轻代配的比较大。(根据此应用需求,大多数对象数据,朝生夕灭)。

 

内存大小:-XX:PermSize=82M -XX:MaxPermSize=82M -Xss256k -Xms480m -Xmx480m -Xmn320m,根据上面GC考虑以及JMX观察,持久带不需要过多的内存,90mb左右足够,堆内存最大480m内存,年轻代占了将近66.7%左右。每个线程只占用256k的栈内存,调小此项可以建立更多的线程。因为此栈调用无递归等复杂操作,所以256k栈深度已经足够了。

 

锁优化:因为JDK6已经默认启用了很多锁的优化,所以未进行干扰(http://kenwublog.com/docs/java6-jvm-options-chinese-edition.htm),如果修改也是进行自旋次数的设置-XX:PreBlockSpin=10,默认10次检测。根据业务调整以及竞争度进行调整。

 

其他方面:+UseFastAccessorMethods:优化原始类型的getter方法性能;在非调试环境下去除各种gc log配置;-Xverify:none,因为暂时没有不可控的代码,默认去除类加载中的类合法性的验证环节。-Djava.nio.channels.spi.SelectorProvider强指定NIO的提供者是oracle的实现——sun.nio.ch.EPollSelectorProvider;若系统稳定了,可以去除JMX监控,看性能指标是否略有提升。

 

(JVM的调优还可进一步进行,主要关注点还是,GC组合算法、锁、分代年龄、若内存减少数据库方面的消耗的话,还可以进一步剥削,堆内存可调大,栈内存继续略调小)

 

<!--[if !supportLists]-->2)  <!--[endif]-->数据库优化

 

最难的当属数据库优化,因为它既牵扯着业务(数据结构、数据记录内容)也牵扯着非功能(表引擎、连接数、mysql相关配置)。

 

在应用层修改JDBC连接池的url配置,是为了让JDBC Connection连接都能开启相应的优化特性(仅在应用程序与mysql connection之间这座连接桥)比如:useServerPrepStmts=true是为了让连接支持长久的预编译statement。对于mysql以及应用程序,更能快速的互相通讯、请求-吞吐数据。

Mysql优化后的server方面的配置再次附上,附解说

 

#update start
#客户端最大连接数
max_connections=1500
#查询缓存,因为在应用内存中。所以此项调制较小(http://banu.blog.163.com/blog/static/2314648201077735322/)
query_cache_size=16M
#默认表引擎是带事务的INNODB
default-storage-engine=INNODB
#表缓存大小,同以上连接介绍。为所有线程打开表的数量。增加该值能增加mysqld要求的文件描述符的数量。MySQL对每个唯一打开的表需要2个文件描述符还是因为表中热点数据已经缓存到应用本地内存,等价于----->table_open_cache
#table_cache=256
#此设置是查询中如果出现了临时虚表,虚表的大小,每个查询线程都要占用内存,因为应用中使用了group by语句建立了临时表,所以需要设置此项,在业务中尽量避免group by(麻烦)
tmp_table_size=8M
#这个值表示可以重新利用保存在缓存中线程的数量,当断开 连接时如果缓存中还有空间,那么客户端的线程将被放到缓存中,如果线程重新被请求,那么请求将从缓存中读取,如果缓存中是空的或者是新的请求,那么这个线 程将被重新创建,如果有很多新的线程,这里整个操作系统的内存才1GB,那么设为8其实有点大。
thread_cache_size=8
#因为用例是连续扫描news表的前10页(每页15条)数据记录,所以为了避免连续扫描过慢,设置此读取缓存
read_buffer_size=64K
#类似于read_buffer_size选项,MySql的随机读(查询操作)缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。因为查询语句有个随机页数,非顺序读取。所以排序缓存应当设置上
read_rnd_buffer_size = 256K
#消息缓冲区被初始化为net_buffer_length字节,但是可在需要时增加到max_allowed_packet个字节。缺省地,该值太小必能捕捉大的(可能错误)包。如果你正在使用大的BLOB列,你必须增加该值。用例查询并未涉及到大字段的列,都是简单短型json字符数据,所以可以预估缓冲的大小。每个JDBC反馈的数据包缓冲大小8k差不多可以承载15条数据记录
net_buffer_length = 8K
#为所有线程打开表的数量。增加该值能增加mysqld要求的文件描述符的数量。MySQL对每个唯一打开的表需要2个文件描述符。表缓存大小,同以上连接介绍,还是因为表中热点数据已经缓存到应用本地内存---->等价于table_cache
table_open_cache = 16
#每个线程排序所需的缓冲
sort_buffer_size = 256K
#这个参数用来设置 InnoDB 存储的数据目录信息和其它内部数据结构的内存池大小,类似于Oracle的library cache。因为表的结构不复杂,索引信息也不复杂。所以2m有点大
innodb_additional_mem_pool_size=2M
#如果将此参数设置为1,将在每次提交事务后将日志写入磁盘。为提供性能,可以设置为0或2,但要承担在发生故障时丢失数据的风险。设置为0表示事务日志写入日志文件,而日志文件每秒刷新到磁盘一次。设置为2表示事务日志将在提交时写入日志,但日志文件每次刷新到磁盘一次
innodb_flush_log_at_trx_commit=0
#innodb的日志缓冲,同log4j
innodb_log_buffer_size=4M
# innodb_buffer_pool_size 定义了 InnoDB 存储引擎的表数据和索引数据的最大内存缓冲区大小。和 MyISAM 存储引擎不同, MyISAM 的 key_buffer_size 只能缓存索引键,而 innodb_buffer_pool_size 却可以缓存数据块和索引键。适当的增加这个参数的大小,可以有效的减少 InnoDB 类型的表的磁盘 I/O 。在一个以 InnoDB 为主的专用数据库服务器上,可以考虑把该参数设置为物理内存大小的 60%-80% (http://blog.zol.com.cn/2413/article_2412509.html),在此还是仅仅设置16m,因为热点数据缓存在了应用内存,除非缓存穿透了。
innodb_buffer_pool_size=16M
#每个log日志的大小,增大此数目可以减少日志个数,避免过多创建日志文件
innodb_log_file_size=128M
#服务器CPU有几个就设置为几,默认为多少,这是允许mysql同时处理的会话线程个数
innodb_thread_concurrency=1
#update over

 

 

单点还可以采取哪些手段?

 

1):增加像nginx这样的http服务器。开启静态缓存,用于缓存静态文件。不过此用例暂时不涉及页面,仅仅返回json data。如果和页面联调压力测试,需要加上此手段。

 

2):JDK6不支持AIO连接,不过tomcat有本地 Native APR连接器,若前端安装Nginx配合Native APR连接器,节省NIO开销,将连接压力踢给了操作系统。

 

3):Mysql数据库,还有进一步配置优化的余地,而且使用Mysql的变种mariadb以及Percona可以使用他们变种的优化特性。

4):操作系统,最后就是底层操作系统相关配置的优化。因为使用的是Centos操作系统,修改配置(IO、内存页、系统日志、关闭无关服务),升级磁盘系统(EXT3-EXT4),升级Linux内核。都可以达到优化操作系统的目的——待测试。

分享一个linux优化连接---- http://www.myhack58.com/Article/sort099/sort0102/2011/29507.htm

 

第一阶段到此为止,后续有内容,笔者会持续更新。。。。。



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [web 应用 压力测试] 推荐:

Web应用单点压力测试调优-第6季-阶段性总结

- - ITeye博客
1)  应用的测试用例类型其实是简单的随机get几条json信息. 并没有复杂的业务逻辑处理(权限验证等等),所以在业务上基本没有可优化的余地,转成异步的队列请求操作也是得不偿失. 像注册用户、提交评论、上传图片等等这些耗时、与下一个操作无关联的、又是POST的HTTP请求就可以使用生产者与消费者模式进行(story待改变).

十款免费的Web压力测试工具简介

- - 服务器运维与网站架构|Linux运维|互联网研究
下面是十个免费的可以用来进行Web的负载/压力测试的工具,这样,你就可以知道你的服务器以及你的WEB应用能够顶得住多少的并发量,以及你的网站的性能. Grinder –  Grinder是一个开源的JVM负载测试框架,它通过很多负载注射器来为分布式测试提供了便利. 支持用于执行测试脚本的Jython脚本引擎HTTP测试可通过HTTP代理进行管理.

(总结)Web性能压力测试工具之ApacheBench(ab)详解

- - 服务器运维与网站架构|Linux运维|互联网研究
PS:网站性能压力测试是性能调优过程中必不可少的一环. 只有让服务器处在高压情况下才能真正体现出各种设置所暴露的问题. Apache中有个自带的,名为ab的程序,可以对Apache或其它类型的服务器进行网站访问压力测试. ApacheBench命令原理:. ab命令会创建很多的并发访问线程,模拟多个访问者同时对某一URL地址进行访问.

(总结)Web性能压力测试工具之WebBench详解

- - 服务器运维与网站架构|Linux运维|互联网研究
PS:在运维工作中,压力测试是一项很重要的工作. 比如在一个网站上线之前,能承受多大访问量、在大访问量情况下性能怎样,这些数据指标好坏将会直接影响用户体验. 但是,在压力测试中存在一个共性,那就是压力测试的结果与实际负载结果不会完全相同,就算压力测试工作做的再好,也不能保证100%和线上性能指标相同.

压力测试工具集合【ab,webbench,Siege,http_load,Web Application Stress】

- - 小彰
-------------------------------------------------- 1 Apache附带的工具ab. ab的全称是ApacheBench,是Apache附带的一个小工具,专门用于HTTP Server的benchmark testing,可以同时模拟多个并发请求.

压力测试工具Benchmark

- - 企业架构 - ITeye博客
已有 0 人发表留言,猛击->> 这里<<-参与讨论. —软件人才免语言低担保 赴美带薪读研.

从 Newsstand 到 Web 应用

- - 爱范儿 · Beats of Bits
本地应用和 HTML5 谁将主导的争论持续了好几年,目前本地应用依然是绝对主流. 因此英国《金融时报》上周宣布将在 6 月 撤下 iPad 应用,完全依赖 HTML5 Web 应用的消息引来不少关注. 早在去年苹果宣布订阅政策时,《金融时报》就开始着手准备 Web 应用以绕开苹果 30% 的大刀.

使用webbench做压力测试

- - 三水清
元旦假期帮朋友折腾VPS,安装了centOS和lnmp,网站部好了,而且定时任务也加上了,觉得应该做个压力测试,之前用过apache自带的ab,不过今天使用的是 webbench做压力测试. 首先我们先来安装webbench吧, 为了测试准确,请将 webbench 安装在别的linux服务器上, 因为webbench 做压力测试时,自身也会消耗CPU和内存资源, 否则, 很可能把自己服务器搞挂掉.

Mysql压力测试shell脚本

- - CSDN博客数据库推荐文章
转载请注明,来自: http://blog.csdn.net/skyman_2001. Mysql自带了压力测试工具mysqlslap,所以我们可以不用自己编写程序来测试Mysql读取的压力. 上面脚本的意思是每隔100ms循环做这样的事:模拟100个mysql客户端,对数据库test的表test执行200次插入(number-of-queries = concurrency * 每个mysql客户端的查询次数,所以这里的每个mysql客户端的查询次数是2次),迭代10次.

Jmeter教程 简单的压力测试

- - 行业应用 - ITeye博客
Jmeter是一个非常好用的压力测试工具.   Jmeter用来做轻量级的压力测试,非常合适,只需要十几分钟,就能把压力测试需要的脚本写好.     做压力测试的常用工具.     做压力测试的步骤如下:.     本文做压力测试的例子.     第一步: 使用CSV Data Set Config 来参数化.