这一篇文章介绍了I/O密集型服务器和计算密集型的服务器的两种场景,对多epoller服务器和goroutine-per-connection服务器两种服务器进行测试,连接数分别是5000、2000、1000、500、200和100。
第一篇 百万 Go TCP 连接的思考: epoll方式减少资源占用
第二篇 百万 Go TCP 连接的思考2: 百万连接的吞吐率和延迟
第三篇 百万 Go TCP 连接的思考: 正常连接下的吞吐率和延迟
相关代码已发布到github上: 1m-go-tcp-server。
前两篇的是有巨量连接的情况下服务器的性能,这类服务器可能应用于消息推送、IOT、页游等场景,追求的是大量连接,并发量相对不大的场景。还有一类场景是服务器的连接数不多,几十几百,最多几千的TCP连接,比如公司内的服务之间的调用等,这类服务器在不同的实现下的性能是怎样的?
测试区分两个场景: I/O密集型和计算密集型。I/O密集型的服务比如文件的读取、数据库的访问,远程服务的调用等等,计算密集型的访问比如区块链的挖矿、算法的计算、类似redis这样的基于内存的数据处理服务等等(当然redis还是memory bound类型的服务)。
我们通过time.Sleep
让goroutine休眠来模拟I/O密集型的服务,实际goroutine休眠和真正的I/O密集型的服务还是有区别的,虽然它们都有一定的耗时,goroutine在等待的过程中会休眠,但是I/O密集型还有大量的I/O访问,比如磁盘、网络等等。出于方便测试的目的,我们还是使用time.Sleep
来模拟,主要测试goroutine在休眠一段时间后对性能的影响。
计算密集型的访问我们采用挖矿算法,通过计算hash值,满足一定的挖矿难度让CPU进行大量的计算动作。
测试分别采用并发连接数为 5000、2000、1000、500、200、100,测试对应的吞吐率和延迟。
测试使用多epoller的方式实现的服务器和goroutine-per-connection实现的服务器。因为连接数少,我们可以采用goroutine-per-connection的方式。
九、 I/O 密集型的服务器(无sleep)
首先测试I/O密集型的服务器,在没有sleep的情况下,两个服务器的数据对比如下:
多epoller服务器
代码: 10_io_intensive_epoll_server
5000 | 2000 | 1000 | 500 | 200 | 100 | |
---|---|---|---|---|---|---|
tps | 210064 | 203027 | 207097 | 208460 | 200798 | 212587 |
latency(s) | 23.2 | 9.1 | 4.5 | 2.3 | 0.9 | 0.5 |
吞吐率变化不大,基本都在误差以内,延迟随着连接数的降低而降低,基本成线性关系。
服务器可以达到20万的吞吐率。
goroutine-per-connection 服务器
5000 | 2000 | 1000 | 500 | 200 | 100 | |
---|---|---|---|---|---|---|
tps | 203038 | 208002 | 209128 | 207990 | 209192 | 212376 |
latency(s) | 24 | 9.2 | 4.6 | 2.3 | 0.9 | 0.5 |
可以看到,当服务器的业务简单,基本没有耗时的情况下,这两种实现的差别不大,基本一样。
十、 I/O 密集型的服务器 (sleep 10 ms)
我们模拟I/O耗时10毫秒的情况,两个服务器的数据对比如下:
多epoller服务器
5000 | 2000 | 1000 | 500 | 200 | 100 | |
---|---|---|---|---|---|---|
tps | 6218 | 6256 | 6251 | 6108 | 6027 | 4736 |
latency(s) | 0.8 | 0.3 | 0.2 | 0.08 | 0.04 | 0.03 |
吞吐率急剧下降。
goroutine-per-connection 服务器
5000 | 2000 | 1000 | 500 | 200 | 100 | |
---|---|---|---|---|---|---|
tps | 203088 | 194783 | 98895 | 49326 | 19747 | 9886 |
latency(s) | 0.02 | 0.01 | 0.01 | 0.01 | 0.01 | 0.01 |
可以看懂吞吐率会和连接数相关,但是也不是线性关系,随着连接数的增加,所带来的吞吐率收益也慢慢的变弱,也就是有一个拐点,连接数的增加带来的吞吐率的增加将变得很小。
看它的延迟时间,连接数2000以下延迟就是都是业务所耗费的时间(10毫米)。
这给了我们一个启示,在连接数比较小的情况下,正统的goroutine-per-connection可以取得很好的延迟,并且为了提高吞吐率,我们可以适当增加连接数。
十一、 计算密集型服务器
采用挖矿算法,计算哈希值,如果哈希值的前12bit都是0的话算挖矿成功。
多epoller服务器
代码:12_cpu_intensive_epoll_server
5000 | 2000 | 1000 | 500 | 200 | 100 | |
---|---|---|---|---|---|---|
tps | 212554 | 227291 | 224509 | 229796 | 226687 | 226147 |
latency(s) | 0.02 | 0.01 | 0.004 | 0.002 | 0.001 | 0.0005 |
吞吐率基本不变,但是延迟随着连接数的降低而成线性降低。
goroutine-per-connection 服务器
5000 | 2000 | 1000 | 500 | 200 | 100 | |
---|---|---|---|---|---|---|
tps | 211048 | 212343 | 228978 | 228756 | 228768 | 229425 |
latency(s) | 0.02 | 0.01 | 0.005 | 0.002 | 0.001 | 0.0005 |
吞吐率和多epoller方式基本一致,延迟也一样。 可以看出对于计算密集型的服务,这两种方式的性能差别不大。