協程的一些特性和優點我就不說了,網上很多文章都講述的很透徹。
協程可以理解為純使用者態的線程,其通過協作而不是搶佔來進行切換。相對於進程或者線程,協程所有的操作都可以在使用者態完成,建立和切換的消耗更低。開發人員可以無感知的用同步的代碼編寫方式達到非同步IO的效果和效能,避免了傳統非同步回調所帶來的離散的代碼邏輯和陷入多層回調中導致代碼無法維護。
1、golang::goroutine
最近在學習 go,一些高大上的特性果然是為高並發而生,內建的 net/http 包對請求的處理也透明的放在了協程上下文中,真開箱即用。
server.go
package mainimport ( "fmt" "net/http" "time" "log" "runtime" "bytes" "strconv")func main() { // 註冊請求 handler http.HandleFunc("/", func (responseWrite http.ResponseWriter, request *http.Request) { log.Println("goroutine: ", GetGID(), "start") // 類比2秒的IO耗時操作 會發生協程切換 time.Sleep(2 * time.second) // 協程繼續執行 log.Println("goroutine: ", GetGID(), "end") // 結束請求 fmt.Fprintln(responseWrite, "<h1>hello world!</h1>") }) log.Println("server start ...") http.ListenAndServe(":8081", nil)}// 擷取 goroutine 的協程 idfunc GetGID() uint64 { b := make([]byte, 64) b = b[:runtime.Stack(b, false)] b = bytes.TrimPrefix(b, []byte("goroutine ")) b = b[:bytes.IndexByte(b, ' ')] n, _ := strconv.ParseUint(string(b), 10, 64) return n}
golang 內建的 net/http 包是會自行的建立一個協程去處理請求的,所以阻塞的 2秒 並不會將主進程掛起,主進程仍在不停的接收請求並建立一個協程去處理請求。
2、swoole::coroutine
Swoole在2.0開始內建協程(Coroutine)的能力,提供了具備協程能力IO介面(統一在命名空間SwooleCoroutine*)。
server.php
<?php// 高並發下多協程也需要一定的記憶體ini_set('mermory_limit', '1024M');$server = new Swoole\Http\Server('127.0.0.1', 8082);// 背景工作處理序數設為1 這樣和 golang 的進程模型基本一致// swoole worker 預設的 max_coroutine 數為 3000 可根據業務負載自行配置// @reference https://wiki.swoole.com/wiki/page/950.html$server->set([ 'worker_num' => 1,]);$server->on("start", function ($server) { echo "server start ..." . PHP_EOL;});// 處理請求$server->on('request', function ($request, $response) { echo "goroutine: " . Swoole\Coroutine::getuid() . " start" . PHP_EOL; /** * 這裡我們需要使用 Swoole\Coroutine::sleep() 來類比IO耗時 觸發協程切換 * 真實的業務情境中你可能使用的是以下協程用戶端 * Coroutine\Client Coroutine\Http\Client Coroutine\Http2\Client * Coroutine\Redis Coroutine\Socket Coroutine\MySQL Coroutine\PostgreSQL * php 的 sleep() 是沒辦法觸發協程切換的 會被同步阻塞 * 這也是為什麼 swoole 的協程中要使用指定的 IO 用戶端才能發揮協程的效能 */ Swoole\Coroutine::sleep(2); echo "goroutine: " . Swoole\Coroutine::getuid() . " end" . PHP_EOL; $response->end("<h1>Hello world!</h1>");});$server->start();
swoole 的 Server 同 golang 的 net/http 一樣的將協程透明化,不過我們在 swoole 中需要使用一些預先提供的方法或用戶端才能觸發協程切換,進而發揮協程的高效能。
swoole 的各協程用戶端:https://wiki.swoole.com/wiki/...
3、ab壓測
伺服器配置略渣,vsphere上的一台小小自用伺服器 1 核 2G,不要在意
壓測 200 並發 5000 請求
ab -c 200 -n 5000 -k http://127.0.0.1:8081ab -c 200 -n 5000 -k http://127.0.0.1:8082
go
swoole
壓測 500 並發 5000 請求
ab -c 500 -n 5000 -k http://127.0.0.1:8081ab -c 500 -n 5000 -k http://127.0.0.1:8082
go
swoole
壓測 1000 並發 5000 請求
ab -c 1000 -n 5000 -k http://127.0.0.1:8081ab -c 1000 -n 5000 -k http://127.0.0.1:8082
go
swoole
壓測 2000 並發 5000 請求
ab -c 2000 -n 5000 -k http://127.0.0.1:8081ab -c 2000 -n 5000 -k http://127.0.0.1:8082
go
swoole
可以通過以上幾組資料看出,golang::goroutine 和 swoole::coroutine 兩個協程的效能基本沒有什麼差距,而且協程在高並發下才能更直觀的體現出優異的效能。
同樣 5000 的請求,200 - 500 - 1000 - 2000 處理耗時越來越小,說明不是我吃不掉,是你發的太慢。當 2000 並發時,我這台 1核 2G 的伺服器都能在阻塞 2 秒的情境下跑到 600- 並發。
但 swoole 還是要佔更多些的記憶體,我測試機記憶體比較小,沒辦法用 swoole 完美的跑 c10k,記憶體不夠用,沒辦法建立更多的協程去並發處理請求,但 go 還可以,後面在看下 go::goroutine 是不是佔用記憶體更小吧,先跑一個看看。
go 10k 並發 200k請求
go 20k 並發 1000k請求
ab 最大並發類比量為 20k,僅供參考