文章目录

一 、环境准备

打包部署环境: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 服务端支持正则的路由:​​javascript:void(0)​

示例:传入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官网文档】​​​ ​​【tornado中文文档】​​ \

在这里查看tornado详情:​​javascript:void(0)​​​ 关于日志使用:​​javascript:void(0)​

#!/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。

  1. 基本使用
    配置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.")
  1. 将日志写入到文件
    设置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.")
  1. 将日志同时输出到屏幕和日志文件
    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")