本文首先设计了一个简单的测时工具类进行函数耗时计算,然后利用spring AOP测试函数性能,最后分析了系统的耗时函数。
测试准备
测时方法1-工具类-代码入侵:
写了一个简单测试函数耗时的工具类(以后用spring aop代替)
public class ProfileUtils {
private static Map<String, Long> tempTime = new HashMap<>();
private static Map<String, Long> avgTime = new TreeMap<>();
public static void start(String tag) {
tempTime.put(tag, System.nanoTime());
}
public static void end(String tag) {
Long preTime = tempTime.get(tag);
Long avgpre = avgTime.get(tag);
if (preTime == null) return;
long cuTime = System.nanoTime() - preTime;
if (avgpre != null && avgpre != 0) {
avgpre = (avgpre + cuTime) / 2;
} else {
avgpre = cuTime;
}
avgTime.put(tag, avgpre);
}
public static void print() {
Set<String> keys = avgTime.keySet();
for (String key : keys) {
Long rs = avgTime.get(key);
System.out.println(key + "-avg: " + rs / 1000000 + "ms");
}
}
}
缺点:使用比较麻烦,需要注意前后名称对应,且需要显示入侵代码。
解决方案:SpringAOP
性能评测方法2:spring AOP
spring文件配置中加入一行(基于注解的aop配置)如下:
<aop:aspectj-autoproxy/>
配置aspect类与切入点:
/**
* Created by caiqingliang on 2016/8/7.
* 切面,计算函数耗时,按函数总耗时排序输出(总耗时、平均单次耗时、调用次数)
*/
@Component @Aspect public class TimeAdvice {
private Map<String, MyProfile> map = new ConcurrentHashMap<>();
@Around("execution(* com.leocai.bbscraw.crawlers.MyCrawler.nextPage(..))"
+ "||execution(* com.leocai.bbscraw.crawlers.MyCrawler.getCuCaoTarget(..))"
+ "||execution(* com.leocai.bbscraw.crawlers.MyCrawler.getInfoDTO(..))"
+ "||execution(* com.leocai.bbscraw.services.*.*(..))"
// + "||execution(* com.leocai.bbscraw.*.*(..))"
// + "||execution(* com.leocai.bbscraw.mappers.*.*(..))"
+ "") public Object logTime(ProceedingJoinPoint pointcut) throws Throwable {
long pre = System.nanoTime();
Object rs = pointcut.proceed();//此处注意要返回
String sig = pointcut.getSignature().getName();
long time = (System.nanoTime() - pre) / 1000000;
MyProfile profile = map.get(sig);
if (profile == null) {
profile = new MyProfile();
map.put(sig, profile);
}
profile.incr();
profile.addTime(time);
return rs;
}
/**
* 输出的数据结构,包含总耗时、平均单次耗时、调用次数
* 大小根据总耗时比较
*/
public static class MyProfile implements Comparable {
List<Long> times = new ArrayList<>();
{
times = Collections.synchronizedList(times);
}
AtomicLong count = new AtomicLong(0);
public void incr() {
count.incrementAndGet();
}
public void addTime(long time) {
times.add(time);
}
public long getAvg() {
long avg = 0;
int count = 0;
for (long t : times) {
avg += t;
count++;
}
avg /= count;
return avg;
}
public long getTotalCost() {
return getAvg() * count.get();
}
@Override public String toString() {
return "total-cost " + getTotalCost() + "ms\tavg-cost " + getAvg() + " ms\tcalled " + count + " times";
}
@Override public int compareTo(Object o) {
return (int) (((MyProfile) o).getTotalCost() - getTotalCost());
}
}
@PreDestroy public void print() {
map = MapUtil.sortByValue(map);
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key+"\t"+map.get(key));
}
}
}
优点很明显:只需要在注解中包含需要代理的函数即可
输出:
continueCraw total-cost 7117ms avg-cost 7117 ms called 1 times
getInfoDTO total-cost 742ms avg-cost 53 ms called 14 times
nextPage total-cost 609ms avg-cost 203 ms called 3 times
produceJobInfo total-cost 378ms avg-cost 27 ms called 14 times
getCuCaoTarget total-cost 126ms avg-cost 42 ms called 3 times
getLatestDateBySource total-cost 11ms avg-cost 11 ms called 1 times
性能分析:
现在对CrawlerWriter重要部分函数进行性能分析:
- crawSince:单个爬虫爬取总时间
- crawOnePage-avg:单个爬虫在爬取一页上的平均耗时(不包括获取网址)
- nextPage-avg:获取下一页的耗时
单线程执行:
NJUCSCrawler.crawSince-avg: 12310ms
NJUCSCrawler.crawOnePage-avg: 579ms
NJUCSCrawler.nextPage-avg: 221ms
NJUCrawler.crawSince-avg: 14272ms
NJUCrawler.crawOnePage-avg: 1000ms
NJUCrawler.nextPage-avg: 180ms
NOWCODERCrawler.crawSince-avg: 33186ms
NOWCODERCrawler.crawOnePage-avg: 2003ms
NOWCODERCrawler.nextPage-avg: 584ms
NYUCrawler.crawSince-avg: 27739ms
NYUCrawler.crawOnePage-avg: 1131ms
NYUCrawler.nextPage-avg: 871ms
SJUCrawler.crawSince-avg: 17627ms
SJUCrawler.crawOnePage-avg: 1146ms
SJUCrawler.nextPage-avg: 172ms
continueCraw-avg: 105215ms
init-avg: 49ms
五个线程执行:
NJUCSCrawler.crawSince-avg: 16404ms
NJUCSCrawler.crawOnePage-avg: 906ms
NJUCSCrawler.nextPage-avg: 193ms
NJUCrawler.crawSince-avg: 26351ms
NJUCrawler.crawOnePage-avg: 1221ms
NJUCrawler.nextPage-avg: 272ms
NOWCODERCrawler.crawSince-avg: 55302ms
NOWCODERCrawler.crawOnePage-avg: 2125ms
NOWCODERCrawler.nextPage-avg: 614ms
NYUCrawler.crawSince-avg: 43536ms
NYUCrawler.crawOnePage-avg: 1328ms
NYUCrawler.nextPage-avg: 980ms
SJUCrawler.crawSince-avg: 33862ms
SJUCrawler.crawOnePage-avg: 1168ms
SJUCrawler.nextPage-avg: 214ms
continueCraw-avg: 55321ms
init-avg: 4ms
问题分析:
- 可以看到单线程执行仅仅比5个线程执行多耗时1倍,问题可能在webdriver需要同步,cpu核心不够,mysql冲突等。
- 五个线程并发执行,最终耗时约等于最耗时间的一个爬虫,从时间分析中看出,爬取牛客网的耗时最高,这可以理解,牛客网一页信息量较大
- 爬取一页的耗时比较大,是下一页的1.5倍-5倍不等。
- 所以爬取一页需要优化,问题可能在写mysql,也可能在查找dom
- 优化重点在爬取单页的内容上
分析crawlerReader的性能
不用缓存的CrawlerReader性能
getFromCache-avg: 0ms
getJobInfos-avg: 98ms
getJobInfosMysql-avg: 98ms
init-avg: 3ms
使用缓存的CrawlerReader性能
getFromCache-avg: 109ms
getJobInfos-avg: 124ms
getJobInfosMysql-avg: 14ms
init-avg: 4ms
分析:
- 使用缓存效果不明显,甚至更差,
- 可能缓存使用不当,或是数据量太少
- mysql有查询缓存