精华 很久没来,丢一份前阵子做的 10 万连接性能测试 (fibjs, golang, nginx, nodejs)
发布于 17 天前 作者 xicilion 1687 次浏览 来自 分享

测试内容

动态HTTP服务器极限性能,分为两项:

  • 优化前后的各版本fibjs
  • 优化后的fibjs、使用cluster的nodejs、基于模块的nginx、使用多核的go

测试环境及方法

服务器

CPU:8核(4sockets*2cores),每核2.1GHz
内存:24GB

客户端(10个)

CPU:1核,每核2.1GHz
内存:1GB

测试流程:

  1. 首先在服务器端开启server
  2. 调度机ssh到客户端触发对服务器的wrk请求(测试时长为30min)
  3. 测试的并发量从1w~10w,递增幅度为1w

测试项一:fibjs优化前后性能对比

3个版本的fibjs,分别为:

  1. v0: 原版fibjs
  2. v1: 优化了SOCKET_ BUFF_ SIZE大小
  3. 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曲线如下图:

case1-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

内存占用曲线如下图:

case1-Mem

测试项二:优化后的fibjs与其他服务器性能对比

服务器分别为:

  1. 优化后的fibjs (v0.1.7)
  2. 使用了cluster的nodejs (v0.10.25)
  3. 使用多核的go (v1.5.1)
  4. 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曲线如下图

case2-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

内存占用曲线如下图

case2-Mem

服务器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
42 回复

node用0.10、、、、、

@xicilion 会的,用4.2或5.x

@i5ting 估计会有多大差别?提升 100% 还是提升 1000%?提升 100% 以内就不麻烦再测试一遍了。

然而生态、、、、、

不可否认node的确曾经依靠纯异步有不错的性能表现,但现在要讲高性能,这得看跟谁比了,跟同步比那还能算高性能的,但跟其他异步框架比起来,还能说是高性能么?从测试成绩来看,顶多算还行,就算用4.2,5.x来测,结果也好看不到哪里去,至少不会出现数量极的改变。

现在node社区的人,唯一可以自慰的就是生态了,为什么会这样?因为性能已经不好意思拿来吹了。

感谢响马大哥的测试。

支持国产是重点

定位要准确,没有一种语言是万金油,选择适合自己的

fibjs 在牛X再好,我也不会去学习去使用用,也不会叫同事去学习去使用,只是一年内不会,等以后用的人多起来再观望。 在一个团队或者项目中选择要使用fibjs,不但要看团队里面的使用的人多不多,还要看外面用的人多不多,否则招不到人也是悲剧。 当然,本着追求探索的目的,玩玩还是可以的

nodejs 代码里 var pathname = url.parse(req.url).pathname; 这句可以省掉,直接用"/node" === req.url

很详细很赞的测试,我只能膜拜了。 说说几点我的观点吧。

  1. 不管是fibjs, nodejs, go或者其他工具,在大规模以及大项目中使用不会只有一台服务器在跑,都会有相应的分流来均衡.
  2. 追求性能的提升当然是件好事也是对各种技术的挑战以及自我的挑战,但是很多小公司小项目或者中型项目,在没有遇到性能瓶颈的时候 我觉得没有很大的必要去追求高性能高并发. 我这里只是对那些小公司小项目说的。(楼主对技术的追求还是很赞的)
  3. 性能瓶颈往往是出现在数据的存储以及调用方面以及磁盘IO

对楼主还是敬佩的。上面所列的几点只不过我站在客观角度所言。

做这个测试的原因当然是出于是业务需要,有一些业务场景是需要维持大量连接而运算量并不大的。

测试过程中也有两次对 fibjs 优化的结果。

最初并没有打算测试 nodejs,因为知道它肯定抗不住。但是考虑到发出来肯定有人会问 nodejs 的数据,就特意测了一份来。做出来才知道,nodejs 比我能想象的更差劲。一万以内,fibjs 是它的 2 倍,到 10 万的时候,已经是它的 15 倍了。就这点性能还启用了 cluster。

至于招人,还真不难,校招新人一周内上手,比 nodejs 快多了,而且基本不会写错代码。

@flyingcodes

fibjs的思想还是非常值得学习的,利用协程隐藏回调,实现形式同步,实质异步。形式同步是大势所驱,编程模型应该越简单越好,这不仅仅对nodejs,对其他所有技术都一样。@xicilion 大哥的公司能够在大学里招人过来,一个礼拜上手,本身就说明了这点。学校里教的就是同步编程,业务大多也是同步需求。

显式的异步代码对异步本身的发展就是阻碍。我们只需要异步带来的性能好处,但并不需要由此引入的编程复杂度。

@xicilion 为何还不干掉go那个我讨厌的语言

@coordcn 虽然我掉进了回调的坑,但是我觉得掉的还蛮值得的。不管代码层面异步还是同步,我个人觉得没关系,而且通过异步代码的编写我多少了解了一点异步回调隐藏的方法。如果fibjs雄起甚至干掉nodejs也是蛮好的。如果确实性能突出, 而且生态好的话,必须用!大大们多做做实验吧~撇开这个测试不说,希望响马能多广告发扬一下fibjs,让广大编程工作者来检验一下,要是真把nodejs屁股给踢疼了,我觉得应该很赞!

@coordcn 首先我也肯定fibjs另一种开发思想和代码风格,我只是现在不看好fibjs在其他公司的应用,比较了解的人少,深入的人估计只有@xicilion自己, 至于 @xicilion敢招大学里面的新手,那是因为他自己了解,其他人敢在他们公司的项目里面使用?然后也去学校里面招人?

@DevinXian

