问题

部署含有深度学习模型的Django项目的uWSGI、Nginx服务器的时候,所有模块都可以正常运行,也可以实现并发。唯独在处理需要调用深度学习模型的请求的时候无法成功运行,也不报错,之后逐行debug发现总是卡在模型的model.predict()这个函数中没法继续运行,在此记录一下。
另外曾一度怀疑是cpu跑不动了还更换了云主机,换成了有GPU的云主机,又掉进了显卡驱动的一堆坑中。附上(腾讯CVM)GPU云主机显卡驱动

测试

  • 直接runserver启动django,然后从http端口访问,可以成功调用深度学习模型;
  • 通过uwsgi配置http并启动django,然后从http端口访问,无法调用深度学习模型,不报错;
  • 通过nginx经socket访问uwsgi再启动django,然后从socket端口访问,无法调用深度学习模型,不报错;

鉴于其他模块都可以正常使用,直接怀疑是uWSGI的部署除了问题,检查uwsgi.ini,折腾了一阵子由于uwsgi配置项太多,折戟而返。于时借助其他的工具查找问题所在。

先用到了uwsgitop,看uwsgi负载情况,在uwsgi.ini设置了stats端口为127.0.0.1:8001,故使用该端口即可查看性能。

$ pip install uwsgitop
$ uwsgitop 127.0.0.1:8001   #实际上用curl 127.0.0.1:8001可以看到json格式数据,只是不直观

可以看到正常情况下负载如下
在这里插入图片描述
各参数含义如下
在这里插入图片描述
当我调用深度学习模型的时候,worker总是处于busy状态,但是其他参数跟cheap的worker一样,一直卡着不动了。

于是使用lsof查看运行情况

(root)$ lsof -nPp pid 看卡住的pid

调用深度模型时的lsof -nPp 2256:
在这里插入图片描述
不调用深度模型时的lsof -nPp 32735
在这里插入图片描述
结合netstat -anpt查看端口使用情况发现9001端口和45728端口建立链接总是处于CLOSE_WAIT状态,而在没有调用深度模型的时候就不会出现这种情况。实际上这是因为调用深度模型的时候,程序执行不下去,nginx没法得到结果所以只能保持close_wait状态。

只找到了结果,没找到原因。

继续使用strace查看运行情况

$ strace -ttp pid  #看卡住的pid

调用深度模型时strace:
在这里插入图片描述
参考Linux中的线程同步机制(一) — Futex

这里futex操作正在等待另一个线程释放一个锁

于时索性不启动线程,且之启动一个进程,在uwsgi.ini里面再次配置如下

$ master = false  #不启动主进程
$ processes = 1 #最大进程数为1
$ cheaper = 0 #最小进程数为0

!!!发现成功运行深度学习模型。

解决方法

总结一下:之所以只能用一个进程且主线程不能开启,是因为深度学习模型调用的时候都需要一段时间,如果有多个进程势必会争夺资源,即使只有一个请求的时候,可能是因为主进程和子进程同时在运行导致没法调用深度学习模型。另外除了上面的三个限制,其他限制还有下面两条:

  • 不能将项目中存放且要用的图片和视频作为静态资源被收集,收集的静态资源一般都是css,html,js这些;
  • 不能开启thunder-lock;
  • 建议关闭stats,因为主进程不开启的话就没办法使用stats,再打开也是多次一举

另外似乎因为没有主线程的关系(只有一个进程),现在要杀掉一个uwsgi,如果不能通过uwsgi --stop uwsgi.pid 来杀uwsgi而是通过删除占用端口的pid,则需要删除很多个pid对应的程序,比之前麻烦很多,用这个fuser -k (uwsgi的socket号)/tcp方便一些,更快捷的方法是killall -9 uwsgi

目前为止,通过uwsgi依然只实现了单进程,没法并发处理请求,更别提并行。为了进一步提高并发并行性能,只开1个processes并且尝试打开多线程,

$ enable-threads=true
$ threads = 4

再次测试,由于线程之间资源共享,此时多个线程可以并发(因为GIL所以不能并行),服务器也相对稳定,暂时可以满足需求,但毕竟只有一个进程,要进一步提高并发能力的话,关键是要解决多进程调度的问题。

这里考虑两个可能造成只能开启单线程的原因:

  • 使用了from django.utils.module_loading import autodiscover_modules的方式预加载模型,可能导致模型变成了全局变量,在多进程争夺资源的时候引起阻塞。
  • 本身就需要使用multiprocessing来支持多进程的调度

这个问题需要解决才能实现Nginx+uWSGI实现高并发,充分利用多核计算资源。这里就涉及到了runserver和uwsgi两种启动方式的区别。

Django两种启动方式runserver和uwsgi的区别

Django原生为单线程
Django server 默认多线程
uwsgi启动时,可以单进程+单线程;单进程+多线程;多进程+单线程;多进程+多线程。后三个都可以并发,能否并行还得看操作系统和cpu的情况。
详细内容可以参考https://blog.csdn.net/cpxsxn/article/details/106102441

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