京山游侠

专注技术,拒绝扯淡
posts - 50, comments - 868, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Web 2.0时代,决战效率之巅

Posted on 2009-04-18 22:17 京山游侠 阅读(3837) 评论(9)  编辑  收藏 所属分类: Linux和Java
在这个博客上晃悠的,大部分都是Java的忠实粉丝。大家都知道Java开发Web应用的众多优点,有大量开源的稳定的应用服务器,有大量开源的稳定的开发库。以前在开发效率上Java比不上动态语言,但是现在出现了数都数不清的开发框架,让我们使用Java进行Web开发的效率突飞猛进,直逼动态语言。

然而在Web 2.0时代,Java一定是最优秀的吗?

在Web 2.0时代,大家都有点在效率方面的偏执,不断有人在挑战极限,他们追求更大的并发链接数,他们追求更加快的处理速度。操作系统觉得select不够快,于是出现了IOCP、epoll、kqueue等系统调用;Web服务器的开发者们觉得Apache httpd不够快,于是出现了lighttpd、nginx。使用上Fedora 10之后,我就不断用yum list命令企图发现一些惊奇。在这样的探索中,我发现了Fedora 10中安装lighttpd和nginx是多么简单,也发现使用Fast CGI开发也是那么顺手。下图中是我用yum list命令发现的一些软件包
yum_list.png


于是,在我的心中出现了一个疑问:httpd、lighttpd、nginx、tomcat哪一个更快呢?

我使用PHP写了一个小程序,以便测试上面的Web服务器哪一个更快,tomcat是Java的服务器,不能运行PHP,只能运行JSP。我的程序是这样设计的:先生成1个长度为1000个单词、每个单词长度为1-10个字母的字符串,再生成1000个长度为10个单词、每个单词长度为1-10个字母的字符串,然后对这些字符串排序,输出。为什么这么设计呢?因为我觉得这比较接近Web 2.0的真实使用环境,一个1000字长度的文章加上1000个10字长度的评论,这不是我们最常见的吗?而且这个程序中会多次调用rand()函数,会多次进行字符串连接操作,会对字符串进行排序,这些操作对语言的执行速度也是一个考验。

先来看代码,test.php:
<?php
  
// 定义随机生成字符串的函数
  function make_string($word_count,$word_length){
    
$letters = range('a','z');
      
$temp_string = '';
      
for($i=0$i<$word_count$i++){
        
$temp_word = '';
        
for($j=0$j<$word_length$j++){
              
$temp_word = $temp_word.$letters[rand(0,25)];
        }
        
$temp_string = $temp_string.' '.$temp_word;
      }
      
return $temp_string;
  }
  
  
// 定义随机数种子,以便每次运行测试,都生成相同的内容
  srand(1);
  
  
