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