主页 > 网络知识 > 记一次单机系统的性能优化:最后竟是 TCP 的锅

记一次单机系统的性能优化:最后竟是 TCP 的锅

前言

这篇文章的主题是记录一次 Python 程序的性能优化,在优化的过程中遇到的问题,以及如何去解决的。为大家提供一个优化的思路,首先要声明的一点是,我的方式不是唯一的,大家在性能优化之路上遇到的问题都绝对不止一个解决方案。

如何优化

首先大家要明确的一点是,脱离需求谈优化都是耍流氓,所以有谁跟你说在xx机器上实现了百万并发,基本上可以认为是不懂装懂了,单纯的并发数完全是无意义的。其次,我们优化之前必须要有一个目标,需要优化到什么程度,没有明确目标的优化是不可控的。再然后,我们必须明确的找出性能瓶颈在哪里,而不能漫无目的的一通乱搞。

需求描述

这个项目是我在上家公司负责一个单独的模块,本来是集成在主站代码中的,后来因为并发太大,为了防止出现问题后拖累主站服务,所有由我一个人负责拆分出来。对这个模块的拆分要求是,压力测试 QPS 不能低于3万,数据库负载不能超过50%,服务器负载不能超过70%,单次请求时长不能超过70ms,错误率不能超过5%。
环境的配置如下:
服务器:4核8G内存,CentOS7系统,SSD 硬盘
数据库:MySQL 5.7,最大连接数 800
缓存: Redis,1G 容量。
以上环境都是购买自腾讯云的服务。
压测工具:locust,使用腾讯的弹性伸缩实现分布式的压测。
需求描述如下:
用户进入首页,从数据库中查询是否有合适的弹窗配置,如果没有,则继续等待下一次请求、如果有合适的配置,则返回给前端。这里开始则有多个条件分支,如果用户点击了弹窗,则记录用户点击,并且在配置的时间内不再返回配置,如果用户未点击,则24小时后继续返回本次配置,如果用户点击了,但是后续没有配置了,则接着等待下一次。

重点分析

根据需求,我们知道了有几个重要的点,1、需要找出合适用户的弹窗配置,2、需要记录用户下一次返回配置的时间并记录到数据库中,3、需要记录用户对返回的配置执行了什么操作并记录到数据库中。

调优

我们可以看到,上述三个重点都存在数据库的操作,不只有读库,还有写库操作。从这里我们可以看到如果不加缓存的话,所有的请求都压到数据库,势必会占满全部连接数,出现拒绝访问的错误,同时因为 SQL 执行过慢,导致请求无法及时返回。所以,我们首先要做的就是讲写库操作剥离开来,提升每一次请求响应速度,优化数据库连接。整个系统的架构图如下:

将写库操作放到一个先进先出的消息队列中来做,为了减少复杂度,使用了Redis 的 list 来做这个消息队列。
然后进行压测,结果如下:
QPS 在 6000 左右 502 错误大幅上升至 30%,服务器 CPU 在 60%-70% 之间来回跳动,数据库连接数被占满 TCP 连接数为 6000 左右,很明显,问题还是出在数据库,经过排查 SQL 语句,查询到原因就是找出合适用户的配置操作时每次请求都要读取数据库所导致的连接数被用完。因为我们的连接数只有 800,一旦请求过多,势必会导致数据库瓶颈。好了,问题找到了,我们继续优化,更新的架构如下:

我们将全部的配置都加载到缓存中,只有在缓存中没有配置的时候才会去读取数据库。

接下来我们再次压测,结果如下:

QPS 压到 2万左右的时候就上不去了,服务器 CPU 在 60%-80% 之间跳动,数据库连接数为300个左右,每秒TPC连接数为1.5万左右。

这个问题是困扰我比较久的一个问题,因为我们可以看到,我们2万的 QPS,但是TCP 连接数却并没有达到2万,我猜测,TCP连接数就是引发瓶颈的问题,但是因为什么原因所引发的暂时无法找出来。

这个时候猜测,既然是无法建立 TCP 连接,是否有可能是服务器限制了 socket 连接数,验证猜测,我们看一下,在终端输入 ulimit -n 命令,显示的结果为65535,看到这里,觉得 socket 连接数并不是限制我们的原因,为了验证猜测,将 socket 连接数调大为100001.
再次进行压测,结果如下:
说点什么吧
  • 全部评论(0
    还没有评论,快来抢沙发吧!