对异步理解到了一个程度,异步的形式已经不重要了,这个时候追求的应该是非显式的异步,就像武功高强的人达到无招胜有招的境界。很多原来同步的语言,现在通过改进后,nodejs已经没有任何性能优势了,如果继续抱着原有的编程模型不放,只会越来越落后。

node在阿里的应用也仅仅限于大前端,阿里的人自己在之乎上说核心交易nodejs成绩为0。这里的原因,我想,java成熟稳定是一方面,nodejs显示异步的编程模型和业务同步逻辑之间的矛盾才是最主要的。核心交易业务复杂的程度,不是简单的理解了回调,理解了异步就能应对的。你只要自己试着用nodejs写一个异步文件夹遍历,就可以想象在复杂业务条件下,显示的异步是多么的无力。

回调不能代表异步,如果你愿意,同步代码也可以用回调来表达。回调在异步程序中的作用恰恰是保证同步,保证执行顺序的。

回调模式下的异步,现在已经成为nodejs的包袱,generator和promise的缓和作用有限,学习成本还是有的,是典型的为了解决一个问题,又引入另外一个问题的做法。这些东西在前端是适用的,但面对复杂的后端,形式同步才是终极解决方案。

nodejs用来做demo,做一些大前端页面渲染是合适的,这些应用不涉及复杂的后端数据库交互过程,只要一两次异步就能搞定,阿里就是这么搞的,真正的后端将数据准备好,nodejs取过来,套上模板渲染传给浏览器。

@coordcn 多谢鸡汤。PS:你撸汉字快赶上我撸代码速度了0.0

@flyingcodes

fibjs的源代码我大致浏览过,实现思路也大致了解,细节没有深入。我更看重的是思路,有了思路,代码堆出来只是时间问题。

我自己也在利用libuv和lua实现一个形式同步的网络库,做这个事情,是去年受到fibjs的触动,我那时正好在写文件夹遍历,被回调搞得很爽,这个过程我就有了反思,后来也看了openresty的代码,我c++不行,fibjs,v8都玩不转,就选择了c + lua + libuv,lua天生提供协程支持,openresty又有现成的通过协程回调转同步代码参考,lua和javascript在本质上很相似,libuv读nodejs的源代码的时候看过,自己很喜欢,自然就用这个网络库了。整个框架已经搭好,中间重构了一次,马上就可以发布出来。

@DevinXian

那你写代码速度好快了,我除非写测试代码,不然一天也就顶多200行不到。

@coordcn 赞,期待中。。。

这样看go这么牛逼,要性能那直接上go,至少生态要好啊。

有对比,对语言,还是社区都是好事。 @xicilion 可否有空翻成英文,发到 HN 等,看看社区反映。

fibjs确实不错,目前还属于极客,还需要时间吧。

@coordcn 哈哈,你自己都说了你c++不行,那么出了bug你解决不了,我没有关注fibjs,只关注过node.js,但深入使用了node.js就发现很多bug,其中大部分是由于c++(包括node的核心库和v8引擎的)码引起的,所以即使深入使用node了都会引起问。

@xicilion

跟响马大哥的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 引擎都有移植的版本。

@coordcn 看是什么代码了,哈哈,我指的其实是字符数!

我想知道nodejs以后有没有性能提高的可能,在现有架构下,毕竟前端工程师不想放弃npm丰富的支持

fibjs好像很有趣。我去看看。

  1. 我试过在ubuntu lts 32bit 上编译,每次都是段错误。
  2. 在vs2015社区版无法按说明正常编译(非要用旧版本vs才可以么)。
  3. 希望能提供二进制版本下载
  4. 官网文档有点…(谈一些喜欢喝什么的问题真的好么)

与其唱双簧,还是希望能把官网的引导做好一点。(官网打开很慢,jquery的content-download时间20s,也不知道是网络问题还是服务器问题还是我的浏览器爆炸了)

用nodejs其实没别的,就是生态太完善了,想要的库基本上都有,再加上性能也还可以。最大的问题就是异步。成也异步败也异步,不过现在也已经有比较好的方案再解决这个问题。至于性能,很多时候说实话不是最关键,初创企业能有几个项目真能一开始就碰到太多性能问题,开发效率和速度才是最关键啊。

统一回复:

@dlutwuwei 没有可能。

@lingmm 一、中间有几个版本在 32 bits 上运行是会出错,我们没做好全平台测试,一直也没有人回报,目前已经发现并修复了 二、vs2015 稍微转换即可编译,而设置为 vs2015,vs2013 却肯定不能编译了。windows 版本仅为兼容,虽然性能同样很好,但是我们自己并没有大量使用,虽然我知道有公司在 windows 上投入生产环境,但是我们没有一手数据,所以不建议生产环境使用,做做测试尚可 三、人力所限,不提供二进制下载,如果是 mac,可以通过 brew install fibjs 直接安装 四、文档是萝卜青菜,各有所爱,大家都是程序员,就别在那些虚头巴脑的文字上纠结了,直接干代码就好了 五、官网慢是因为 vps 慢,建议从 github 下载 docs 本地阅读,国内 vps 还要备案,烦躁

@arden 文无第一,武无第二,大家都是程序员,快就是快,不快就是不快。你看我就从来不辩解说生态不重要,没有就是没有。

我记得我去年在这里就看到,楼主发测试了。今年又是测试。。。对,fibjs快!

楼主大神,弄的我都不想学node了,好样的

佩服楼主的功力,但是还是不看好。

每次看到楼主的帖子就想放弃nodejs,还是安心做一个前端吧,想当初在java的泥潭里受不了,转前端也就是因为nodejs才下定决心

@dlutwuwei 为啥要放弃,是因为你觉得楼主太厉害了吗。。。

这种贴,评论永远比正文更精彩

回到顶部