为何没有充分利用CPU的一个小故事
解决问题之后, 了解了负载均衡中一个比较隐蔽的坑(也只是对我这种新手来说是坑)
写完之后发现自己还是没有把事情讲清楚的能力!摔.
1
花了整整一天(还是两天?不记得了)才发现了为什么, 原来不是任何编码问题, 也不是任何语言问题, 就是个思维模式问题, 更像脑筋急转弯.
2
事情是这样滴, 一个同事做一个influxdb proxy类似的东西. 后面三台influxdb server, 前面的请求到proxy转发给后面的influxdb server. 现在在模拟节段, 并没有client, 只是放了一个队列, 里面是N个(可能是无穷多个)请求. 这里的请求是已经封装进了应该转发到哪个server(这里是个伏笔, 是个关键点). c个goroutine异步从队列里面取请求,然后转发给相应的influxdb server.
3
但是在测试过程中发现了一个百思不得其解的问题.
如果后面只用一个influxdb server, 三个goroutine就可以把server cpu打满. 如果后面放三个influxdb server, 9个goroutine却不能打满三个server的cpu.
如图, 左边是CPU打满的情况, 右边是多线程请求多influxdb server的情况. (监控系统使用netdata,实时秒级)
4
反正我的第一反应是proxy这里有瓶颈了, 导致在转发或者是go的Do httprequest的调用里面有锁等瓶颈吧.
我自己也写了个简单的程序测了下, 情况是一模一样的. 不能打满CPU的问题, 可以转换成每请求1000条数据的时间消耗长短上. 这样看起来更方便一些.
简单起见, 一次是单线程请求单influxdb server, 另外一次是2线程请求2个influxdb server.
单线性情况下, 1000条请求只需要4秒, 但是双线程的时候, 每1000条请求需要6秒左右.
先是确定了时间都花在Client.Do上面, 我以为这里面做了很多事情, 包括生成http request等, 觉得是在处理多host的时候, 这里面某处消费时间太多了. 但始终找不到头绪.
5
早就想抓包, 但又觉得太麻烦. 后来终于抓包看一下, 于是就在两种情况下各tcpdump出来10000条请求的数据, 一点点分析.
在单线程情况下, 确认了一次请求基本上就是4ms.
2线程的看着有点眼花, 耐着性子看一下, 还是能看出来有这样一种pattern: 到influxdb server A的时候4ms一个请求,马上就会发起到B的请求, 但是接下来到B的请求会变成8ms左右. 这里导致总的请求时间变长了.
tcpdump抓包的结果瞬间排除了proxy这边有瓶颈, 包已经发出去了, 返回就花了8ms, 怎么说都是server响应慢. 这时候才突然反应过来, 因为B到了CPU 100%, 响应已经变慢了, 这时候queue里面的请求如果是到A的会快速反回, 然后拿到B的请求就会”卡住”, 这个时候, A的CPU就会降下来.
其实注意看两个influxdb server的CPU使用率图线, 应该能发现A高的时候B就会低, 反过来也一样. 他们的波峰波谷是互补的. 这只是一个理论给出的预测, 但我坚信这一点, 都懒得去验证了.
6
同事说算是模拟一个load balance, 我也就先入为主的接受了这个观念. 但后面发现, 和load balance是有本质的区别的: 就在于queue里面的请求已经包含了这个请求本身应该发到哪个server上面, 但load balance不是这样的. LB是收到的请求应该只是剥离出来path本身, 然后再根据一定的算法决定发到后面哪个server.
7
本来觉得挺有意思的一件事, 怎么被我写出来之后就这么索然无味了呢…