1 需求分析
得到新增页面的url 知道页面数据量等
2 开启数据库 log
数据库---general_log和slow_log或long_log
ps -ef|grep mysql
mysql -uroot -p -S /tmp/mysql.sock --port=3306
show databases;
use bbs7_auto_perf ;
show variables like '%gene%';
set global general_log =1;
show variables like '%slow%';
set global log_slow_queries =1;
打开并得到相应日志的位置
数据库打开相应日志进行查看 vi /usr/local/mysql/data/pftest-db-74-95.log
3 MC查看缓存次数
找出log4j.properties文件
find / -name 'log4j.properties'
修改该文件 vi /data/counter/autobbs7/mq/conf/log4j.properties
将所有ERROR级别修改为INFO级别,同时需要重启应用。
./https.... restart
/wap/index.jsp,344,DR96DU0CR105CU85,127.0.0.1
/wap/index.jsp,8,DR0DU0CR1CU0,127.0.0.1
其中DR:读数据库次数,DU:写数据库次数;CR:读MC次数,CU:写MC次数。
从第二次访问的情况,可知当页面缓存住后(写入MC次数=0),读MC次数=1,正常。
4 打开 accesslog 分析响应慢的uri
性能测试注意点:
在开展功能测试前,可以先把Resin的accesslog打开,等功能测试完后,再分析accesslog 响应时间比较长的uri。
功能测试完成后,可以使用accesslog分析工具,重点关注响应时间较慢的uri。
分析步骤:
对resin的accesslog格式转成nginx的accesslog格式。
/usr/bin/resinlog2nginx.sh /app/bbs7/resin-pro-3.1.8/logs/access.log > /app/bbs7/resin-pro-3.1.8/logs/nginx_access.log
执行分析脚本。
/usr/bin/wget.sh /app/bbs7/resin-pro-3.1.8/logs/nginx_access.log
1.1Mysql
General_log
#vi $resin_home/conf/resin.conf.XXX.xml |
#mysql -u root -p root -S /tmp/mysql.version_test.sock --port=3307 SQL >show variables like '%gene%'; // 检查general_log是否已开启及存放路径
general_log值为OFF,表示日志已关闭;ON,表示开启。 SQL >set global general_log=1; //开启 SQL >set global general_log=0; //关闭 |
Slow_log
#mysql -u root -p root -S /tmp/mysql.version_test.sock --port=3307 SQL >show variables like '%slow%'; // 检查slow_log是否已开启及存放路径
slow_query_log值为OFF,表示日志已关闭;ON,表示开启。 SQL >set global slow_query_log=1; //开启 SQL >set global slow_query_log=0; //关闭 ////slow_query_log 是全局变量,log_slow_queries是局部变量,只要全局变量开了,局部变量也会相应改变。
SQL >show variables like '%long%'; // 检查slow_log记录临界值
long_query_time值为1.0,表示查询>1s的sql将被记录到slow_log里。 SQL >set global long_query_time=0.5; //修改记录时间 ////需要重新连接mysql才能生效。 |
1.2Oracle(10g/11g)
#vi $resin_home/conf/resin.conf.XXX.xml |
#/data1/oracle/admin/test7410/trace_analyse/trace_analyse.sh AUTOPRICE_APP 1 ////试跑1min看下能不能记录成功,AUTOPRICE_APP为数据库用户名,1为1min
////采集结果 More /data1/oracle/admin/test7410/udump/Fetch.log有详细的sql,查询次数,平均查询时间的记录
////查看明细的SQL More /data1/oracle/admin/test7410/udump/20150622-11:23.log |
1.3Postgresql
#su - postgres #vi /usr/local/pgsql/data/postgresql.conf ² log_directory=‘pg_log’ ² log_destination = 'csvlog' (输出的日志格式为csv) ² logging_collector = on ² 修改log_min_duration_statement=500(单位为毫秒,500代表0.5s -1代表不记录,0表示全部记录) #pg_ctl reload
慢语句将被记录到/usr/local/pgsql/data/pg_log目录下 |
1.4Resin
Accesslog
#vi $resin_home/conf/resin.conf.XXX.xml format='%h %{X-Real-IP}i %l %{common_session_id}c %t "%r" %s %b %D "%{Referer}i" "%{X-Forwarded-For}i" "%{User-Agent}i"' archive-format="logs/access.log.%Y%m%d%H.gz" rollover-period="24h" rollover-count="48"/> |
Log4
#vi ./web/WEB-INF/classes/log4j.properties log4j.rootLogger=INFO, stdout log4j.logger.org.gelivable.web.EnvFilter = INFO, env ####批量将ERROR和WARM都改为INFO |
1.5Nginx
Accesslog
#vi $nginx_home/conf/nginx.conf log_format tpynormal '$remote_addr | [$time_local] | $host | "$request" | ' '$status | $body_bytes_sent | "$http_referer" | ' '"$http_user_agent" | "$http_x_forwarded_for" | ' '$upstream_addr | $upstream_status | $upstream_response_time | ' '$server_addr | $request_time | $cookie_common_session_id'; access_log logs/access_XXXX.log tpynormal; #access_log off; |
2.1Nginx
遇到类似配置,要去到对应的缓存文件夹将里面的文件及文件夹都删掉,重启nginx
#vi $nginx_home/conf/nginx.conf proxy_cache_path /usr/local/nginx/proxy_cache; proxy_cache cache_price; proxy_cache_key $host$uri$is_args$args; |
2.2Hcs
去到对应的文件夹,将缓存文件删掉,重启ktserver和hcs
#cd /app/hcs/hcs_storage #rm -rf casket_1971.kch |
2.3Ats
去到对应的文件夹,将缓存文件删掉,重启ats
#cd /data1/PRG/ts_cache #rm -rf cache.db |
2.4Squid
待补充
2.5Xsp
Xsp多用内存作缓存,重启xsp即可清除
2.6Resin
去到对应的文件夹,将缓存文件删掉,重启resin
#cd $resin_home #rm -rf cache/* |
2.7Xindex
xindex多用内存作缓存,重启xindex即可清除
2.8r系统
清空r系统配置的mc
# vi $resin_home/conf/r-route.conf.xml servers=192.168.74.54:11220,192.168.74.55:11220 |
2.9Mc
cl $mc_port ////将端口的内容都置为过期
cm $mc_port ////重启端口
注:
3.1重复sql优化
# cat test740_5.log|grep 'bbs7_' >tmp_file |
# tail -f test740_5.log|grep 'bbs7_' |
3.2Sql逻辑优化
SELECT counter_id FROM bk_counter WHERE object_id = 2933 AND object_type = 2; SELECT counter_id FROM bk_counter WHERE object_id = 2934 AND object_type = 2; SELECT counter_id FROM bk_counter WHERE object_id = 2935 AND object_type = 2; SELECT counter_id FROM bk_counter WHERE object_id = 2936 AND object_type = 2;
优化: 对多次类似的查询进行合并,减少查询次数,后续判断由程序处理, Select counter_id ,object_id FROM bk_counter WHERE object_type = 2;
|
Ps.将第一条sql的select id改为select *即可(代码可能使用框架查询)。与第一题不同是多了第一条sql
http://m.pcauto.com.cn/tongyong/tongyong_80.html http://v19.pcauto.com.cn:8003/wap/general/general.do?id=80
1014093 Query select id from seo_auto_tag where general_id = 80 and archive_count >= 3 order by heat desc 1014094 Query select * from seo_auto_tag where id=33785 1014092 Query select * from seo_auto_tag where id=68726 1014093 Query select * from seo_auto_tag where id=57273 1014094 Query select * from seo_auto_tag where id=56018 1014092 Query select * from seo_auto_tag where id=52401 1014093 Query select * from seo_auto_tag where id=48946 1014094 Query select * from seo_auto_tag where id=48278 1014092 Query select * from seo_auto_tag where id=46834 1014093 Query select * from seo_auto_tag where id=45914 1014094 Query select * from seo_auto_tag where id=39163 1014092 Query select * from seo_auto_tag where id=37203 1014093 Query select * from seo_auto_tag where id=71589 |
Ps. IN查询是会每个id查一次,有多少个id相当于查询了多少次,效率很低。
SELECT id FROM fk_questions WHERE 1 = 1 AND STATUS = 1 AND create_time <= '2014-11-14 13:21:40.0' AND process_status = 1 AND question_type_id IN ( '96','97','98','99','100','164','165','166','167','168','169','202','203','212','214','234','235','278','329','382','383','400','480','500', '523','524','531','534','552','553','554','560','567','586','588','592','634','647','662','663','702','703','704','705','707','718','719', '720','721','728','731','738','739','748','749','751','752','753','754','755','766','788','791','793','810','811','812','813','847','848', '880','884','885','958','977','978','980','981','984','1010','1011','1030','1179','1180','1195','1196','1197','1198','1200','1223', '1224','1225','1228','1261','1263','1265','1270','1271','1272','1273','1275','1276','1280','1287','1288','1293','1294','1296', '1301','1316','1327','1350','1353','1394','1402','1430','1450','1452','1458','1460','1474','1476','1534','1535','1560','1566', '1586','1596','1602','1616','1617','1618','1619','1621','1622','1623','1625','1629','1630','1631','1632','1633','1640','1650', '1653','1655','1657','1663','1668','1671','1677','1681','1683','1690','1702','1709','1710','1715','1717','1718','1719','1720', '1721','1736','1744','1745','1785','1796','1807','1812','1829','1836','1850','1856','1857','1884','1893','1895','1916','1931', '1945','1980','2003','2004','2005','2006','2007','2026','2027','2029','2042','2051','2055','2065','2066','2067','2068','2069', '2070','2071','2072','2077','2085','2120','2340','30061','30090','30100','30150','30152','30153','30154','30156','30171', '30201','30380','30410','30412','30442','30450','30470','30490','30491','30501','30512','30515','30530','30550','30630', '30660','30872','30900','30950','31000','31023','31061','31080','17','18','19','20','22','23','24','25','26','27' ) ORDER BY create_time DESC LIMIT 90, 30 ;
优化: 1.先对sql进行化简,化简之后效果跟原题的效果是一样的,只是更为简洁,方便进一步的优化: SELECT COUNT(id ) FROM fk_questions WHERE 1 = 1 AND STATUS = 1 AND create_time <= '2014-11-14 13:21:40.0' AND process_status = 1 AND question_type_id IN ( SELECT id FROM fk_question_type ) ORDER BY create_time DESC LIMIT 90, 30 ;
2.含有"IN"、"OR"的Where子句会使索引失效;拆开子句才会索引。将表fk_question_type从嵌套中释放出来。 SELECT COUNT(q.id) FROM fk_questions q, fk_question_type t WHERE 1 = 1 AND q.STATUS = 1 AND q.create_time <= '2014-11-14 13:21:40.0' AND q.process_status = 1 AND q.question_type_id = t.id ORDER BY q.create_time DESC LIMIT 90, 30 ; |
SELECT k.id FROM kl_knowledge k, kl_knowledge_count c WHERE c.knowledge_id = k.id AND k.status = 1 AND k.classify_id IN (SELECT id FROM kl_classify WHERE TYPE = 2 AND `status` = 1 AND parent_id = 191) OR k.classify_id = 191 GROUP BY k.id ORDER BY c.pv DESC LIMIT 0, 7 ;
优化: 1.先把in结构分解: EXPLAIN SELECT k.id FROM kl_knowledge k, kl_knowledge_count c, kl_classify cl WHERE c.knowledge_id = k.id AND ( ( cl.type = 2 AND cl.status = 1 AND cl.parent_id = 151 AND k.classify_id = cl.id ) OR k.classify_id = 151 ) AND k.status = 1 GROUP BY k.id ORDER BY c.pv DESC LIMIT 0, 7 ;
由于此处or中并列的是这两句sql:AND k.classify_id = cl.id OR k.classify_id = 151, 其他字段都可以移到外面去,所以对or进行调整: EXPLAIN SELECT k.id FROM kl_knowledge k, kl_knowledge_count c, kl_classify cl WHERE c.knowledge_id = k.id AND k.classify_id = cl.id AND cl.type = 2 AND cl.status = 1 AND ( cl.parent_id = 151 OR cl.id = 151 ) AND k.status = 1 GROUP BY k.id ORDER BY c.pv DESC LIMIT 0, 7 ;
|
3.3Sql索引整体优化
-------------------------------------------------------------------------- [ il_activity ] percent: 99.00% count: 19765925
# percent: 66.00% count: 13167578 + abstract: select * from il_activity where activityid=N + sample: select * from il_activity where activityId=17390
# percent: 33.00% count: 6583500 + abstract: update il_activity set * where activityid=N + sample: update il_activity set status=2 where activityId=14
# percent: 0.00% count: 13566 + abstract: select * from il_activity where status != N and createat < S limit N, + sample: select activityId from il_activity where status != 3 and createAt < '2015-07-13 09:25:01.0' LIMIT 0,500
# percent: 0.00% count: 399 + abstract: select * from il_activity where status != N and createat < S + sample: select count(1) from il_activity where status != 3 and createAt < '2015-07-13 09:25:01.0'
# percent: 0.00% count: 399 + abstract: select * from il_activity where publishstatus = -N and createat < S limit N, + sample: select activityId from il_activity where publishStatus = -1 and createAt < '2015-07-13 09:25:01.0' LIMIT 0,500
# percent: 0.00% count: 399 + abstract: select * from il_activity where publishstatus = -N and createat < S + sample: select count(1) from il_activity where publishStatus = -1 and createAt < '2015-07-13 09:25:01.0'
............
####针对下面查询较多的sql,可以增加createAt 单字段索引,一次能优化多条sql ####多字段sql优化方式同理,要注意索引前后的顺序。 |
3.4附:常用语句
各类数据库常用语句实例与对比
1.打印执行计划
Oracle
explain plan for select id from test.username order by id desc;
select * from table(DBMS_XPLAN.display);
Mysql
Explain select id from test.username order by id desc\G;
Mongodb
db.r_table.find({ }, { "_id": 1 }).sort({ "_id": 1 }).explain();
2. 实例
Oracle
SELECT COUNT(*)AS cou,
TO_CHAR(creation_date, 'dd-mon-yyyy hh24:mi:ss') as time
FROM ent_news
WHERE valid_begin_date BETWEEN sysdate-130 AND sysdate
GROUP BY TO_CHAR(creation_date, 'dd-mon-yyyy hh24:mi:ss')
ORDER BY cou DESC;
说明:
Mysql
查询一周:
select * from table where DATE_SUB(CURDATE(), INTERVAL 7 DAY) <= date(column_time);
查询一个月:
select * from table where DATE_SUB(CURDATE(), INTERVAL INTERVAL 1 MONTH) <= date(column_time);
SELECT 'select '''||table_name||''', count(*) from autoprice_0609.'||table_name||';'
FROM dba_tables
WHERE owner='AUTOPRICE_0609';
SELECT V.TABLE_NAME FROM SYS.DBA_TAB_COLUMNS V
WHERE V.COLUMN_NAME = 'AD_LEVEL'
AND V.OWNER='ADPUB_APP';
3.5慢语句分析
1.mysql
通过general日志,分析一些较复杂的SQL,一些简单的SQL查询,如果有用到主键作为索引的,不用再拿出来分析。
如分析如下SQL语句:
#use pclady_ptest; //进入对应的数据库
#explain select t.id from pt_topic t,pt_topic_info i where t.id=i.id and t.status=1 and t.create_at>=str_to_date('2014-09-08 00:00:00', '%Y-%m-%d %H:%i:%s') and t.create_at<=str_to_date('2014-09-21 23:59:59', '%Y-%m-%d %H:%i:%s') order by i.count desc,t.id desc LIMIT 0,8 \G
【添加索引】:
ALTER TABLE `pclady_ptest`.`pt_topic` ADD INDEX `idx_ptopic_createAt` (`create_at`);
【补充】:
对应上述添加的索引,如果修改为以下索引,发现会对另外一个查询SQL有影响。
ALTER TABLE `pclady_ptest`.`pt_topic` DROP KEY `idx_ptopic_createAt`, ADD INDEX `idx_ptopic_createAt` (`status`, `create_at`);
被影响的查询SQL:
select id from pt_topic where id != 5918 and type_id = 1 and status=1 order by create_at desc LIMIT 0,5
同时通过profiling命令得到更准确的SQL执行消耗系统资源的信息,profiling默认是关闭的(即退出数据库终端后,便关闭)。可以通过以下语句查看:
#select @@profiling; //查看profiling的状态
#set profiling=1; //打开功能
#select id from pt_topic where id != 5918 and type_id = 1 and status=1 order by create_at desc LIMIT 0,5 //执行需要查询的SQL
#show profiles\G; //得到被执行的SQL语句的时间和ID
对比添加复合索引及单索引时,SQL执行的情况,可知使用单索引时,该SQL查询较快。
关于索引优化的参考blog:
http://blog.chinaunix.net/uid-11640640-id-3426908.html
http://www.phpddt.com/db/mysql_explain.html
优化索引的原则:
按最多过滤数据的字段在前的原则建索引,查询条件后面的字段建立索引
select entity,categoryid,status,count(*) from intelhelper_schedule group by entity,categoryid,status order by count(*) desc
categoryid的不同值最多,entity只有两种值,status只有三种,按过滤最多值放在在第一位的原则,建复合索引 顺序可以是 categoryid,entity,status
2.Oracle
Oracle数据库的慢语句分析方法:
Orale也可以执行计划:
Cost 指资源消耗成本,是一个衡量值,是CPU、IO等的综合, 值越小越好。
Cardinality 指基数,返回的查询结果条数,值越小越好。
举个例子:
在加上索引前
加上索引后:
Create Index idx_model_color_id On ap_photo (model_color_id)
#tail -200f stdout.log 同时对应查看应用的stdout.log,检查当页面缓存住后,读MC的次数是否超过100次,如前后两次对比首页http://v25.pclady.com.cn:83/test/的MC读写情况: /wap/index.jsp,344,DR96DU0CR105CU85,127.0.0.1 /wap/index.jsp,8,DR0DU0CR1CU0,127.0.0.1 其中DR:读数据库次数,DU:写数据库次数;CR:读MC次数,CU:写MC次数。 从第二次访问的情况,可知当页面缓存住后(写入MC次数=0),读MC次数=1,正常。 PS:在SVN( /trunk/性能测试/监控相关/页面MC请求次数检查/MCRequestNum.sh)有个自动执行脚本,可以把脚本放到log所在目录,来执行。 |
功能测试完成后,可以使用accesslog分析工具,重点关注响应时间较慢的uri。
分析步骤:
/usr/bin/resinlog2nginx.sh /app/bbs7/resin-pro-3.1.8/logs/access.log > /app/bbs7/resin-pro-3.1.8/logs/nginx_access.log
accesslog /app/bbs7/resin-pro-3.1.8/logs/nginx_access.log
如果响应比较平稳的话,取平均响应时间即可。如果响应波动比较大还需要取最大和最小的响应,计算出波动值。
响应图:Available Graphs->Trans Response Time
如下图,响应曲线比较平稳:
如下图,响应曲线波动比较大:
像这种的话就要记录最小响应,最大响应,和平均响应,并且计算出波动幅度。
波动幅度=[max((最大响应-平均响应),(平均响应-最小响应))]/平均响应。
注意:
这种波动的话要引起注意,要注意分析影响原因,排查外因。有可能是以下这些原因影响到
缓存过期;
定时任务影响;
ssi或者外部接口影响
... ...
如下图,响应曲线只降不升:
1、像这种情况的话,下降前那段时间的平均响应和下降平稳后的平均响应,然后统计响应下降幅度。
2、响应下降幅度=(开始平均响应-下降后平均响应)/开始平均响应。
3、注意:如果看到响应是一直下降,没有平稳下来,那就要继续长时间压下去,看应用会不会被压死,并找出响应下降的原因。
虚拟机需要通过VMware vSphere Client (需要安装)来查看这个参数。
步骤:选中虚拟机(如虚拟机名为:c50)->性能->高级(时间筛选:图标选项->cpu->自定义->选好时间范围)->切换到“cpu”->性能图表图例下选择“c50 使用情况(MHz)”->获取最大值、平均值、最小值,如下图:
http://bug.pconline.com.cn/cacti/graph_view.php?action=tree&tree_id=2&lea f_id=128
用top命令查看:SecureCRT登陆服务器->top->根据pid 记录,如下:
需要记录“使用大小” 和“命中率”。
文件命中率:Document Hit Rate -------- 68.356682 % *
1、windows 负载机:启动任务管理器->性能(记录cpu使用率,内存使用)->联网(记录网络流量:使用率*线路速度)。
2、Linux负载机:SecureCRT登陆服务器->记录cpu(MHz;负载)->网络流量等
【xxx应用_summary report 简要报告中性能部分内容格式】:
性能测试目标:
一年内,wap端最高峰达到5万/PV/天,0.3万/IP/天;pc端最高峰达到0.8万/PV/天,0.2万/IP/天。
性能测试内容:
首页、栏目列表、测试详情页、测试开始页、测试结果页
性能测试方法:
由于应用压力小,采用了手动分析页面性能的测试方法。
性能调优:
ALTER TABLE `pclady_ptest`.`pt_topic` ADD INDEX `idx_ptopic_createAt` (`create_at`);
ALTER TABLE `pclady_ptest`.`pt_question` ADD INDEX `idx_pquestion_topicId_type` (`topic_id`, `type`);
性能测试结果:
通过手动分析页面,无慢语句,缓存使用合理,应用的性能满足上线需求。
【邮件正文内容格式】:
性能测试:
1. 测试目标:wap端最高峰达到5万/PV/天,0.3万/IP/天;pc端最高峰达到0.8万/PV/天,0.2万/IP/天。
2. 测试内容:首页、栏目列表、测试详情页、测试开始页、测试结果页
3. 测试方法:由于应用压力小,采用了手动分析页面性能的测试方法
4. 性能优化:
Ø 缓存优化:测试详情页、测试开始页、测试结果页部分SQL查询内容加MC缓存;
Ø 索引优化:优化测试话题表及测试问题表各一条索引。
5. 测试结果:通过手动分析页面,无慢语句,缓存使用合理,应用的性能满足上线需求。
备注:如果没有性能优化项的话,可以不用写性能优化。
类别 |
判断维度 |
不通过 |
通过 |
备注 |
监控工具 |
应用服务器性能指标 |
错误概率 |
大于万分之一 |
小于万分之一 |
1、如果404/500页面较多的,需要重新取合理的参数。 |
LR |
TPS(响应) |
小于期望高峰值 |
大于期望高峰值 |
根据需求分析进行,通常情况,期望高峰值=PV/3600/8 |
lr |
|
TPS波动范围 |
>8% |
5%-8% |
t (TPS波动范围) = TPS标准差/TPS平均值 ×100% |
lr |
|
|
cpu利用率 |
大于75% |
小于75% |
|
top |
响应时间 |
大于期望时间 |
小于期望时间 |
一般控制在1s以下,0.3s以下最佳,大于0.3s可以尝试调优 |
top |
|
load |
平均每核cpu的load大于1 |
平均每核cpu的load 小于1 |
例如8核服务器,负载在8以下 |
|
|
jvm内存使用率 |
大于80% |
小于80% |
|
jstat |
|
Full GC频率 |
平均小于半小时1次 |
平均大于半小时1次 |
jstat工具检查 |
|
|
IO读写时间 |
大于20ms |
小于20ms |
|
|
|
Traffic |
|
|
|
|
|
数据库服务器性能 |
Load |
平均每核cpu的load大于1 |
平均每核cpu的load 小于1 |
|
|
缓存 |
MC缓存命中率 |
小于85% |
大于85% |
|
|