写在前面
(感觉自从保研后,有一个世纪没更新博客了orz,趁复习周更新一波)
之前在把训练好的算法模型部署到服务端后用的是直接调用py脚本的形式(好蠢T T),就是每次客户端传来请求时,直接执行python xx.py
,但是这样会导致每次都先加载一次模型,再进行predict,白白浪费很多时间在模型加载上。本文使用Flask开一个Server,Nginx进行反向代理,实现服务端模型预加载,并将模型预测封装为API。
注:服务器为CentOS 7
原理
虽然说不明白原理应该也可以完成本文目标,但我始终认为一个优秀的技术人应该做到“知其所以然”。对原理不感兴趣的或者已经了解的可以跳过这节^ ^~
名词解释
- WSGI:全称是Web Server Gateway Interface,是一种规范,描述web server如何与web application通信的规范。要实现WSGI协议,必须同时实现web server和web application,当前运行在WSGI协议之上的web框架有Bottle, Flask, Django。
- WSGI server:负责从客户端接收请求,将request转发给application,并将application返回的response返回给客户端,比如uWSGI和Gunicorn都是实现了WSGI server协议的服务器。
- WSGI application:接收由server转发的request,处理请求,并将处理结果返回给server,比如Flask和Django都是实现了WSGI application协议的web框架。
注:Flask和Django框架都有自己实现的简单的WSGI server,但一般只用于调试,生产环境下还是最好用其他WSGI server。 - 反向代理:指的是用代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端。与正向代理不同,用户不知道真实的服务端,反向代理服务器会帮我们把请求转发到提供真实计算的服务器那里去。
整体架构
下图可以很直观地说明客户端(浏览器)、Nginx、uWSGI(Server)、Flask应用(application)之间的关系。
当客户端向服务器发起模型预测请求(如:访问http://服务器公网ip/predict)时,Nginx接收了这一请求,由于Nginx设置了反向代理,所以Nginx将请求转发给目标server(也就是uWSGI),server和app(Flask应用)都实现了WSGI协议,所以可以互相通信,server根据请求的种类调用app内的相应模块,并将执行的结果返回给Nginx,进而返回给客户端,完成本次请求。
因此,搭建完上述架构后,只需将训练好的模型上传至服务器,就能在server初始化的时候完成模型加载,当有请求时再调用预测模块进行预测即可,节省了模型加载的时间。
Flask配置
Flask是一个基于Python语言编写且实现了WSGI application协议的轻量级web框架,详细说明见官方文档,这里简单介绍一下安装步骤:
1.【建议但非必须】创建并激活虚拟环境:
$ pip install virtualenv
$ cd /var/www/demo(替换为你的项目文件夹)
$ virtualenv venv
$ . venv/bin/activate
2.安装flask
$ pip install flask
3.创建hello.py如下
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8080)
4.测试flask运行是否正常
$ python hello.py
浏览器输入服务器公网ip:8080
,若网页显示Hello World说明flask运行正常
Nginx配置
如果应用不需要发布,只是在开发和调试环境中使用,那么不需要安装配置Nginx也可以。但大多数情况下应用需要在生产环境中使用,此时就需要Nginx(或Apache等)反向代理软件登场啦!Nginx除了可以多线程工作外,也能合理分发请求以达到负载均衡,下面介绍一下配置步骤:
1.安装并配置服务、开机自启动:
$ yum install nginx
$ vi /etc/rc.local
在最后一行加入:service nginx start
2.启动Nginx:
$ service nginx start
浏览器输入服务器公网ip
,若显示Nginx欢迎页说明Nginx安装成功
3.备份默认配置文件
$ cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bk
$ vi /etc/nginx/nginx.conf
因为我们要用新的配置,所以把下面的默认server块注释掉或删除
# server {
# listen 80 default_server;
# listen [::]:80 default_server;
# server_name _;
# root /usr/share/nginx/html;
# Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
# location / {
# }
# error_page 404 /404.html;
# location = /40x.html {
# }
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }
4.创建新配置文件:
$ vi /etc/nginx/conf.d/default.conf
配置文件内容如下:
server {
listen 80;
server_name localhost;
charset utf-8;
client_max_body_size 75M;
location / {
include uwsgi_params;
uwsgi_pass unix:/var/www/demo/demo_uwsgi.sock;
}
}
注意上面uwsgi_pass一行要替换为自己的项目文件夹,demo_uwsgi.sock表示Nginx将通过该socket文件与uWSGI通信,名字可以随便取,但要记住,下文配置uWSGI还会用到。
5.重启Nginx,测试效果
$ service nginx restart
浏览器中输入服务器公网ip
,显示502 Bad Gateway
,说明Nginx已经采用了新的配置文件,但因为uWSGI还没有配置,所以socket文件还不存在,因此出现502,下面对uWSGI进行配置。
uWSGI配置
1.安装
$ pip install uwsgi
2.创建新的uWSGI配置文件:
$ vi /var/www/demo/demo_uwsgi.ini
注意!!这里的配置文件名需要和上面socket文件的名字一样!!因为uWSGI会根据该配置文件生成对应名字的socket文件。
配置文件参考格式如下,更多参数说明详见官方文档:
[uwsgi]
#application's base folder
base = /var/www/demo
#python module to import
app = hello
module = %(app)
home = %(base)/venv
pythonpath = %(base)
#socket file's location
socket = /var/www/demo/%n.sock
#permissions for the socket file
chmod-socket = 666
#the variable that holds a flask application inside the module imported at line #6
callable = app
#location of log files
logto = /var/log/uwsgi/%n.log
注意如果使用virtualenv,需要配置home一项;app一项表示flask脚本的名字(前面我们创建了hello.py进行测试,所以这里app=hello,下文需要进行替换)。
3.创建log文件夹存放日志文件:$ mkdir /var/log/uwsgi
4.测试:
$ uwsgi --ini /var/www/demo/demo_uwsgi.ini &
这一步会生成demo_uwsgi.sock文件,进而启动uWSGI服务器,建立Nginx和Flask应用的通信。此时浏览器输入服务器公网ip
,网页显示Hello World。
5.让uWSGI开机自启动:
$ vi /etc/rc.local
最后一行加入:
/usr/local/bin/uwsgi -ini /var/www/demo/demo_uwsgi.ini
模型部署&预加载
完成了框架搭建,下面就可以正式部署模型啦!
1.把训练好的h5模型model.h5上传到/var/www/demo文件夹中
2.创建manage.py如下:
# -*-coding:utf8-*-
from flask import Flask
from keras.models import load_model
import tensorflow as tf
app = Flask(__name__)
graph = tf.get_default_graph()
model = load_model('model.h5')
@application.route('/predict/<imgName>')
def predict(imgName):
imgName = "images/"+imgName
with graph.as_default():
res = model.predict(imgName)
return res
if __name__ == '__main__':
app.run(host='0.0.0.0',port=8080)
注意:
(1)要把模型加载(load_model)放在全局部分,才能做到模型在服务器启动时就加载,而不是每次有请求时再加载;
(2)需要创建全局的graph,并且预测语句要放在with graph.as_default()
块中
(3)app的名字不能改动,不然配置文件里callable=app
部分也要改
(4)app.run()
要写在main块中,不然识别不了
3.更改uWSGI配置文件中的app部分,改为manage,其他不变
$ vi /var/www/demo/demo_uwsgi.ini
[uwsgi]
#application's base folder
base = /var/www/demo
#python module to import
app = manage
module = %(app)
home = %(base)/venv
pythonpath = %(base)
#socket file's location
socket = /var/www/demo/%n.sock
#permissions for the socket file
chmod-socket = 666
#the variable that holds a flask application inside the module imported at line #6
callable = app
#location of log files
logto = /var/log/uwsgi/%n.log
4.重新启动uWSGI和Nginx:
$ service nginx restart
$ killall -9 uwsgi
$ uwsgi --ini /var/www/demo/demo_uwsgi.ini &
5.测试:浏览器输入公网ip/predict/参数
,若返回模型预测结果,说明部署完成。
错误处理
出现错误时及时查看日志,nginx的错误日志在/var/log/nginx/errors.log,uWSGI的错误日志在/var/log/uwsgi/demo_uwsgi.log。下面列举我遇到的几个错误:
1.pip install uwsgi
时出现如下错误:
[gcc -pthread] plugins/python/python_plugin.o
In file included from plugins/python/python_plugin.c:1:0:
plugins/python/uwsgi_python.h:2:20: fatal error: Python.h: No such file or directory
#include <Python.h>
解决:安装python开发依赖库,如果是python2的,运行 yum install python-devel
,python3的需要先更新yum源yum update
,再运行yum install python36-devel
,如果提示找不到,则可能是包改名了,运行yum search python3 | grep devel
可找到包的名字。
2.创建新的配置文件后重启nginx服务器发现如下错误:
nginx: [emerg] "server" directive is not allowed here in /etc/nginx/conf.d/default.conf:1
解决:注意要把新的配置文件放在conf.d文件夹下,且/etc/nginx/nginx.conf中原有的server块要注释掉或者删掉
3.Flask应用的错误:
ValueError: Tensor Tensor xxx is not an element of of this graph
解决:检查你的代码是否有在全局加载图(graph),并且预测语句在with块中
4.uWSGI的错误:
unable to load app 0 (mountpoint='') (callable not found or import error)
解决:检查uWSGI的配置文件中是否有callable=app
一行,如果没有,uWSGI默认会去程序里找名字为application的变量,而如果你的程序里只有app变量,没有application变量就会报错。所以要么加入callable=app
,要么将app变量改名为application。