1. 程式人生 > >記一次Node和Go的效能測試

記一次Node和Go的效能測試

以前簡單測過go的效能,高併發場景下確實比node會好一些,一直想找個時間系統性地測一下,手頭正好有一臺前段時間買的遊戲主機,裝了ubuntu就開測了

準備工作
測試機和試壓機系統都是ubuntu 18.04.1
首先安裝node和go,版本分別如下:

node 10.13.0

go 1.11

測試機和試壓機修改fd的限制​ ulimit -n 100000 ​,否則fd很快就用完了。

如果是試壓機是單機,並且QPS非常高的時候,也許你會經常見到試壓機有N多的連線都是​TIME_WAIT​狀態,具體原因可以在網上搜一下,執行以下命令即可:

$ sysctl -w net.ipv4.tcp_timestamps=1
$ sysctl -w net.ipv4.tcp_tw_reuse=1 
$ sysctl -w net.ipv4.tcp_tw_recycle=1

測試工具我用的是siege,版本是​3.0.8​。

測試的js程式碼和go程式碼分別如下:
Node(官網的cluster示例程式碼)

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    console.log(`Master ${process.pid} is running`);
    
    // Fork workers.
    for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
    }
    
    cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
});} else {
    // Workers can share any TCP connection
    // In this case it is an HTTP server
    http.createServer((req, res) => {
    res.end('hello world\n');
}).listen(8000);
    
console.log(`Worker ${process.pid} started`);}
Go
package main

import(
    "net/http"
    "fmt"
)

func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello world")
}

func main() {
    http.HandleFunc("/", hello);
    err := http.ListenAndServe(":8000", nil);
    if err != nil {
            
    }
}

開始測試
首先開始併發量的測試,然而。。。遊戲主機的CPU是i7-8700K,效能太強,以至於拿了兩臺mac也沒能壓滿。。。

四處尋找一番,找到了好幾年前花了千把塊錢配的nas,上去一看是顆雙核的i3-4170,妥了,這下肯定沒問題

正式開始
跳過了小插曲,直接開測

I/O密集型場景
Node - 多程序模型,可以看到因為所有請求都由master程序轉發,master程序成為了瓶頸,在CPU佔用100%的情況下,worker程序僅僅只佔了50%,因此整體CPU利用率只有70%。
qps: 6700

Go - 單程序多執行緒模型,系統剩餘CPU差不多也有30%,查了一下原因是因為網絡卡已經被打滿了,千兆網絡卡看了下已經紮紮實實被打滿了。
qps: 37000

千兆網絡卡已經被打滿了

在helloworld場景下,如果我們有萬兆網絡卡,那麼go的qps就是50000,go是node的7倍多,乍一看這個結果就非常有意思了,下面我們分析下原因:

首先node官方給的cluster的例子去跑壓測是有問題的,CPU物理核心雙核,超執行緒成4核,姑且我們就認為有4核。
cluster的方案,先起主程序,然後再起跟CPU數量一樣的子程序,所以現在總共5個程序,4個核心,就會造成上下文頻繁切換,QPS異常低下
基於以上結論,我減少了一個worker程序的數量
Node 多程序模型下,1個主程序,3個worker程序
qps: 15000 (直接翻倍)

以上的結果證明我們的想法是對的,並且這個場景下CPU利用率幾乎達到了100%,不是很穩定,暫且我們可以認為壓滿了,所以我們可以認為1master4worker場景下,就是因為程序上下文頻繁切換導致的qps低下

那麼進一步想,如果把程序和CPU繫結,是不是可以進一步提高qps?

Node 多程序模型,並且用​taskset​命令把程序和CPU綁定了
qps: 17000 (比不繫結cpu效能提高了10%多)

結論 :node在把CPU壓滿的情況下,最高qps為:17000,而go在cpu利用率剩餘30%的情況,qps已經達到了37000,如果網絡卡允許,那麼go理論上可以達到50000左右的qps,是node的2.9倍左右。
CPU密集場景
為了模擬CPU密集場景,並保證兩邊場景一致,我在node和go中,分別添加了一段如下程式碼,每次請求迴圈100W次,然後相加:

var b int
for a := 0; a < 1000000; a ++ {
	 b = b + a
}

Node 多程序模型:這裡我的測試方式是開了4個worker,因為在CPU密集場景下,瓶頸往往在woker程序,並且將4個程序分別和4個核繫結,master程序就讓他隨風飄搖吧
qps: 3000

go:go不用做特殊處理,不用感知程序數量,不用繫結cpu,改了程式碼直接走起
qps: 6700,依然是node的兩倍多

結論
Node因為用了V8,從而繼承了單程序單執行緒的特性,單程序單執行緒好處是邏輯簡單,什麼鎖,什麼訊號量,什麼同步,都是浮雲,老夫都是await一把梭。

而V8因為最初是使用在瀏覽器中,各種設定放在node上看起來就不是非常合理,比如最大使用記憶體在64位下才1.4G,雖然使用buffer可以避開這個問題,但始終是有這種限制,而且單執行緒的設計(磁碟I/0是多執行緒實現),註定了在如今的多核場景下必定要開多個程序,而多個程序又會帶來程序間通訊問題。

Go不是很熟,但是前段時間小玩過幾次,挺有意思的,一直聽聞Go效能好,今天簡單測了下果然不錯,但是包管理和錯誤處理方式實在是讓我有點不爽。

總的來說在單機單應用的場景下,Go的效能總體在Node兩倍左右,微服務場景下,Go也能起多程序,不過相比在機制上就沒那麼大優勢了。

Node
優點
單程序單執行緒,邏輯清晰
多程序間環境隔離,單個程序down掉不會影響其他程序
開發語言受眾廣,上手易
缺點
多程序模型下,註定對於多核的利用會比較複雜,需要針對不同場景(cpu密集或者I/O密集)來設計程式
語言本身因為歷史遺留問題,存在較多的坑。
Go
優點
單程序多執行緒,單個程序即可利用N核
語言較為成熟,在語言層面就可以規避掉一些問題。
單從結果上來看,效能確實比node好。
缺點
包管理方案並不成熟,相對於npm來說,簡直被按在地上摩擦
if err != nil ….
多執行緒語言無法避開同步,鎖,訊號量等概念,如果對於鎖等處理不當,會使效能大大降低,甚至死鎖等。
本身就一個程序,稍有不慎,程序down了就玩完了。