对于一个有追求的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比起来,性能还是差很多,还得仔细的查找原因,到底因为什么。