转载

使用nodejs做模板渲染引擎服务

对于一个有追求的IT,会不断的追求网站性能,更不用说,直接面对用户的网站,特别担心高峰流量。为了快速迭代我们,初期使用了php,后来核心逻辑切换到go,php只是渲染模板。

最近我一段时间,一直感觉php性能差,也有可能是我心里的原因,我就想换到php,使用其他的方式,终于有一天,突然想到,我是否可以用户访问go,然后使用nodejs来渲染模板。趁着周六没人,自己会公司鼓捣。

欣喜若狂

我搜索发现这么一个地址https://github.com/baryshev/template-benchmark,介绍了各个开源模板引擎,以及渲染10万模板的耗时。我自己克隆下来,发现自己的电脑,比这个数据还好一些。都是一些小的模板并且不转义的情况下,1秒可以渲染100万的模板。我自己初步估计,如果模板大一些,即使下降到10万的模板,也是非常不错的。

在监控CPU利用率的情况下,发现只有单核CPU跑满了100%。我的电脑有8核,我使用taskset命令,强制将进程绑定到某个CPU,同时开了4个进程,4个核都满了,时间没有变慢。这么说,我8个核应该1秒,可以渲染80万的模板。

表现还不错

既然有这么好的性能,应该可以做成独立服务。go和nodejs通信可以使用长连接,也可以使用短连接。为了快速,我使用了短连接,http协议进行通信。

我初步写了一个简单的nodejs web 服务,使用ab压力单个端口,可以压到1万。如果开了多个进程,应该至少可以压到4万。

var http = require("http");
var i = 0;
var arguments = process.argv;
var dot = require('dot');
var data = require('./checkout');
var fs = require('fs');

var str = fs.readFileSync('./dot/tpl_unescaped.dot', 'utf8');
compiled = dot.template(str)

http.createServer(function(request, response) {
    console.log('request received ' + i++);
    response.writeHead(200, { "Content-Type": "text/plain" });
    response.write('hello world');
    response.end();
}).listen(arguments[2]);

prepareUnescaped = function(data) {
    return compiled(data);
};

console.log('server started, port is ' + arguments[2]);

然后,我就用GO简单写了http连接。

package main

import (
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strconv"
	"time"
	"os"
	"runtime"
)

var Total = 100000
var errNum = 0
var threads = 100
var transport = &http.Transport{
	Dial: func(netw, addr string) (net.Conn, error) {

		c, err := net.DialTimeout(netw, addr, time.Millisecond*200) //设置建立连接超时

		if err != nil {

			return nil, err

		}

		c.SetDeadline(time.Now().Add(time.Millisecond * 300)) //设置发送接收数据超时

		return c, nil

	},
}

func main() {
	f, _ := os.OpenFile("testlogfile", os.O_RDWR|os.O_CREATE, 0666)
	
	defer f.Close()

	log.SetOutput(f)
	runtime.GOMAXPROCS(runtime.NumCPU() - 1)
	runTest()
}

func runTest() {
	log.Println("begin")
	var i = 0
	var startTime = time.Now().UnixNano()

	chs := []chan int{}

	var threadId = 0
	for ; threadId != threads; threadId = threadId + 1 {
		ch := make(chan int)
		chs = append(chs, ch)
		go func(threadId int) {
			for ; i <= Total; i = i + 1 {

				var index = i
				var port = index%8 + 8120
				var startTime = time.Now().UnixNano()
				_, err := getResponse(strconv.Itoa(port))
				var endTime = time.Now().UnixNano()
				log.Println("lost time :", (endTime-startTime)/1000000)
				if err != nil {
					errNum = errNum + 1
					//log.Println("thread :", threadId, ";port:", port, ";index:", index, ";error:", err.Error())
				} else {
					//log.Println("thread :", threadId, ";port:", port, ";index:", index, ";  msg:", str)
				}
			}
			ch <- 0
		}(threadId)
	}

	for _, ch := range chs {
		<-ch
		close(ch)
	}
	var endTime = time.Now().UnixNano()
	log.Println("lost time :", (endTime-startTime)/1000000)
	log.Println("err num", errNum)
}

func getResponse(port string) (string, error) {
	req, err := http.NewRequest("GET", "http://10.236.103.101:"+port, nil)
	if err != nil {
		return "", err
	}

	//client := &http.Client{Transport:transport}
	client := &http.Client{}
	resp, perr := client.Do(req)
	if perr != nil {
		return "", perr
	}

	defer resp.Body.Close()
	bodyByte, rerr := ioutil.ReadAll(resp.Body)

	if rerr != nil {
		return "", rerr
	}
	return string(bodyByte), nil
}

如果连接本地的nodejs,100万请求,开启了500个goroutine,设置连接和读超时时间都是设置1秒,需要耗时53.7秒,平均每秒处理18621个请求。

如果远程访问,100万请求,开启1000个goroutine, 设置连接和读超时时间都是设置1秒,需要耗时44秒,平均每秒处理,22727个请求。

看了一下每一个请求的耗时,TP99在60ms左右,耗时确实有一些长了。

性能大降

看着刚才的数据表现还不错,我就选择dot这个渲染模板引擎,然后把一段简单的模板拷贝复制到200行。

<html>
	<head>
		<title>{title|s}</title>
	</head>
	<body>
		<p>{text|s}</p>
		{#projects}
			<a href="{url|s}">{name|s}</a>
			<p>{description|s}</p>
		{/projects}
		{^projects}
			No projects
		{/projects}
	</body>
</html>

使用go继续测试,本地连接的方式,每秒处理2000个请求,性能直接下降10倍,难以接受。使用了远程连接,每秒处理请求更少,最后发现网卡打满了,就放弃了这个测试。TP90的时候,170毫秒。

有点不太死心,或许是我go没有优化,于是使用了ab继续测试,单压一个端口,差不多3000个请求,TP90是100ms。

如果真的是2000话,那就没有必要做了。不过和ab比起来,性能还是差很多,还得仔细的查找原因,到底因为什么。

原文  http://www.woniubi.cn/using_nodejs_render_template_service/
正文到此结束
Loading...