記一次Node和Go的性能測試

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

準備工作

  1. 測試機和試壓機系統都是ubuntu 18.04.1
  2. 首先安裝node和go,版本分別如下:
  3. node 10.13.0
  4. go 1.11
  5. 測試機和試壓機修改fd的限制​ ulimit -n 100000 ​,否則fd很快就用完了。
  6. 如果是試壓機是單機,並且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
  1. 測試工具我用的是siege,版本是​3.0.8​。
  2. 測試的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
記一次Node和Go的性能測試

  • Go - 單進程多線程模型,系統剩餘CPU差不多也有30%,查了一下原因是因為網卡已經被打滿了,千兆網卡看了下已經紮紮實實被打滿了。
  • qps: 37000
記一次Node和Go的性能測試

千兆網卡已經被打滿了

記一次Node和Go的性能測試

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

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

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

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

  • Node 多進程模型,並且用​taskset​命令把進程和CPU綁定了
  • qps: 17000 (比不綁定cpu性能提高了10%多)
記一次Node和Go的性能測試

結論 :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
記一次Node和Go的性能測試

  • go:go不用做特殊處理,不用感知進程數量,不用綁定cpu,改了代碼直接走起
  • qps: 6700,依然是node的兩倍多
記一次Node和Go的性能測試

結論

Node因為用了V8,從而繼承了單進程單線程的特性,單進程單線程好處是邏輯簡單,什麼鎖,什麼信號量,什麼同步,都是浮雲,老夫都是await一把梭。

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

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

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

Node

優點

  1. 單進程單線程,邏輯清晰
  2. 多進程間環境隔離,單個進程down掉不會影響其他進程
  3. 開發語言受眾廣,上手易

缺點

  1. 多進程模型下,註定對於多核的利用會比較複雜,需要針對不同場景(cpu密集或者I/O密集)來設計程序
  2. 語言本身因為歷史遺留問題,存在較多的坑。

Go

優點

  1. 單進程多線程,單個進程即可利用N核
  2. 語言較為成熟,在語言層面就可以規避掉一些問題。
  3. 單從結果上來看,性能確實比node好。

缺點

  1. 包管理方案並不成熟,相對於npm來說,簡直被按在地上摩擦
  2. if err != nil ….
  3. 多線程語言無法避開同步,鎖,信號量等概念,如果對於鎖等處理不當,會使性能大大降低,甚至死鎖等。
  4. 本身就一個進程,稍有不慎,進程down了就玩完了。


分享到:


相關文章: