文章目录
一 、环境准备
打包部署环境:CentOS7 + Anaconda3.5.X
下载源后上传至~/
目录配置系统源:
#!/usr/bin/bash
echo -e '\033[32m======= start ==========\033[0m'
cp CentOS7-Base-163.repo /etc/yum.repos.d/
cd /etc/yum.repos.d/
mv CentOS-Base.repo CentOS-Base.repo.bak
mv CentOS7-Base-163.repo CentOS-Base.repo
echo
echo "cp CentOS7-Base-163.repo /etc/yum.repos.d/"
echo "cd /etc/yum.repos.d/"
echo "mv CentOS-Base.repo CentOS-Base.repo.bak"
echo "mv CentOS7-Base-163.repo CentOS-Base.repo"
echo
echo -e '\033[32m======= doing...==========\033[0m'
echo "yum clean al"
echo "yum makecache"
echo "yum update"
yum clean all
sleep 1
yum makecache
sleep 1
yum update
sleep 1
echo -e '\033[32m======= done! ==========\033[0m'
关于Anaconda环境安装
#!/usr/bin/bash
echo -e '\033[32m======= first ==========\033[0m'
echo
echo "yum -y install swig gcc gcc-c++ kernel-devel"
echo
yum -y install swig gcc gcc-c++ kernel-devel
echo
echo -e '\033[32m======= second ==========\033[0m'
echo
echo "pip install oscrypto"
echo "pip install paramiko"
echo "pip install pyinstaller"
echo "pip install flask"
echo "pip install flask_restful"
echo "pip install pandas"
echo "pip install endesive"
echo "pip install opencv-python"
echo "pip install pdfplumber"
echo "pip install gunicorn"
echo "pip install gevent"
echo "pip install PyMuPDF==1.17.0"
echo
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple oscrypto
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple paramiko
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyinstaller
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple flask
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple flask_restful
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple endesive
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-python
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pdfplumber
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gunicorn
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gevent
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple PyMuPDF==1.17.0
echo -e '\033[32m======= third ==========\033[0m'
echo "pip install wrapt"
echo "pip install tensorflow & keras"
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pip -U
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade setuptools
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple wrapt --ignore-installed
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple tensorflow==2.2.0
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple keras
echo
echo -e '\033[32m======== end ==========\033[0m'
二、pyinstaller打包基于tensorflow 2.2.0的程序
参考文章:【Flask作为Web App部署Keras模型】
准备完服务的代码执行 pyinstaller app.py
打包服务。执行服务:/dist/app/app
缺少依赖模块解决方法:
添加hook文件:hook-tensorflow.py
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
hiddenimports = collect_submodules('tensorflow_core.core.framework')
hiddenimports += collect_submodules('tensorflow_core.core')
hiddenimports += collect_submodules('tensorflow.compiler.tf2tensorrt')
hiddenimports += collect_submodules('tensorflow_core')
hiddenimports += collect_submodules('tensorflow.python.keras.engine.base_layer_v1')
hiddenimports += collect_submodules('tensorflow_core.lite.experimental.microfrontend.python.ops')
可能需要添加hook文件:book-tensorflow.core.framework.py
导入:
hiddenimports=['pkg_resources.py2_warn',
'tensorflow',
'tensorflow_core.python',
'tensorflow_core.python.platform',
'tensorflow_core.python.platform.tf_logging',
'tensorflow_core.python.platform.self_check',
'tensorflow_core.python.platform.build_info',
'tensorflow_core.python.pywrap_tensorflow',
'tensorflow_core.python.pywrap_tensorflow_internal',
'tensorflow_core.python.util',
'tensorflow_core.python.util.deprecation',
'tensorflow_core.python.util.tf_export',
'tensorflow_core.python.util.tf_decorator',
'tensorflow_core.python.util.tf_stack',
'tensorflow_core.python.util.tf_inspect',
'tensorflow_core.python.util.*',
'tensorflow_core.python.util.decorator_utils',
'tensorflow_core.python.util.ANY.*',
'tensorflow_core.python.util.is_in_graph_mode',
'tensorflow_core.python.util.tf_contextlib',
'tensorflow_core.core.*',
'tensorflow_core.core.framework.*']
若含有sklearn模块,则还需要添加如下依赖文件。
["cython", "sklearn", "sklearn.ensemble", "sklearn.neighbors.typedefs", "sklearn.neighbors.quad_tree", "sklearn.tree._utils","sklearn.utils._cython_blas"]
缺少二进制文件模块解决方法:编辑app.spec
文件,在binaries栏添加内容:
binaries = [("/home/`username`/anaconda3/lib/python3.6/site-packages/tensorflow/lite/experimental/microfrontend/python/ops/_audio_microfrontend_op.so",\
"./tensorflow_core/lite/experimental/microfrontend/python/ops")],
打包完,执行程序时可能还会报错:
无法找到动态库 libpython3.6m.so
无法找到动态库tensorflow/lite/experimental/microfrontend/python/ops/_audio_microfrontend_op.so
(可能)需要执行:
mv /home/`username`/path/dist/ocr_server/libpython3.6m.so.1.0 /home/wangsp/ocr_server_files/dist/ocr_server/libpython3.6m.so
mkdir -p /home/`username`/path/dist/ocr_server/tensorflow/lite/experimental/microfrontend/python/ops/
cp /home/`username`/anaconda3/lib/python3.6/site-packages/tensorflow/lite/experimental/microfrontend/python/ops/_audio_microfrontend_op.so /home/`username`/path/dist/ocr_server/tensorflow/lite/experimental/microfrontend/python/ops/_audio_microfrontend_op.so
三、flask服务请求
flask 服务端与客户端示例代码:http://www.suoniao.com/article/23907
flask 服务端支持正则的路由:https://www.cnblogs.com/felixwang2/p/9262464.html
示例:传入json字符串 --server 端
#!/usr/bin/python
# -*- coding: utf-8 -*-
import json
from flask import Flask
from flask import request
from flask import redirect
from flask import jsonify
app = Flask(__name__)
@app.route("/" , methods=["GET", "POST"])
def index():
if request.method == "POST":
a = request.get_data()
dict1 = json.loads(a)
return json.dumps(dict1["data"])
else:
return "<h1>Only post request</h1>"
@app.route("/user/<name>")
def user(name):
return"<h1>hello, %s</h1>" % name
if __name__ =="__main__":
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port)
—client端
#coding=utf-8
import requests
import json
data={ "opr": "add", "data": { "userName": "98997", "disc": "hudihiudhu", "ip": ["10.10.11.1", "10.10.11.2"]}}
data = json.dumps(data)
r = requests.post("http://127.0.0.1:5000/", data)
print(r.status_code)
print(r.headers["content-type"])
print(r.encoding)
print(r.text)
pyinstaller打包后,运行可执行文件报错:TemplateNotFound: index.html
解决方法:在可执行文件中添加
import sys
sys.path.append(os.getcwd())
在app\__init__.py
文件中添加如下内容:
import sys
import os
from flask import Flask
if getattr(sys, 'frozen', False):
template_folder = os.path.join(sys._MEIPASS, 'templates')
app = Flask(__name__, template_folder=template_folder)
else:
app = Flask(__name__)
四、flask 实现多线程
在服务实现多线程中找寻了许多方法,这里我主要测试了gunicorn
,tornado
,gevent
这三种方式。
不同部署方式方法:https://www.jianshu.com/p/e8ee1eed2e50
- gunicorn方式部署
- tornado方式部署
- bjoern部署方式
- cherrypy部署方式
- meinheld 方式
- gevent 部署方式
性能测试:
组合 | 成功率 | 总耗时 | 备注 |
---|---|---|---|
cherrypy | 48% | 18s | 单进程 |
tornado | 76% | 9.5s | 单进程 |
tornado | 84% | 4.5s | 4进程 |
gevent | 84% | 6s | 单进程 |
meinheld | 84% | 3.7s | 单进程 |
bjoern | 84% | 3.7s | 单进程 |
gunicorn+gevent | 84% | 4.3s | 9进程 |
gunicorn+meinheld | 84% | 3.6s | 9进程 |
4.1 gunicorn + flask服务打包
【gunicorn 官网】
flask使用的是Werkzeug来作为它的WSGI server,但是性能很一般,生产环境一般会使用其他的WSGI server, 网上查到有以下WSGI server:
Gunicorn 独角兽,从Ruby的Unicorn移植过来的。
uWSGI 比较全能的一个WSGI server。
CherryPy CherryPy是Python的一个HTTP Framework,然后它也有WSGI server。
可能还有其他的一些WSGI server,对于gunicorn熟悉,那么要使用gunicorn,app.py需添加以下代码:
gunicorn + flask服务:https://www.jianshu.com/p/70a30944fade
使用该方法打包依赖处理,隐藏导入:
hiddenimports=[
"tensorflow.python.keras.engine.base_layer_v1",\
"gunicorn.glogging",\
"gunicorn.workers.sync",\
"gunicorn.workers.ggevent"],
重写Gunicorn基类服务 【Gunicorn文档】
import multiprocessing,os
import gunicorn.app.base
from flask import Flask, request
app = Flask(__name__)
def number_of_workers():
return (multiprocessing.cpu_count() * 2) + 1
class StandaloneApplication(gunicorn.app.base.BaseApplication):
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()
def load_config(self):
config = {key: value for key, value in self.options.items()
if key in self.cfg.settings and value is not None}
for key, value in config.items():
self.cfg.set(key.lower(), value)
def load(self):
return self.application
if __name__ == '__main__':
gunicorn_config = {
'bind': '%s:%s' % ('127.0.0.1', '8080'),
"check_config": True,
"worker_class": "gthread",
"workers": number_of_workers(),
"threads": 2,
'timeout': 60,
"loglevel": "info",
"access_log_format": "gunicorn %(h)s - %(t)s - %(r)s - %(s)s - %(f)s",
"backlog": 30,
}
StandaloneApplication(app, options=gunicorn_config).run()
4.2 tornado+ flask服务打包
在这里查看tornado详情:https://blog.csdn.net/ka_ka314/category_7820903.html
关于日志使用:https://www.cnblogs.com/shijingjing07/p/7670672.html
#!/usr/bin/python
# -*- coding:utf-8 -*-
import os,json
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"]="-1"
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route("/" , methods=["GET", "POST"])
def index():
if request.method == "POST":
a = request.get_data()
dict1 = json.loads(a)
return json.dumps(dict1["data"])
else:
return "<h1>Only post request</h1>"
@app.route("/user/<name>")
def user(name):
return"<h1>hello, %s</h1>" % name
if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
http_server = HTTPServer(WSGIContainer(app))
http_server.listen(port)
IOLoop.instance().start()
4.2 gevent+ flask服务打包
使用协程的方式启动。【参考文章】
下载地址:pip install gevent
https://pypi.org/project/gevent/
#!/usr/bin/python
# -*- coding:utf-8 -*-
import os,json
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"]="-1"
from gevent import monkey
monkey.patch_all()
from flask import Flask
from flask import request
from gevent.pywsgi import WSGIServer
app = Flask(__name__)
@app.route("/" , methods=["GET", "POST"])
def index():
if request.method == "POST":
a = request.get_data()
dict1 = json.loads(a)
return json.dumps(dict1["data"])
else:
return "<h1>Only post request</h1>"
@app.route("/user/<name>")
def user(name):
return"<h1>hello, %s</h1>" % name
if __name__ =="__main__":
port = int(os.environ.get('PORT', 5000))
http_server = WSGIServer(('0.0.0.0', port), app)
http_server.serve_forever()
五、设置日志输出
Python 中的 logging 模块可以让你跟踪代码运行时的事件,当程序崩溃时可以查看日志并且发现是什么引发了错误。Log 信息有内置的层级——调试(debugging)、信息(informational)、警告(warnings)、错误(error)和严重错误(critical)。你也可以在 logging 中包含 traceback 信息。不管是小项目还是大项目,都推荐在 Python 程序中使用 logging。
- 基本使用
配置logging基本的设置,然后在控制台输出日志:
import logging
logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# 使用示例
logger.info("Start log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
- 将日志写入到文件
设置logging,创建一个FileHandler
,并对输出消息的格式进行设置,将其添加到logger,然后将日志写入到指定的文件中,
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log.txt") # 文件路径
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
# 使用示例
logger.info("Start log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
- 将日志同时输出到屏幕和日志文件
logger中添加StreamHandler,可以将日志输出到屏幕上,
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
logger.addHandler(handler)
logger.addHandler(console)
# 使用示例
logger.info("Start log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
综合
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import logging.handlers #日志滚动及删除使用
#1.设置log日志记录格式及记录级别
#level记录级别包括DEBUG/INFO/WARNING/ERROR/CRITICAL,级别依次上升,log只会输出保存设置的级别及以上的日志。如果设置level=logging.DEBUG,则所有级别日志都会输出保存、如果level=logging.CRITICAL,则只输出保存CRITICAL级别日志
#format输出格式levelname级别名、asctime 时间、filename所在文件名、message记录内容
#datefmt 时间格式
#filename 要保存的文件名
#a写入模式,a则每次启动脚本时在原有文件中继续添加;w则每次启动脚本会重置文件然后记录
logging.basicConfig(level=logging.INFO,
format='%(levelname)s: %(asctime)s %(filename)s %(message)s',
datefmt='%Y-%m-%d %A %H:%M:%S',
filename='myapp.log',
filemode='a')
#2.设置log日志的标准输出打印,如果不需要在终端输出结果可忽略
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(levelname)s: %(asctime)s %(filename)s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
#3.设置log日志文件按时间拆分记录,并保存几个历史文件,如果不需要拆分文件记录可忽略
#class logging.handlers.WatchedFileHandler(filename, mode='a', encoding=None, delay=False)
#例:设置每天保存一个log文件,以日期为后缀,保留7个旧文件。
myapp = logging.getLogger()
myapp.setLevel(logging.INFO)
formatter = logging.Formatter('%(levelname)s: %(asctime)s %(filename)s %(message)s')
filehandler = logging.handlers.TimedRotatingFileHandler("myapp.log", when='d', interval=1, backupCount=7)#每 1(interval) 天(when) 重写1个文件,保留7(backupCount) 个旧文件;when还可以是Y/m/H/M/S
filehandler.suffix = "%Y-%m-%d_%H-%M-%S.log"#设置历史文件 后缀
filehandler.setFormatter(formatter)
myapp.addHandler(filehandler)
#4.设置log日志文件按文件大小拆分记录,并保存几个历史文件,如果不需要拆分文件记录可忽略
#class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0)
myapp = logging.getLogger()
myapp.setLevel(logging.INFO)
formatter = logging.Formatter('%(levelname)s: %(asctime)s %(filename)s %(message)s')
filehandler = logging.handlers. RotatingFileHandler("myapp.log", mode='a', maxBytes=1024, backupCount=2)#每 1024Bytes重写一个文件,保留2(backupCount) 个旧文件
filehandler.setFormatter(formatter)
myapp.addHandler(filehandler)
#使用
logging.debug('debug message : %s , result: %s',info,result)
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
https://www.v2ex.com/t/480247
多进程日志
由于python 中logging 并不支持多进程,所以会遇到不少麻烦。
不过python有第三方库pip install concurrent-log-handler
查看详情使用方法
from logging import getLogger, INFO
from concurrent_log_handler import ConcurrentRotatingFileHandler
import os
log = getLogger(__name__)
# Use an absolute path to prevent file rotation trouble.
logfile = os.path.abspath("mylogfile.log")
# Rotate log after reaching 512K, keep 5 old copies.
rotateHandler = ConcurrentRotatingFileHandler(logfile, "a", 512*1024, 5)
log.addHandler(rotateHandler)
log.setLevel(INFO)
log.info("Here is a very exciting log message, just for you")