Java,Go,NodeJS性能测试

PS: 项目地址:github,这边有所有的代码 写在文章之前: rpctest文件夹下为测试程序,使用说明见文档,改写自网上淘宝的性能测试用代码 go java nodejs文件夹下为三个不同版本的rpc echo server. 线下测试看,go语言的效率和Nodejs差不多,java因为没有解决并发问题所以只有单线程,效率约为前两者的70~80%,后续将会使用netty进行测试比较 测试环境: Intel i5 4核4线程 内存8G UBuntu x64 文章开始 文章缘由,因为公司中真好有一个代码竞赛,就是比赛写一个rpc服务调用性能竞赛,不能使用现有的轮子,所以java的netty,mina都被毙了,于是就开始了坑跌的 码码。第一个反应肯定使用java,毕竟是最熟悉的语言,然后就开始选型,bio,nio,aio,各种东西都出现了。到底选哪个?因为是追求性能,所以bio第一个被毙了,…

关于容器的新的看法

关于Java中的容器,大家因该第一个想到的是Tomcat,毕竟这应该是学习Java中的第一个容器(如果玩Java Web的话)。在经历了第一个项目和第二个项目之后,我才开始体会到容器的 必要性。 在此之前,我之前的所有的Java程序都是指定main函数的runnable jar package。这样做确实简单。而且快捷。但是有一个很明显的缺点,就是你无法控制他的生命周期。举一个很 简单的例子,如果程序挂了,除了运气比较好去top或者ps一把发现没有这个进程之外,貌似没有好的办法了(此处不考虑心跳协议)。还有一点,程序的可装配性比较差。需要在 main函数里指定加载的顺序。凡此种种,都是因为我们将程序直接泡在了jvm上,而jvm并没有在java层面上给我们过多的api接口。所以,为了改变这个状况,我们在jvm上又加了一个 容器。 所以,简单地说,容器的作用就是一个脸盆,程序就像是一个一个土豆,然后程序的运行就像是在脸盆里煮土豆。如果土豆煮过头了,脸盆就可以最先发现,同时,土豆爆炸了,脸盆也最先知道。 就拿这次的爬虫程序来说,我们本来可以用一个简单的jar程序就可以实现,但是考虑到爬虫的状态获取和报警机制,还是将爬虫扔到了tomcat中,同时,由于有多个模块的组合,我们又 通过spring进行了粘合。所以,爬虫看起来就是这样的一个架构: 所有线程的生命周期都被tomcat控制着。在爬虫阵亡的时候,或者Tomcat OOM跪了的时候,只要我们继承了ServletContextListener这个接口,如下的方法必定执行。 /** ** Notification that the servlet…

第一个任务 — 店铺爬虫

关于这个项目,首先需求比较简单.或者说单一吧.唯一要做的就是将PHP传来的任务给做完.没有多余的爬取,只需要爬取一个页面上的商品描述和商店的描述.唯一好玩的一点就是需要在一周时间内 爬取180万url左右的数据.同时还要考虑对面的防刷设置.分配给的测试资源有两台16核32G的服务器. 很显然.爬虫的关键在于VPS的分配,因为一个ip访问某个网站的频率有限制.在有限的时间,要想爬取更多的网页,就需要多个vps,要多少呢.理论上,服务器的出口带宽为1000Mb,购买的VPS带宽为2Mb, 所以,考虑充分利用带宽,则需要500个.但事实上,我们的需求并没有如此之高.在实际测试中,发现最终的瓶颈在cpu上,频繁的字符串匹配,导致了cpu的飙涨.因为考虑到之后的维护,我用了整页面的 正则进行匹配,这样,之后的修改会方便很多.所以代码里会有如下的正则表达式: 看上去很难维护,但是要想到,之前用doc方式进行解析需要70~80行代码,就释然了.修改解析函数的需求需要对方改变自己的页面展示,到时候,就算是用jsoup写的解析函数,也逃不过重写的事实,这样的 话,再写一个正则可以说要简单很多. 其次是,任务的获取,任务的分发者是PHP,而我的爬虫是消费者,这样的话,需要有一个传输消息的机制.有人会说用socket,简单暴力.但是,如果考虑到今后的水平拓展,则需要使用高大上的MQ,为了简化开发 我们这边选型了Redis的MQ,其实原理很简单,就是在Redis中存放一个列表,然后我顺序读取即可.当然,因为Redis没有提供类似与锁的机制,很可能会造成多爬虫的脏读.这个我们在Redis上又存了一个信号量. 前面也说了,选Redis就是为了更快的开发,所以没有选用RabitMQ,还有一个重要的原因,通过RedisMQ(就这么先叫着吧),PHP端可以读取队列的状况,从而写一个控制界面,这也是一个重要的因素. 说完了选型.之后就是我的爬虫的编写.首先,因为淘宝和美丽说对爬取搜索页有频率限制,所以我抽象了两个搜索类,一个为ShopListTask,ShopDetailsTask,两个的不同就在于,List任务完成后,会主动暂停2秒, 防止被封,而两个网站对店铺详情页则没有限制.两个接口都继承了Callable,方便之后使用线程池. 然后就是一个简单的生产者消费者了.这个不要太简单,一个分发线程,一个接收线程池,搞定了.但是在实际使用中,会发现,对于某些特定的url,正则匹配的效率非常低,而对cpu占用则到了恐怖的境界.两天测试下来 积累的错误造成了cpu占用1589%,同时,线程没有回池,又造成了系统的吞吐量严重退化,两天积累的任务数到了恐怖的8000,(正常情况,对面一分钟的任务,我10秒搞定)!然后就加上了自己之前设计的线程级监控,对 于超时线程进行回收.具体的代码如下: public void dispatchTask(Callable<String> task) {…