协程的一些特性和优势我就不说了,网上不少文章都讲述的很透彻。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的配置能压出这样的性能指数,很能够了。