测试内容
动态HTTP服务器极限性能,分为两项:
- 优化前后的各版本fibjs
- 优化后的fibjs、使用cluster的nodejs、基于模块的nginx、使用多核的go
测试环境及方法
服务器
CPU:8核(4sockets*2cores),每核2.1GHz
内存:24GB
客户端(10个)
CPU:1核,每核2.1GHz
内存:1GB
测试流程:
- 首先在服务器端开启server
- 调度机ssh到客户端触发对服务器的wrk请求(测试时长为30min)
- 测试的并发量从1w~10w,递增幅度为1w
测试项一:fibjs优化前后性能对比
3个版本的fibjs,分别为:
- v0: 原版fibjs
- v1: 优化了SOCKET_ BUFF_ SIZE大小
- v2: socket 等待时释放 buffer 内存
测试结果
QPS(req/sec)
\ | v0 | v2 | v3 |
---|---|---|---|
10000 | 12750 | 15971 | 17705 |
20000 | 12757 | 15480 | 17893 |
30000 | 12551 | 15362 | 18045 |
40000 | 11858 | 15087 | 17326 |
50000 | 11678 | 15503 | 17237 |
60000 | 11708 | 15093 | 16800 |
70000 | 10682 | 14674 | 16513 |
80000 | 9395 | 14223 | 15840 |
90000 | 8733 | 13681 | 15209 |
100000 | 8645 | 13375 | 14664 |
QPS曲线如下图:
内存占用(单位GB)
\ | v0 | v1 | v2 |
---|---|---|---|
10000 | 1.55 | 0.37 | 0.14 |
20000 | 2.95 | 0.68 | 0.22 |
30000 | 4.32 | 1.05 | 0.27 |
40000 | 5.82 | 1.32 | 0.35 |
50000 | 7.27 | 1.63 | 0.41 |
60000 | 8.79 | 1.89 | 0.47 |
70000 | 10.15 | 2.32 | 0.53 |
80000 | 11.57 | 2.63 | 0.6 |
90000 | 13.03 | 2.98 | 0.67 |
100000 | 14.5 | 3.36 | 0.79 |
内存占用曲线如下图:
测试项二:优化后的fibjs与其他服务器性能对比
服务器分别为:
- 优化后的fibjs (v0.1.7)
- 使用了cluster的nodejs (v0.10.25)
- 使用多核的go (v1.5.1)
- nginx模块 (v1.9.2)
测试结果
QPS(req/sec)
\ | fibjs | nodejs | go | nginx |
---|---|---|---|---|
10000 | 17705 | 8268 | 22284 | 19393 |
20000 | 17893 | 5866 | 21975 | 19096 |
30000 | 18045 | 4412 | 21531 | 18521 |
40000 | 17326 | 2817 | 21362 | 18331 |
50000 | 17237 | 2179 | 21277 | 18328 |
60000 | 16800 | 1853 | 21113 | 17936 |
70000 | 16513 | 1536 | 21029 | 17644 |
80000 | 15840 | 1385 | 20497 | 16973 |
90000 | 15209 | 1232 | 19843 | 16966 |
100000 | 14664 | 1089 | 19386 | 16805 |
QPS曲线如下图
内存占用(单位GB)
\ | fibjs | nodejs | go | nginx |
---|---|---|---|---|
10000 | 0.14 | 0.56 | 0.37 | 0.1 |
20000 | 0.22 | 0.68 | 0.68 | 0.1 |
30000 | 0.27 | 0.76 | 1.05 | 0.11 |
40000 | 0.35 | 0.84 | 1.32 | 0.12 |
50000 | 0.41 | 0.92 | 1.63 | 0.12 |
60000 | 0.47 | 1.02 | 1.89 | 0.12 |
70000 | 0.53 | 1.08 | 2.32 | 0.13 |
80000 | 0.6 | 1.14 | 2.63 | 0.13 |
90000 | 0.67 | 1.3 | 2.98 | 0.14 |
100000 | 0.79 | 1.45 | 3.36 | 0.14 |
内存占用曲线如下图
服务器CPU占用情况
\ | fibjs | nodejs | go | nginx |
---|---|---|---|---|
CPU占用率 | 500~600 | (90~98)*8 | 200~400 | (30~35)*8 |
结论
模型
- fibjs和go均为单进程多线程
- nodejs(cluster)和nginx是多进程
内存占用
- go平均每个请求所需内存最多,约为320KB
- fibjs和nodejs平均每个请求所需内存相差不多,均约为72KB,不过nodejs占用的固定内存多于fibjs
- nginx采用了Zero Copy技术,内存占用非常低
QPS
- nginx和go都未跑满CPU,且在高并发下表现良好
- fibjs的http协议栈处理是多线程的,且CPU占用率高于nginx和go,在高并发情况下表现良好
- nodejs在3w并发时已基本跑满CPU,且性能下滑严重
相关代码
服务器代码1_fibjs
var http = require('http'),
net = require('net'),
coroutine = require('coroutine');
var interval = 1000;
var hdlr = new http.Handler(function(req) {
if (req.address == "/fibjs") {
req.response.write('Hello, World!');
}
})
var demon = function() {
while (true) {
console.error("connections:", svr.stats.connections,
"\trequest:", hdlr.stats.request,
"\tresponse:", hdlr.stats.response);
hdlr.stats.reset();
svr.stats.reset();
coroutine.sleep(interval);
}
}
var svr = new net.TcpServer(8080, hdlr);
coroutine.start(demon);
svr.run();
服务器代码2_使用cluster的nodejs
var cluster = require('cluster'),
http = require('http'),
url = require('url'),
numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log("master start...");
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('listening', function(worker, address) {
console.log('listening: worker ' + worker.process.pid + ', Address: ' + address.address + ":" + address.port);
});
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
http.createServer(function(req, res) {
var pathname = url.parse(req.url).pathname;
if ("/node" === pathname) {
res.writeHead(200);
res.end("Hello World!\n");
}
}).listen(8080);
}
服务器代码3_使用多核的go
package main
import (
"net/http"
"runtime"
"log"
)
func hdlr_hello(rw http.ResponseWriter, req *http.Request) {
req.ParseForm()
rw.Write([]byte("Hello world!"))
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
http.HandleFunc("/go", hdlr_hello) //设置访问的路由
err := http.ListenAndServe(":8083", nil) //设置监听的端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
// panic(err)
}
}
服务器代码4_nginx模块
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_buf.h>
/*
*cf 指向ngx_conf_t 结构体指针,从指令后面传过来的参数
*cmd 指向当前结构体ngx_command_t 的指针(互相指)
*conf 指向自定义模块配置结构体的指针
*/
static char *ngx_http_hello_world(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_command_t ngx_http_hello_world_commands[]={
{
ngx_string("hello_world"), //指令名称,nginx.conf中使用
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, //注释1
ngx_http_hello_world, //回调函数,上面申明
0, //保持的值存放位置:全局,server,location
0, //指令的值保存位置
NULL
},
ngx_null_command //读入ngx_null_command 指令后停止
};
//ngx_http_<module name>_module_ctx用于创建和合并三个配置
static ngx_http_module_t ngx_http_hello_world_module_ctx={
NULL, //preconfiguration
NULL, //postconfiguration
NULL, //create main configuration
NULL, //init main configuration
NULL, //create server configuration
NULL, //merge server configuration
NULL, //create location configuration
NULL //merge localtion configuration
};
//nginx进程,线程相关,ngx_http_<module name>_module把数据处理关联到特定模块
ngx_module_t ngx_http_hello_world_module={
NGX_MODULE_V1,
&ngx_http_hello_world_module_ctx, //module context
ngx_http_hello_world_commands, //module directives
NGX_HTTP_MODULE, //module type
NULL, //init master
NULL, //init module
NULL, //init process
NULL, //init thread
NULL, //exit thread
NULL, //exit process
NULL, //exit master
NGX_MODULE_V1_PADDING
};
static ngx_int_t ngx_http_hello_world_handler(ngx_http_request_t *r)
{
if(!(r->method & NGX_HTTP_GET))
return NGX_HTTP_NOT_ALLOWED;
ngx_int_t rc = ngx_http_discard_request_body(r);
if(rc != NGX_OK)
return rc;
ngx_str_t type = ngx_string("text/plain");
ngx_str_t response = ngx_string("Hello World!");
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = response.len;
r->headers_out.content_type = type;
rc = ngx_http_send_header(r);
if(rc == NGX_ERROR || rc > NGX_OK || r->header_only)
return rc;
ngx_buf_t *b;
b = ngx_create_temp_buf(r->pool, response.len);
if(b == NULL)
return NGX_HTTP_INTERNAL_SERVER_ERROR;
ngx_memcpy(b->pos, response.data, response.len);
b->last = b->pos + response.len;
b->last_buf = 1;
ngx_chain_t out;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r,&out);
}
//回调函数,1获得location中的“核心”结构体,2为他分配个处理函数
static char *ngx_http_hello_world(ngx_conf_t *cf,ngx_command_t *cmd,void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);
clcf->handler = ngx_http_hello_world_handler;
return NGX_CONF_OK;
}
###脚本_集群调度
#!/usr/bash
if [ $# -lt 1 ]; then
echo ""
echo "option [conn | close]"
echo ""
exit 0
fi
user=verdant
concurrency=50000
duration=600
logdir="log/"
logfile="ctrl.log."
ip_array=("171" "193" "209" "213" "231" "219" "222" "223" "224" "225")
conn=$(($concurrency/${#ip_array[*]}))
sleeptime=1
# sleeptime=$(($concurrency/10000+1))
remote_cmd="~/wrk -t10 -c$conn -d""$duration""s http://192.168.1.61:8080/fibjs"
# remote_cmd="~/wrk -t10 -c$conn -d""$duration""s http://192.168.1.61:8080/node"
# remote_cmd="~/wrk -t10 -c$conn -d""$duration""s http://192.168.1.61:8080/go"
# remote_cmd="~/wrk -t10 -c$conn -d""$duration""s http://192.168.1.61:8080/nginx"
function main()
{
echo "${#ip_array[*]} client each: $conn"
for ip in ${ip_array[*]}
do
if [ -e "$logdir$logfile$ip" ]; then
rm "$logdir$logfile$ip"
fi
done
if [ $1 = 'close' ]; then
for ip in ${ip_array[*]}
do
result=$(ssh -t -t $user@"192.168.1."$ip "killall -9 wrk">> $logdir$logfile$ip &)
sleep "$sleeptime"
echo $result
done
exit 0
fi
if [ $1 = 'conn' ]; then
for ip in ${ip_array[*]}
do
ssh -t -t $user@"192.168.1."$ip "$remote_cmd">> $logdir$logfile$ip &
sleep "$sleeptime"
done
fi
}
function recovery()
{
running=0
server_ok=()
while [ $running -ne ${#ip_array[*]} ]
do
for ip in ${ip_array[*]}
do
if [ ! -s "$logdir$logfile$ip" ]; then
echo "restart:""$logdir$logfile$ip"
sleep $((5*$sleeptime))
ssh -t -t $user@"192.168.1."$ip "killall -9 wrk"
sleep $((10*$sleeptime))
ssh -t -t $user@"192.168.1."$ip "$remote_cmd">> $logdir$logfile$ip &
else
if [[ "${server_ok[@]/$ip/}" = "${server_ok[@]}" ]]; then
server_ok[${#server_ok[@]}]=$ip
running=$(($running+1))
fi
fi
done
done
}
function calculate()
{
result1=0
result2=1
while [ $1 = 'conn' -a $result1 != $result2 ]
do
result1=`cat $logdir$logfile*|grep Requests/sec|awk '{print $2}'|awk '{sum+=$1} END {print sum}'`
if [ "$result1" = "" ]; then
result1=0
sleep 10
continue
fi
sleep 10
result2=`cat $logdir$logfile*|grep Requests/sec|awk '{print $2}'|awk '{sum+=$1} END {print sum}'`
done
echo "Requests/sec: $result1"
cat $logdir$logfile*|grep Latency
}
main $1
sleep $((${#ip_array[*]}*$sleeptime))
#客户端异常重连机制
if [ $1 = 'conn' ]; then
recovery
fi
sleep $duration
echo "calculate..."
calculate $1
不可否认node的确曾经依靠纯异步有不错的性能表现,但现在要讲高性能,这得看跟谁比了,跟同步比那还能算高性能的,但跟其他异步框架比起来,还能说是高性能么?从测试成绩来看,顶多算还行,就算用4.2,5.x来测,结果也好看不到哪里去,至少不会出现数量极的改变。
现在node社区的人,唯一可以自慰的就是生态了,为什么会这样?因为性能已经不好意思拿来吹了。
感谢响马大哥的测试。
很详细很赞的测试,我只能膜拜了。 说说几点我的观点吧。
- 不管是fibjs, nodejs, go或者其他工具,在大规模以及大项目中使用不会只有一台服务器在跑,都会有相应的分流来均衡.
- 追求性能的提升当然是件好事也是对各种技术的挑战以及自我的挑战,但是很多小公司小项目或者中型项目,在没有遇到性能瓶颈的时候 我觉得没有很大的必要去追求高性能高并发. 我这里只是对那些小公司小项目说的。(楼主对技术的追求还是很赞的)
- 性能瓶颈往往是出现在数据的存储以及调用方面以及磁盘IO
对楼主还是敬佩的。上面所列的几点只不过我站在客观角度所言。
做这个测试的原因当然是出于是业务需要,有一些业务场景是需要维持大量连接而运算量并不大的。
测试过程中也有两次对 fibjs 优化的结果。
最初并没有打算测试 nodejs,因为知道它肯定抗不住。但是考虑到发出来肯定有人会问 nodejs 的数据,就特意测了一份来。做出来才知道,nodejs 比我能想象的更差劲。一万以内,fibjs 是它的 2 倍,到 10 万的时候,已经是它的 15 倍了。就这点性能还启用了 cluster。
至于招人,还真不难,校招新人一周内上手,比 nodejs 快多了,而且基本不会写错代码。
fibjs的思想还是非常值得学习的,利用协程隐藏回调,实现形式同步,实质异步。形式同步是大势所驱,编程模型应该越简单越好,这不仅仅对nodejs,对其他所有技术都一样。@xicilion 大哥的公司能够在大学里招人过来,一个礼拜上手,本身就说明了这点。学校里教的就是同步编程,业务大多也是同步需求。
显式的异步代码对异步本身的发展就是阻碍。我们只需要异步带来的性能好处,但并不需要由此引入的编程复杂度。
@coordcn 虽然我掉进了回调的坑,但是我觉得掉的还蛮值得的。不管代码层面异步还是同步,我个人觉得没关系,而且通过异步代码的编写我多少了解了一点异步回调隐藏的方法。如果fibjs雄起甚至干掉nodejs也是蛮好的。如果确实性能突出, 而且生态好的话,必须用!大大们多做做实验吧~撇开这个测试不说,希望响马能多广告发扬一下fibjs,让广大编程工作者来检验一下,要是真把nodejs屁股给踢疼了,我觉得应该很赞!
对异步理解到了一个程度,异步的形式已经不重要了,这个时候追求的应该是非显式的异步,就像武功高强的人达到无招胜有招的境界。很多原来同步的语言,现在通过改进后,nodejs已经没有任何性能优势了,如果继续抱着原有的编程模型不放,只会越来越落后。
node在阿里的应用也仅仅限于大前端,阿里的人自己在之乎上说核心交易nodejs成绩为0。这里的原因,我想,java成熟稳定是一方面,nodejs显示异步的编程模型和业务同步逻辑之间的矛盾才是最主要的。核心交易业务复杂的程度,不是简单的理解了回调,理解了异步就能应对的。你只要自己试着用nodejs写一个异步文件夹遍历,就可以想象在复杂业务条件下,显示的异步是多么的无力。
回调不能代表异步,如果你愿意,同步代码也可以用回调来表达。回调在异步程序中的作用恰恰是保证同步,保证执行顺序的。
回调模式下的异步,现在已经成为nodejs的包袱,generator和promise的缓和作用有限,学习成本还是有的,是典型的为了解决一个问题,又引入另外一个问题的做法。这些东西在前端是适用的,但面对复杂的后端,形式同步才是终极解决方案。
nodejs用来做demo,做一些大前端页面渲染是合适的,这些应用不涉及复杂的后端数据库交互过程,只要一两次异步就能搞定,阿里就是这么搞的,真正的后端将数据准备好,nodejs取过来,套上模板渲染传给浏览器。
fibjs的源代码我大致浏览过,实现思路也大致了解,细节没有深入。我更看重的是思路,有了思路,代码堆出来只是时间问题。
我自己也在利用libuv和lua实现一个形式同步的网络库,做这个事情,是去年受到fibjs的触动,我那时正好在写文件夹遍历,被回调搞得很爽,这个过程我就有了反思,后来也看了openresty的代码,我c++不行,fibjs,v8都玩不转,就选择了c + lua + libuv,lua天生提供协程支持,openresty又有现成的通过协程回调转同步代码参考,lua和javascript在本质上很相似,libuv读nodejs的源代码的时候看过,自己很喜欢,自然就用这个网络库了。整个框架已经搭好,中间重构了一次,马上就可以发布出来。
跟响马大哥的fibjs是没法比的,整个项目还很原始,但是通过协程异步转同步的思路还是表达出来了。fibjs的让我对异步有了更进一步的认识,同步代码才是异步程序的最终归宿,编程思想才是最重要的,非常感谢引路人响马大哥。
我c++水平太次,只能看懂代码,自己写不行,无奈只好用c+lua来实现,无法为fibjs做什么贡献。
其实你们可以把 fibjs理解为一个能运行v8的框架,这个框架包括了文件数据库网络等等的东西 node理解为一个封装有常用系统api和其它一些常用类库的服务端用的js运行时 只不过js引擎用的v8 其实也可以换成其他的比如微软就换成查克拉了 其它有些人换成spidermonkey了 nodejs 和 fibjs 差不多的地方就是都碰巧选用了v8而已
@jiangzhuo fibjs 选 v8 倒不是碰巧,spidermonkey 和 JavascriptCore 都不能很好地支持 fiber,只有 v8 能够兼容这个模式。微软的引擎向来支持多线程,应该可以使用,但是跨平台是个问题。
因此 fibjs 最终选择了 v8。nodejs 对引擎依赖比较小,倒是可以很方便移植,几个 Javascript 引擎都有移植的版本。
- 我试过在ubuntu lts 32bit 上编译,每次都是段错误。
- 在vs2015社区版无法按说明正常编译(非要用旧版本vs才可以么)。
- 希望能提供二进制版本下载
- 官网文档有点…(谈一些喜欢喝什么的问题真的好么)
与其唱双簧,还是希望能把官网的引导做好一点。(官网打开很慢,jquery的content-download时间20s,也不知道是网络问题还是服务器问题还是我的浏览器爆炸了)
用nodejs其实没别的,就是生态太完善了,想要的库基本上都有,再加上性能也还可以。最大的问题就是异步。成也异步败也异步,不过现在也已经有比较好的方案再解决这个问题。至于性能,很多时候说实话不是最关键,初创企业能有几个项目真能一开始就碰到太多性能问题,开发效率和速度才是最关键啊。
统一回复:
@dlutwuwei 没有可能。
@lingmm 一、中间有几个版本在 32 bits 上运行是会出错,我们没做好全平台测试,一直也没有人回报,目前已经发现并修复了 二、vs2015 稍微转换即可编译,而设置为 vs2015,vs2013 却肯定不能编译了。windows 版本仅为兼容,虽然性能同样很好,但是我们自己并没有大量使用,虽然我知道有公司在 windows 上投入生产环境,但是我们没有一手数据,所以不建议生产环境使用,做做测试尚可 三、人力所限,不提供二进制下载,如果是 mac,可以通过 brew install fibjs 直接安装 四、文档是萝卜青菜,各有所爱,大家都是程序员,就别在那些虚头巴脑的文字上纠结了,直接干代码就好了 五、官网慢是因为 vps 慢,建议从 github 下载 docs 本地阅读,国内 vps 还要备案,烦躁
@arden 文无第一,武无第二,大家都是程序员,快就是快,不快就是不快。你看我就从来不辩解说生态不重要,没有就是没有。