go解决c10k problem(一)
零:缘起
最近两个事情
一个是easeprobe,haocl 对其http probe 做了一个10k的benchmark easeprobe benchmark
一个是读到go memory这篇文章 go-memory-ballast
谈到技术,并发是一个绕不过的话题,从第一家公司单机2万qps,到一个50w+ tps的超大型分布式交易系统。
那么自己能否来简单来测试一下呢? 跑一个web server,来个10k 并发,再来个可观测,看下瓶颈到底在哪里? 如何提升? 说干就干。
一:go http server
goroutine的存在,让go在网络编程这块很高效,也有很多优秀的http 框架,比如 gin
echo
fasthttp
fasthttp 比较快,我们先不读取db和其他,仅让cpu是一个瓶颈,仅写个hello world,收到请求后,返回一行文本。
示例代码
package main
import (
"fmt"
"github.com/valyala/fasthttp"
)
// request handler in fasthttp style, i.e. just plain function.
func fastHTTPHandler(ctx *fasthttp.RequestCtx) {
fmt.Fprintf(ctx, "Hi there! RequestURI is %q", ctx.RequestURI())
}
func main() {
fasthttp.ListenAndServe(":8087", fastHTTPHandler)
}
二:go http client 压测的client
2.1 压测client选择
- 简单实现版本,使用time.Sleep 函数,来均匀分布所有的请求到一个server。
- 复杂实现版本,来一个goroutine的pool,一个http connect的pool,分别控制最大连接数和最大goroutine数目,并且根据请求耗时,动态调整两个pool的大小,然后到一个稳定的状态。
- 别人现有的,找到一个 go-strees-testing 的项目,可以先用起来
2.2 先选择现有的试试效果
https://github.com/link1st/go-stress-testing
#!/bin/bash
go-stress-testing-linux -c 100 -n 100000 -u http://127.0.0.1:8087
这里原生不支持多核cpu,所以多核的话,我们需要下载下来,修改main函数,把cpu的数量从
runtime.GOMAXPROCS(1) 修改成runtime.GOMAXPROCS(runtime.NumCPU())
三:可观测
3.1 观测内容
观测分为几点,一个是业务监控,一个是系统监控,像我们这种,没有业务逻辑,仅监控系统即可
系统监控,又分为主机系统监控,应用系统监控、中间件、DB
主机监控有cpu、内存、磁盘、网络的监控,我们这里磁盘其实可以忽略掉
应用系统监控,go runtime监控,比如gc、堆栈
DB和中间件,暂时没有,先不写
3.2 观测方案
系统监控,使用shell、linux 命令行即可,手动监控
应用监控,使用prometheus 的golang client 采集,grafana 展示
四:实战
4.1 Mac 实践
问题
server和client 配置好后,开始监测,QPS也就700左右,死活上不去了,内存和cpu都还好,不高,网络链接也不多,但请求的延时就开始超过10s
解决
尽早放弃了,不想折腾,使用linux
4.2 Linux 实践
问题1
这次简单,直接在现有的小鸡上来跑,0.5G内存,1VCPU
结果1
解决1
可以看到,linux 直接就3800 QPS了,这次是单机CPU满载了,client 64%,server 36%, 100%
这种简单,加机器就可以解决的问题
解决2
*GOMAXPROCS=4 go run server.go &*
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
17606 root 20 0 1438212 119252 5284 R 303.7 0.4 5:51.98 client
17485 root 20 0 1527320 15384 9156 S 230.0 0.0 4:29.19 server
30k+的top 和并发数
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
18814 root 20 0 1816328 53512 9532 S 549.8 0.2 4:21.05 main
17485 root 20 0 1748772 16164 9348 R 166.8 0.0 14:03.47 server
─────┬───────┬───────┬───────┬────────┬────────┬────────┬────────┬────────┬────────┬────────
耗时│ 并发数│ 成功数│ 失败数│ qps │最长耗时│最短耗时│平均耗时│下载字节│字节每秒│ 状态码
─────┼───────┼───────┼───────┼────────┼────────┼────────┼────────┼────────┼────────┼────────
1s│ 100│ 22654│ 0│27766.78│ 45.56│ 0.17│ 3.60│1,245,970│1,245,938│200:22654
2s│ 100│ 46299│ 0│28457.67│ 45.56│ 0.15│ 3.51│2,546,445│1,273,204│200:46299
3s│ 100│ 70470│ 0│28828.85│ 45.56│ 0.15│ 3.47│3,875,850│1,291,943│200:70470
4s│ 100│ 94893│ 0│29192.73│ 45.56│ 0.14│ 3.43│5,219,115│1,304,771│200:94893
5s│ 100│ 119602│ 0│29516.55│ 45.56│ 0.14│ 3.39│6,578,110│1,315,573│200:119602
6s│ 100│ 144138│ 0│29669.31│ 45.56│ 0.14│ 3.37│7,927,590│1,321,143│200:144138
7s│ 100│ 169391│ 0│29952.31│ 45.56│ 0.14│ 3.34│9,316,505│1,330,926│200:169391
8s│ 100│ 194896│ 0│30215.56│ 45.56│ 0.14│ 3.31│10,719,280│1,339,904│200:194896
9s│ 100│ 220623│ 0│30489.03│ 45.56│ 0.14│ 3.28│12,134,265│1,348,246│200:220623
10s│ 100│ 246594│ 0│30690.36│ 45.56│ 0.14│ 3.26│13,562,670│1,356,265│200:246594
11s│ 100│ 272641│ 0│30889.66│ 45.56│ 0.14│ 3.24│14,995,255│1,363,135│200:272641
12s│ 100│ 299071│ 0│31084.94│ 45.56│ 0.14│ 3.22│16,448,905│1,370,737│200:299071
13s│ 100│ 325628│ 0│31266.74│ 45.56│ 0.14│ 3.20│17,909,540│1,377,654│200:325628
14s│ 100│ 352274│ 0│31436.50│ 45.56│ 0.14│ 3.18│19,375,070│1,383,930│200:352311
15s│ 100│ 379019│ 0│31602.18│ 45.56│ 0.14│ 3.16│20,846,045│1,389,734│200:379019
16s│ 100│ 405848│ 0│31756.00│ 45.56│ 0.14│ 3.15│22,321,640│1,395,101│200:405848
17s│ 100│ 432456│ 0│31883.06│ 45.56│ 0.14│ 3.14│23,785,080│1,399,121│200:432456
18s│ 100│ 459049│ 0│32011.63│ 45.56│ 0.14│ 3.12│25,247,695│1,402,645│200:459227
19s│ 100│ 486589│ 0│32163.75│ 45.56│ 0.14│ 3.11│26,762,395│1,408,546│200:486589
20s│ 100│ 513977│ 0│32314.27│ 45.56│ 0.14│ 3.09│28,268,735│1,413,435│200:513978
21s│ 100│ 540328│ 0│32372.83│ 45.56│ 0.14│ 3.09│29,718,040│1,415,142│200:540328
22s│ 100│ 568158│ 0│32517.04│ 45.56│ 0.14│ 3.08│31,248,690│1,420,373│200:568163
23s│ 100│ 595715│ 0│32614.98│ 45.56│ 0.14│ 3.07│32,764,325│1,424,534│200:595715
24s│ 100│ 623436│ 0│32732.62│ 45.56│ 0.14│ 3.06│34,288,980│1,428,599│200:623731
25s│ 100│ 650742│ 0│32858.38│ 45.56│ 0.14│ 3.04│35,790,810│1,431,629│200:650745
26s│ 100│ 678444│ 0│32945.23│ 45.56│ 0.14│ 3.04│37,314,420│1,435,163│200:678444
27s│ 100│ 706591│ 0│33066.18│ 45.56│ 0.14│ 3.02│38,862,505│1,439,116│200:706591
28s│ 100│ 734065│ 0│33133.34│ 45.56│ 0.14│ 3.02│40,373,575│1,441,912│200:734065
29s│ 100│ 761927│ 0│33222.32│ 45.56│ 0.14│ 3.01│41,905,985│1,445,032│200:761934
40k的结果
可以达到40k,耗时很短,server 很高
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
18814 root 20 0 2236976 187344 9532 R 548.5 0.6 25:16.16 main
17485 root 20 0 1748772 16324 9540 S 172.1 0.0 20:36.26 server
─────┬───────┬───────┬───────┬────────┬────────┬────────┬────────┬────────┬────────┬────────
耗时│ 并发数│ 成功数│ 失败数│ qps │最长耗时│最短耗时│平均耗时│下载字节│字节每秒│ 状态码
─────┼───────┼───────┼───────┼────────┼────────┼────────┼────────┼────────┼────────┼────────
247s│ 100│7563722│ 0│40026.69│ 48.31│ 0.14│ 2.50│416,004,710│1,684,229│200:7563722
248s│ 100│7595709│ 0│40034.48│ 48.31│ 0.14│ 2.50│417,763,995│1,684,532│200:7595709
249s│ 100│7628010│ 0│40044.40│ 48.31│ 0.14│ 2.50│419,540,550│1,684,901│200:7628010
250s│ 100│7659374│ 0│40049.95│ 48.31│ 0.14│ 2.50│421,265,570│1,685,058│200:7659374
251s│ 100│7691688│ 0│40060.62│ 48.31│ 0.14│ 2.50│423,042,840│1,685,429│200:7691688
252s│ 100│7723556│ 0│40067.88│ 48.31│ 0.14│ 2.50│424,795,580│1,685,695│200:7723556
结语
可以看到,go 来解决一个C10k的问题,很简单,当然这个只是最简单的一个模拟,下一期,我们加上数据库,再来解决数据库下面的C10k的问题。