1、背景

近期,我决定将测试平台迁移到国外的 VPS 上。由于 VPS 的配置相当有限,只有 2G 的内存,而且已有多个服务在上面运行,内存占用已达 70% 多。在如此紧张的资源环境下,降低测试平台后端的线程数量,期望能减少资源占用。

原来的启动命令是:

# 容器中django启动命令,使用gunicorn默认的worker pool(同步)
# -w4 起4个worker
/usr/local/bin/python -m gunicorn FasterRunner.wsgi_docker -b 0.0.0.0 -w4

image-20230420111105635

修改后的启动命令是:

# 容器中django启动命令,使用gunicorn默认的worker pool(同步),默认起1个worker
/usr/local/bin/python -m gunicorn FasterRunner.wsgi_docker -b 0.0.0.0

image-20230420110714548

内存确实有明显的下降,少了 200M+,对于内存捉急的小机器,还是相当可观!

2、发现问题

但是!!!在修改后,验证发现请求外部接口是正常的,请求测试平台内部接口时会发生超时现象。

用 celery 异步执行用例,定时任务啥的都没问题

这让我百思不得其解,心中充满了困惑。看完 log 之后,还是无法找到问题所在。

你看请求外部接口,妥妥的
image-20230420093217587

请求测试平台内部接口,执行超时

image-20230420093342831

3、排查问题

3.1 排查 gunicorn 命令

第一个怀疑的对象就是 gunicorn 命令,但想了一下,请求外部接口是好的啊,平台接口都能正常的跑;请求测试平台内部才有问题,那会不会是基础镜像问题?

3.2 排查 Python 基础镜像

第二个怀疑对象是 Python 基础镜像。尽管我之前从 python-alpine 更换为 python-buster 已经验证过,但我决定在本地再次尝试回滚到 python-alpine。(至于为什么更换镜像,那又是另一个事情了)

然而,问题仍然存在。这让我更加困惑,因为目前部署在腾讯云就是用 python-alpine,在海外 vps 使用的 python-buster,两个明明可以正常运行。

这就有点尴尬了啊,似乎都没啥问题,只是请求测试平台内部接口有问题。
要不先这样就发布算了,反正正常使用都是请求外部接口,没啥影响?
开玩笑,那当然不行,明显不符合我的风格呀!况且演示 demo 就是请求内部接口啊!

4、验证问题

于是在 ChatGPT 上查询了 Gunicorn 的 worker pool,发现默认的 worker 是 1,并且是同步的。

image-20230420093630179

这时,我觉得问题可能出在这里。我回想起在虾皮工作时,Django 项目也是使用 Gunicorn 部署,并且 worker pool 使用的是协程,这样更节省资源。于是,我决定尝试使用 Gunicorn + 协程 worker 的方式。

# 容器中django启动命令,使用gunicorn + gevent worker pool(异步)
# -w4,启动4个worker
/usr/local/bin/python -m gunicorn FasterRunner.wsgi_docker -b 0.0.0.0 -w4 -k gevent

果不其然,问题得到了解决!
请求外部接口和测试平台内部接口都是妥妥的!

image-20230420094224385

我立刻兴奋起来,再次切换回单线程启动命令,发现问题可以复现。证实问题确实出在 worker 这里。

经过深思熟虑,我意识到了问题的关键所在:

单线程的同步 worker 只能同时处理一个请求。执行 API 测试时就占用了一个线程,这时已经没有多余的线程来处理其他请求,刚好这个 API 测试恰好请求到了内部接口,所以这个请求就只能在那里干等,直到最后超时失败!

并且这期间,整个测试平台的所有接口都无法处理,都会卡住!!!

5、思考

那么问题了,gunicorn worker pool 换成协程之后,4 个 worker 是完全没问题的

# 容器中django启动命令,使用gunicorn + gevent worker pool(异步)
# -w4,启动4个worker
/usr/local/bin/python -m gunicorn FasterRunner.wsgi_docker -b 0.0.0.0 -w4 -k gevent

就是占用资源还是有点高 (刚启动的状态,184M)

image-20230420095940802

如果换成 gunicorn worker pool 换成协程之后,只有 1 个 worker,那会有问题吗?

亲自来验证一波吧
https://fast.huacai.one/fastrunner/login

账号: test

密码: test2020

# 容器中django启动命令,使用gunicorn + gevent worker pool(异步)
# -w1,启动1个worker
/usr/local/bin/python -m gunicorn FasterRunner.wsgi_docker -b 0.0.0.0 -w1 -k gevent

占用资源少了一半,对于内存不足的小鸡,90M 也很香呀!

image-20230420095156709

6、总结

更多技术的文章,欢迎查看我的博客https://blog.huacai.one/


↙↙↙阅读原文可查看相关链接,并与作者交流