協程的一些特性和優勢我就不說了,網上不少文章都講述的很透徹。php
協程能夠理解爲純用戶態的線程,其經過協做而不是搶佔來進行切換。相對於進程或者線程,協程全部的操做均可以在用戶態完成,建立和切換的消耗更低。開發者能夠無感知的用同步的代碼編寫方式達到異步IO的效果和性能,避免了傳統異步回調所帶來的離散的代碼邏輯和陷入多層回調中致使代碼沒法維護。
最近在學習 go,一些高大上的特性果真是爲高併發而生,自帶的 net/http 包對請求的處理也透明的放在了協程上下文中,真開箱即用。html
server.gogolang
package main import ( "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 ...") err := http.ListenAndServe(":8081", nil) if nil != err { log.Fatal(err.Error()) } } // 獲取 goroutine 的協程 id func 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秒 並不會將主進程掛起,主進程仍在不停的接收請求並分配一個協程去處理請求。服務器
Swoole在2.0開始內置協程(Coroutine)的能力,提供了具有協程能力IO接口(統一在命名空間SwooleCoroutine*)。swoole
server.php併發
<?php $server = new Swoole\Http\Server('0.0.0.0', 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/...異步
咱們使用以上的業務代碼,go
和 swoole
都模擬2秒
的業務耗時,服務器配置略渣,vsphere上的一臺小小自用服務器 1 核 2G,不要在乎(ab測試結果的話簡單直接的看Request Per Second
)高併發
ab -c 200 -n 5000 -k http://127.0.0.1:8081 ab -c 200 -n 5000 -k http://127.0.0.1:8082
go
性能
swoole
學習
ab -c 500 -n 5000 -k http://127.0.0.1:8081 ab -c 500 -n 5000 -k http://127.0.0.1:8082
go
swoole
ab -c 1000 -n 5000 -k http://127.0.0.1:8081 ab -c 1000 -n 5000 -k http://127.0.0.1:8082
go
swoole
ab -c 2000 -n 5000 -k http://127.0.0.1:8081 ab -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
2019-8-6 11:50:21 換了個測試機,跑完它,swoole 更新到了 4.4.2
新的測試機配置爲:2Core 2G 測試代碼不變
新測試機對比
go 的併發性好像沒 swoole 高了...
go
swoole
go
ab 最大併發模擬量爲 20k,僅供參考
在 C20K 的併發量下,go http server 依然能夠遊刃有餘的提供服務,qps 能夠達到 5K,要知道我模擬的2s的IO耗時能夠說是比較大的IO開銷了,單機1核2G的配置能壓出這樣的性能指數,很能夠了。