// 首先生成一个包含一千个单词的字符串,每个单词长度为1到10个字母
  $strings[0= make_string(1000,rand(1,10));
  
  
// 再生成1000个包含10个单词的字符串,每个单词的长度为1到10个字母
  for($i=0$i<1000$i ++){
      
$strings[$i+1= make_string(10,rand(1,10));
  }
  
  
// 排序,输出
  sort($strings);
  
for($i=0$i<1001$i++){
    
echo $strings[$i].'<br><br>';
  }
?>

执行情况如何呢?先让大家看三个图。这是我分别使用httpd、lighttpd、nginx做为服务器时,使用
ab -100 -10000 http://localhost/test.php
命令进行测试所得到的结果:
015.png

006.png

009.png

其中,httpd是以mod_php的方式运行PHP,而其它两个用的是FastCGI的方式运行PHP。从速度上来讲,httpd略占优势。但是httpd占用的资源太多,而且很难支持大的并发连接数。当并发数增加到1000的时候,httpd就反应不过来了,出现超时;而lighttpd和nginx都可以轻松支持到30000的并发。注意,如果使用超过1000的并发来进行测试,千万别忘了使用ulimit命令来设置进程可以打开的最大文件数。

到了这里我就想,如果使用C++编写Fast CGI程序,运行的速度是不是更快呢。使用Java呢?于是我用C++和Java分别实现了和上面功能相同的程序。代码如下:
#include <fcgi_config.h>
#include 
<unistd.h>
#include 
<cstdlib>
#include 
<string>
#include 
<vector>
#include 
<algorithm>
#include 
<fcgi_stdio.h> /* fcgi library; put it first*/

using namespace std;

string make_string(int word_count,int word_length){
    
char letters[] = "abcdefghijklmnopqrstuvwxyz";
    
string temp_string;
    
for (int i = 0; i < word_count; i++) {
        
string temp_word;
        
for (int j = 0; j < word_length; j++) {
            
int index = rand() % 26;
            temp_word.append(
1, letters[index]);
        }
        temp_string.append(
" ");
        temp_string.append(temp_word);
    }
    
return temp_string;
}

int main ()
{
    
while (FCGI_Accept() >= 0) {

        
char *contentLength = getenv("CONTENT_LENGTH");
        
int len;

        printf(
"Content-type: text/html\r\n"
            
"\r\n");

        
if (contentLength != NULL) {
            len 
= strtol(contentLength, NULL, 10);
        } 
else {
            len 
= 0;
        }

        
if (len <= 0) {
            printf(
"No data from standard input.<p>\n");
        } 
else {
            
int i, ch;

            printf(
"Standard input:<br>\n<pre>\n");
            
for (i = 0; i < len; i++) {
                
if ((ch = getchar()) < 0) {
                    printf(
                            
"Error: Not enough bytes received on standard input<p>\n");
                    
break;
                }
                putchar(ch);
            }
            printf(
"\n</pre><p>\n");
        }

        vector
<string> strings;
        strings.push_back(make_string(
1000, rand() % 10 + 1));
        
for (int i = 0; i < 1000; i++) {
            strings.push_back(make_string(
10, rand() % 10 + 1));
        }

        sort(strings.begin(), strings.end());

        
for (vector<string>::iterator it = strings.begin(); it != strings.end(); it++) {
            printf(
"%s<br><br>", (*it).c_str());
            printf(
"\r\n");
        }

    } 
/* while */

    
return 0;
}

JSP:
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding
="UTF-8"%>
<%@ page import="java.util.Random" %>
<%@ page import="java.util.Arrays" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%!
public String make_string ( int word_count,int word_length) {
    Random rand 
= new Random();
    
char letters[] = "abcdefghijklmnopqrstuvwxyz".toCharArray();
    String temp_string 
= "";
    
for (int i = 0; i < word_count; i++) {
        String temp_word 
= "";
        
for (int j = 0; j < word_length; j++) {
            
int index = rand.nextInt(26);
            temp_word 
+= Character.toString(letters[index]);
        }
        temp_string 
+= " ";
        temp_string 
+= temp_word;
    }
    
return temp_string;
}
%>
<%
Random rand 
= new Random();

String[] strings 
= new String[1001];
strings[
0= make_string(1000,rand.nextInt(10)+1);
for(int i=1; i<1001; i++){
    strings[i] 
= make_string(10,rand.nextInt(10)+1);
}
Arrays.sort(strings);

for(int i=0; i<1001; i++){
%>
<%=strings[i]%><br><br>
<%
}

%>
</body>
</html>

这时候,我使用lighttpd作为服务器运行PHP和Fast CGI,使用Tomcat运行JSP,得到如下的测试结果:

先看JSP,我使用的测试命令是ab -c 100 -n 10000 http://localhost:8080/test.jsp,测试的结果只有29.30req/s,如下图
java_result.png
这个时候的资源占用是多少呢,请看top命令的截图:
java_top.png
java进程只占用了470兆的内存,CPU基本占满了,那是因为我写的这个程序对CPU使用比较高。

再来看看PHP执行的情况,测试命令为ab -c 100 -n 10000 http://localhost/test.php,测试结果有48.73req/s,如下图:
php_result.png

资源占用有多少呢,再来看看top命令的截图
php_top.png

每个php-cgi进程用4M内存,lighttpd服务器占用5M内存,而我的机器上跑了72个php-cgi进程,如下图:

php_procs.png

总内存占用293M,CPU占用也比较高。

使用C++写的Fast CGI,测试命令为ab -c 100 -n 10000 http://localhost/test.fcgi,结果为266.47req/s,是PHP的5倍多,是JSP的9倍。
fcgi_result.png

top截图,发现C++写的test.fcgi每个进程只占1M内存:
fcgi_top.png

总共有64个test.fcgi进程:

fcgi_procs.png

总内存占用只有79M,CPU占用也比较低,没有达到满载。

讨论:

先来说说服务器,对于Web 2.0的应用来说,httpd基本上可以淘汰了,资源占用太高,支持不了上千的并发请求。lighttpd和nginx在FastCGI上的表现都很不错,从性能上说,nginx似乎还要强一些,但是缺点就是文档不完善,nginx需要lighttpd提供的spawn-fcgi程序来启动Fast CGI进程,而且不知道对Fast CGI负载均衡的支持怎么样,因为我在网上找了很久都没有找到相关的文档。而lighttpd的文档就完善多了,虽然是英文的,但读起来不难。我已经把lihgttpd的文档都读了一遍了,对于fastcgi.txt和performance.txt我还反复阅读,这些文档对于Fast CGI的配置和功能有不错的描述。所以,如果选择Web服务器,我的答案是lighttpd。

再来说说编程语言。速度最快的无疑是C++,它是Java的9倍,是PHP的5倍。这里就有一点奇怪了,Java是编译型语言,而PHP是解释的,在我的测试中,没有对PHP使用字节码缓存,结果编译型的语言居然跑不过解释型的。有人也许会说,PHP开了70几个进程,而Tomcat只有一个进程,但是不管怎么样,CPU都是满载的,就算多开几个Tomcat进程,也不可能把一个CPU当两个用。当然,躲开Tomcat进程对提高并发和提高稳定性肯定是有好处的。大家都知道,httpd可以通过mod_jk来和Tomcat服务器集成,而lighttpd没有mod_jk,但是可以通过mod_proxy实现相同的功能,也就是让lighttpd做前端服务器,把动态请求分别发送到后端的Tomcat,并实现负载均衡和缓存。

上面的测试还有一个问题,那就是纯代码的测试,而在实际应用中,除了运行动态代码,还有数据库操作,数据库操作也是非常费时间的。我在想,应该再写一个测试代码,就把上面生成的这1001个字符串写入数据库再取出,看看运行速度如何。

C++虽然运行速度快,但是用来写Web应用还是比较难的,我读了FastCGI develep kit的源代码,它只实现了很底层的功能:读入环境变量,标准输入输出。很显然,它对多字节字符支持是没有的,所有对于网络上千奇百怪的字符编码,我们普通程序员是没有办法处理的。在Web开发领域,更缺少基于C++的页面模板引擎、MVC引擎、IOC引擎、ORM引擎、SOA引擎,等等。如果有哪个高手立志于C++ Web开发,写一个基于C++的超级牛B的框架,说不定可以创立一番大事业。

PHP应该是个不错的选择,因为lighttpd的作者还有另外一个作品,那就是XCache,是用来缓存PHP的字节码的,据说可以提高PHP的执行速度3-5倍。这么说来,PHP甚至可以达到和C++相同的性能。PHP也有不错的开发框架Zend,PHP有丰富的库可供使用。PHP是动态语言,写起代码来没有Java死板。看来是不错的选择。

Java也不错,不过目前Java领域的应用服务器都很庞大,而且大部分都是基于Java语言开发出来的,比起C++开发的lihgttpd和nginx,性能自然是要差一点点。不过对于企业开发,Java依然是利器,正是因为应用服务器的存在,让我们少考虑很多底层的细节,让我们很方便开发分布式的应用,稳定的企业级应用。Java的库和框架那也是如漫天繁星、数之不尽的。

总之,具体怎么选择,还是要看大家的,我已经有点迷茫了。

评论

# re: Web 2.0,决战效率之巅  回复  更多评论   

2009-04-18 22:56 by SearchFull
怎么没有写完呢?

# re: Web 2.0,决战效率之巅  回复  更多评论   

2009-04-19 13:24 by fireflyc
这样不公平吧?
java有NIO的。你把tomcat的NIO开启看看是效果如何的。我想你会惊讶的发现原来“IOCP、epoll、kqueue”可以不依赖操作系统的——Java的NIO。

# re: Web 2.0,决战效率之巅  回复  更多评论   

2009-04-20 09:15 by r
你java写的不好,用StringBuilder

# re: Web 2.0,决战效率之巅[未登录]  回复  更多评论   

2009-04-20 10:05 by 海边沫沫
用StringBuffer就不公平了,因为PHP中也是用的字符串连接,大量创建消毁字符串对象是肯定的。只有c++最占便宜

# re: Web 2.0时代,决战效率之巅  回复  更多评论   

2009-04-20 12:29 by 海边沫沫
最新测试结果:
Tomcat开启NIO后:28.33req/s
JSP代码改用StringBuffer后:193.05req/s

根据楼上两位的回复,我先是对开启了Tomcat的NIO,结果发现性能基本没有改变,理论上讲NIO应该不可能一点性能提升都没有的,有可能是Tomcat默认就已经开启了NIO,不需要显式配置。

然后我优化了一下代码,该用了StringBuffer,结果不得了,性能提升了5-6倍。和C++的性能以经很接近了。由此可见,在Java中创建对象开销还是很高的。另外,我把Random rand = new Random()换了个地方,这样每运行一次JSP只需要创建一个Random对象。

请看截图,上面一张是开启NIO后的结果,下面一张是优化代码后的结果。






下面是优化后的代码:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.Random" %>
<%@ page import="java.util.Arrays" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%!
Random rand = new Random();
public String make_string ( int word_count,int word_length) {

char letters[] = "abcdefghijklmnopqrstuvwxyz".toCharArray();
StringBuffer temp_string = new StringBuffer();
for (int i = 0; i < word_count; i++) {
StringBuffer temp_word = new StringBuffer();
for (int j = 0; j < word_length; j++) {
int index = rand.nextInt(26);
temp_word.append(letters[index]);
}
temp_string.append(" ");
temp_string.append(temp_word);
}
return temp_string.toString();
}
%>
<%
String[] strings = new String[1001];
strings[0] = make_string(1000,rand.nextInt(10)+1);
for(int i=1; i<1001; i++){
strings[i] = make_string(10,rand.nextInt(10)+1);
}
Arrays.sort(strings);

for(int i=0; i<1001; i++){
%>
<%=strings[i]%><br><br>
<%
}

%>
</body>
</html>

# re: Web 2.0时代,决战效率之巅  回复  更多评论   

2009-04-20 18:10 by 海边沫沫
下面再增加一个比较项目,那就是对静态文件的响应速度。我把前面用PHP生成的页面保存下来,作为test.html,文件大小是80多k,然后使用
ab -c 1000 -n 10000 http://localhost/test.html
ab -c 1000 -n 10000 http://localhost:8080/test.html
进行测试。

结果:
Apache httpd:3276 req/s
lighttpd:5633 req/s
nginx: 6080 req/s
Tomcat:1015 req/s

结果表明,Tomcat只有nginx的六分之一,而且Tomcat的Failed Request字段的值太高,其余三个服务器该字段的值都是0,说明Tomcat面对大量并发连接时还不够稳定。所以,使用lighttpd或nginx做Tomcat的反向代理,并进行缓存,应该可以获得不错的性能。

下面上图:






# re: Web 2.0时代,决战效率之巅  回复  更多评论   

2009-05-30 14:40 by 虎啸龙吟
楼主强啊,什么都会啊

# re: Web 2.0时代,决战效率之巅  回复  更多评论   

2009-06-24 17:58 by ycmhn
这种简单并且cpu密集的任务肯定是java比php占优势
因为毕竟是半编译语言
但是复杂的任务php最大的好处是扩展多~而且很容易用c语言写扩展,
如果把你这个写成c扩展的话~~~嘿嘿 那就难说了~
java。。。我看了几年都很少看到用C写扩展的,因为java对自己速度比较自信,且编写扩展比较繁杂
上面说看到nio会很惊讶的同学,,,可能是对别的语言了解比较少~其实说的就是一个跨平台问题~
以前我也为java宣称的跨平台激动过~可是呢~名不符实~不如很多脚本好

# re: Web 2.0时代,决战效率之巅  回复  更多评论   

2009-06-30 11:29 by aDuan
web 2.0
nginx + php + mysql.

只有注册用户登录后才能发表评论。


网站导航: