多种HTTP服务器:Misultin, Mochiweb, Cowboy, NodeJS 及 Tornadoweb测评



我对HTTP服务器很感兴趣,我花了大量时间来试用它们,还喜欢从不同角度来比较它们。

今天我将用同一个用例对以下不同的HTTP服务器库进行测试:

  • Misultin (Erlang)
  • Mochiweb (Erlang)
  • Cowboy (Erlang)
  • NodeJS (V8)
  • Tornadoweb (Python)



                          

之所以选择这些库是因为我现在对它们几个最感兴趣。Mochiweb 是一个稳定的库,在生产环境上使用广泛(估计曾经,或者现在还是,和其它组件一起,被 Facebook 来作聊天服务的后端);Cowboy一个刚刚诞生不久的库,其开发者在 Erlang 社区里十分活跃;NodeJS将 javascript 引入服务端,开创了一个充满各种可能的新世界(可与前端共用代码,入门成本低等);还有 Tornadoweb,因为 Python 仍然是我最喜欢的语言之一,另外 Tornadoweb 在各种性能测试和生产环境中都有出色的表现,是 FriendFeed 的后端实现。

关于这个测试,还有两个重要的原则。首先,我并不想做 "Hello World" 类型的测试:对于这类测试,使用静态内容服务器(如 Nginx)就完全可以了。这个测试主要针对动态内容服务器。其次,我计划隔一段时间就关闭 socket,因为现实中是不可能仅靠几个 socket 来承担所有负载压力的。

            
                 

为了实现上面所说的第二点,我决定使用一个经过修改 的 HttPerf。这是一个来自HP公司的知名的测试工具,使用广泛。它的作用是往服务器发送指定数目的请求,然后根据响应的数目、整个过程中产生错误以及其他信息来生成报告。HttpPerf 最好的一点是,你可以指定一个参数(叫"-num-calls"),来告诉它你在关闭 socket 前一次性要发送多少个请求(就是 socket 连接)。这次测试用到的命令为:

httperf --timeout=5 --client=0/1 --server= --port=8080 --uri=/?value=benchmarks --rate= --send-buffer=4096
        --recv-buffer=16384 --num-conns=5000 --num-calls=10

我将速度(--rate)从100开始一直增加到 1200。由于每秒请求数目等于 rate*num-calls,这个测试实际上每秒发送的请求数目由 1000 增加到 12000。总的请求数目等于 num-conns*rate,每次迭代中实际等于 50000。

            
                          

这个测试实际上是请求服务器:

  • 检查GET变量是否设置
  • 如果没有,以XML返回错误
  • 如果已经设置,在XML中返回

因此,实际上测试的是服务器的:

  • header (HTTP头) 分析处理
  • querystring (查询串) 分析处理
  • 字符串拼接
  • socket 实现

服务器是一个双核的1.5G内存的虚拟机系统,安装了 Ubuntu 10.04 LTS,并打上了最新的补丁。/etc/sysctl.conf 已经过优化,主要参数如下:

# Maximum TCP Receive Window
net.core.rmem_max = 33554432
# Maximum TCP Send Window
net.core.wmem_max = 33554432
# others
net.ipv4.tcp_rmem = 4096 16384 33554432
net.ipv4.tcp_wmem = 4096 16384 33554432
net.ipv4.tcp_syncookies = 1
# this gives the kernel more memory for tcp which you need with many (100k+) open socket connections
net.ipv4.tcp_mem = 786432 1048576 26777216
net.ipv4.tcp_max_tw_buckets = 360000
net.core.netdev_max_backlog = 2500
vm.min_free_kbytes = 65536
vm.swappiness = 0
net.ipv4.ip_local_port_range = 1024 65535
net.core.somaxconn = 65535
           
                    

/etc/security/limits.conf 文件已经过优化,ulimit -n 设置为 65535。

以下是各个库实现HTTP服务器的代码:

Misultin

-module(misultin_bench).
-export([start/1, stop/0, handle_http/1]).

start(Port) ->
    misultin:start_link([{port, Port}, {loop, fun(Req) -> handle_http(Req) end}]).

stop() ->
    misultin:stop().

handle_http(Req) ->
    % get value parameter
    Args = Req:parse_qs(),
    Value = misultin_utility:get_key_value("value", Args),
    case Value of
        undefined ->
            Req:ok([{"Content-Type", "text/xml"}], ["<http_test><error>no value specified</error></http_test>"]);
        _ ->
            Req:ok([{"Content-Type", "text/xml"}], ["<http_test><value>", Value, "</value></http_test>"])
    end.

Mochiweb

-module(mochi_bench).
-export([start/1, stop/0, handle_http/1]).

start(Port) ->
    mochiweb_http:start([{port, Port}, {loop, fun(Req) -> handle_http(Req) end}]).

stop() ->
    mochiweb_http:stop().

handle_http(Req) ->
    % get value parameter
    Args = Req:parse_qs(),
    Value = misultin_utility:get_key_value("value", Args),
    case Value of
        undefined ->
            Req:respond({200, [{"Content-Type", "text/xml"}], ["<http_test><error>no value specified</error></http_test>"]});
        _ ->
            Req:respond({200, [{"Content-Type", "text/xml"}], ["<http_test><value>", Value, "</value></http_test>"]})
    end.

注: 我在这里使用 misultin_utility:get_key_value/2 函数,因为 proplists:get_value/2 太慢了.

Cowboy

-module(cowboy_bench).
-export([start/1, stop/0]).

start(Port) ->
	application:start(cowboy),
	Dispatch = [
		%% {Host, list({Path, Handler, Opts})}
		{'_', [{'_', cowboy_bench_handler, []}]}
	],
	%% Name, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts
	cowboy:start_listener(http, 100,
		cowboy_tcp_transport, [{port, Port}],
		cowboy_http_protocol, [{dispatch, Dispatch}]
	).

stop() ->
	application:stop(cowboy).
-module(cowboy_bench_handler).
-behaviour(cowboy_http_handler).
-export([init/3, handle/2, terminate/2]).

init({tcp, http}, Req, _Opts) ->
    {ok, Req, undefined_state}.

handle(Req, State) ->
    {ok, Req2} = case cowboy_http_req:qs_val(<<"value">>, Req) of
        {undefined, _} ->
			cowboy_http_req:reply(200, [{<<"Content-Type">>, <<"text/xml">>}], <<"<http_test><error>no value specified</error></http_test>">>, Req);
        {Value, _} ->
			cowboy_http_req:reply(200, [{<<"Content-Type">>, <<"text/xml">>}], ["<http_test><value>", Value, "</value></http_test>"], Req)
    end,
    {ok, Req2, State}.

terminate(_Req, _State) ->
    ok.

NodeJS

var http = require('http'), url = require('url');
http.createServer(function(request, response) {
	response.writeHead(200, {"Content-Type":"text/xml"});
	var urlObj = url.parse(request.url, true);
	var value = urlObj.query["value"];
	if (value == ''){
		response.end("<http_test><error>no value specified</error></http_test>");
	} else {
		response.end("<http_test><value>" + value + "</value></http_test>");
	}
}).listen(8080);

Tornadoweb

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
	def get(self):
		value = self.get_argument('value', '')
		self.set_header('Content-Type', 'text/xml')
		if value == '':
			self.write("<http_test><error>no value specified</error></http_test>")
		else:
			self.write("<http_test><value>" + value + "</value></http_test>")

application = tornado.web.Application([
	(r"/", MainHandler),
])

if __name__ == "__main__":
	application.listen(8080)
	tornado.ioloop.IOLoop.instance().start()
daxiaoming
daxiaoming
翻译于 2年前

0人顶

 翻译的不错哦!

本文测试的各个库的版本:

  • Misultin 0.7.1 (Erlang R14B02)
  • Mochiweb 1.5.2 (Erlang R14B02)
  • Cowboy master 420f5ba (Erlang R14B02)
  • NodeJS 0.4.7
  • Tornadoweb 1.2.1 (Python 2.6.5)

各个库都采用默认设置来运行。其中 Erlang 开启了 Kernel Polling。另外,为了让所有库只使用一个CPU,我禁用了SMP。

测试结果

HttPerf 的原始输出文件可从 这里 下载

多种HTTP服务器:Misultin, Mochiweb, Cowboy, NodeJS 及 Tornadoweb测评_第1张图片

预期和实际响应图

多种HTTP服务器:Misultin, Mochiweb, Cowboy, NodeJS 及 Tornadoweb测评_第2张图片

超时错误图

多种HTTP服务器:Misultin, Mochiweb, Cowboy, NodeJS 及 Tornadoweb测评_第3张图片

响应时间图

注:上面的图的Y-轴使用了对数

多种HTTP服务器:Misultin, Mochiweb, Cowboy, NodeJS 及 Tornadoweb测评_第4张图片

总响应时间图

由图可知,Tornadoweb 最高约为 1500 请求/秒,NodeJS 为 3000,Mochiweb 为4850, Cowboy 为8600, Misultin 为9700。除了 Misultin 和 Cowboy 没有或者只有少数错误外,其他库在高负载情况下性能下降明显。请注意,这里的“错误”是指超时错误(大于5秒没有响应)。Total responses 指总的响应数目,response times 指的是响应时间。

              

不得不说,这些结果还是让我比较吃惊的。写到这里,希望各位读者能在代码和测试方法上给我提些意见,以进行更好的测试。欢迎各种意见,我将和其他贡献者一起,在后续的讨论中继续更新这篇文章以及更正相关错误。

注意:文章内容不代表本人观点,请不要由此进行言论攻击。正是由于这些结果太让我惊奇,我才把这篇文章发布出来。

对于这些结果,你的观点是怎样的呢?


                    
—————————————————–

更新 (2011-5-16)

现在各种各样的测试正源源不断地冒出来。在你们阅读这些测试报告(也包括我的)的时候,我想着重强调一点:

对于各种测试报告,人们常常误认为:如果一个测试对象在图表中表现越好,那么这个测试对象在所有的应用场景下都是最好的。这种观点绝对,绝对是错误的!



            

                         

"快速"只是优秀WEB服务器库的众多特点之一:在选择组件进行开发之前,你还必须考虑健状性、功能、易于维护、尽可能接近标准、代码可用性、支持社区、开发速度及其他因素。没有哪个测试是适用于所有情况的。这类测试问题侧重于某一方面:程序运行速度够快、负载能力够强或者尽量少的数据传输量。

因此,请仅将本文测试作为参考的一个因素,而不要武断地下结论。本文引用的库都很优秀,也很有意思,但是你在使用之前,请经过充分的考虑。本文在语言或者在测试方法若有不当,欢迎指正,严厉一些也没关系。

谢谢。

你可能感兴趣的:(erlang,服务器,HTTP服务器,性能测试)