接着上一篇《线程安全扫盲贴三》,开始第二次测试。
第二次测试
这次并发1000个客户端消息,每次并发20条后休眠1秒。客户端消息发给我的Server后,我的Server再转发给消息指定的服务器。为了产生异常消息,我将指定的目标服务器关闭了,于是每次消息第一次发送完成后,都成为了一个异常消息,每个异常消息将会自动间隔30s;1m;2m发送3次,发送失败后停止发送。
这样虽然是并发了1000个,但是服务器实际发送了4000次。
张上一篇中已经看过这4个线程。此图与上一篇中,最明显的就是4个线程都在分时执行了,《线程安全扫盲贴三》只有2个在执行。新加入的2个就是异常消息errorList与异常文件删除监听的两个线程。
下面看看我的线程池,我有两个线程池,一个用来执行接收到的消息,一个用来发送消息。
接收到的消息进入接收的线程池中,分析消息内容,如果需要发送出去,再加入到发送的线程池中。
根据日志打印的结果,pool-1是发送消息的线程,pool2是接收消息的线程。
基本上pool1发送消息线程在执行的时候,pool2接收消息线程全部在等待。
上面这句话也许不对,pool1发送消息可以很多一起并发,多个发送线程间可能无资源抢占,pool2接收消息线程不能多个并发,这也可能造成这样。
执行了一段时间之后,发现待重发的消息并没有重发,同时4个监听线程显示只有Thread3在执行了,4巨头线程另外三个都在sleeping。
查看日志发现这几个线程分别对应的监听为:
Thread-0:已发送消息删除磁盘文件监听
Thread-1:已发送错误消息删除磁盘文件监听
Thread-2:待发送消息监听
Thread-3:待重发错误消息监听
这么看情况还是很乐观的,我正希望Thread-3是在运行可以将待重发的错误消息重发出去,虽然visual vm显示Thread-3在努力的running,但是下面是Thread-3的run方法,它连心跳的那句debug都无法打出来。也许是在执行errorMessageSearcher.resendErrorMsg(startTime,endTime);方法一直没退出。。
Thread-3的run方法:
public void run() {
log.info("Thread Name="+Thread.currentThread().getName());//Hhread-3
while(true){
endTime = DateUtil.convertDateTOStr(new Date(), DateUtil.PALETTE_8);
log.debug(Thread.currentThread().getName()+"查询定时发送的消息时间范围:"+startTime+"---->"+endTime);
try {
errorMessageSearcher.resendErrorMsg(startTime,endTime);
} catch (IOException e1) {
log.error(e1);
} catch (ClientException e1) {
log.error(e1);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
log.error(e);
}
}
}
跟着这个思路,刚才仔细检查了errorMessageSearcher.resendErrorMsg(startTime,endTime)方法,见下方注释掉的方法,之前是通过Lucene搜索到到点可以重发的数据后,调用客户端代码,通过socket再向我Server发一条数据过来,而现在改成了直接将要重发的消息加入到接收消息的内存List中。
public int resendErrorMsg(String begin,String end) throws IOException, ClientException{
if(ErrorMessageIndexer.getInstance().isHasUpdated()==true){
ErrorMessageIndexer.getInstance().commit();
reader = IndexReader.openIfChanged(reader);
searcher = new IndexSearcher(reader);
}
int rtnValue = 0;
TermRangeQuery rangeQuery = new TermRangeQuery(ErrorMessageIndexer.NEXT_DATE_FIELD,begin, end,true,true); ;
//获得得分靠前的max个匹配记录
ScoreDoc[] docs = searcher.search(rangeQuery,max).scoreDocs;
if(docs!=null&&docs.length>0){
rtnValue = docs.length;
for(int i = 0; i < docs.length; i++) {
int docId = docs[i].doc;
Document doc = searcher.doc(docId);
String message = doc.get(ErrorMessageIndexer.ERROR_MESSAGE_FIELD);
String uuid = doc.get(ErrorMessageIndexer.UUID_FIELD);
//通过客户端,调用socket通信,将消息发送到infoList中去
// IIPayrelayClient client = new SyncIPayrelayClient();
// client.request(message, false);
//直接加入到消息队列中
WSData wsData = new WSData();
wsData.setMessage(message);
InfoReceiveThreadPoolManager.getInstance().addInfoQueue(wsData);
//删除索引
log.debug("删除索引开始:"+uuid);
ErrorMessageIndexer.getInstance().deleteWSData(uuid);
log.debug("删除索引结束:"+uuid);
}
}
return rtnValue;
}
通过这样修改后,1000条数据重发4次都顺利完成了,完成后Thread-3又回到了休眠状态。
第三次测试
像上面那么改了之后,并发1000条是轻松的就处理完了,于是测试了一下1w条异常数据的情况,这下苦逼了。
pool2全部都堵塞了在等锁,不过pool1都空了。
这个也很好解释,上面java代码中的这句:
InfoReceiveThreadPoolManager.getInstance().addInfoQueue(wsData);
导致异常的消息不用再进入发送消息队列了,直接写如了接收队列,于是接收队线程池很紧张了,发送线程池则等待接收到的消息转到发送线程池中,暂时还表示无压力,过一会儿之后可以看到发送线程池开始绿了~
我的Server是用的QuickServer实现的。通过实现QSAdmin的方法,打印了当时的线程情况。
1032条在infoList中等待发送,7747条数据出错等待到点重发。活动的发送消息线程池中线程为0,活动的接收消息线程池中线程为100(也就是我设置的最大值)。
-----------------------------------------------------
今天一边写这个blog,一边解决我的问题了,遇到想起可以优化的再来完善,完全直播。
顺便摸索着学习了把怎么用visual vm分析问题。很多时候还是靠灵光一现就发现了漏洞,慢慢积累灵光就会越来越多~~