python作为服务端语言来说还是比较吃力的,毕竟不像java有那么完善的解决方案。这里分享一个用flask+gunicorn+gevent来实现高并发的后台。代码只是一个功能的抽象表达,只不过刚好可以在计算机上运行而已,所以这个项目结构很多地方是可以个性化修改的,我这里只是展示了我的用法。
关键词:python、flask、restful、gunicorn、gevent
项目地址:https://github.com/zmy537565154/flask-project
flask是一个比较轻的框架,当然很多功能都要自己实现。用flask自带的webserver,每收到一个请求会新建一个线程,所以请求并发数高的话会很慢。gunicorn来实现webserver,会启动多个进程,通过gevent模式来运行,会达到协程的效果,每来一个请求会自动分配给不同的进程来执行。
上图:
解释一下项目的结构:
没什么好说的,django、tornado都有这个文件夹,整个项目的逻辑代码都在这里
在这里可以写入项目启动时要加载的内容,以及注册蓝本。额外初始化的内容可以根据不同的环境进行不同的初始化操作。
from flask import Flask
from conf.config import config
from app.extensions import *
from app.views import blueprint_config
from app._apis import *
def create_app(config_name='DevelopmentConfig'):
'''
封装创建方法
:param config_name: 环境名
:return: app应用
'''
# 创建app应用
app = Flask(__name__)
# 加载配置
app.config.from_object(config[config_name])
# 额外初始化
# config.get().init_app(app)
# 加载扩展
extension_config(app)
# 注册蓝本
blueprint_config(app)
return app
这里我写了接收请求时的操作,对请求数据进行校验,并且可以针对不同的场景有两种实现,BaseResource是restful的标准写法,但是用postman测试的时候只能接收json数据,无法接收text,所以我又写了一个MyBaseResource,json和text兼容
# coding: utf-8
from flask_restful import Resource, reqparse
from flask import request
import json
from .common.log_ import logger
import flask_restful
class MyRequestParser(reqparse.RequestParser):
"""自定义参数请求"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class BaseResource(Resource):
default_help = '不是一个有效值'
# tid_file_dir = Config._REPORT_FILES_DIR
def __init__(self, *args, **kwargs):
self.parser = MyRequestParser()
logger.info(request.remote_addr + ' >>> 请求参数:' + str(request.json))
super().__init__(*args, **kwargs)
# 自定义请求体检验
class MyBaseResource(Resource):
def __init__(self, *args, **kwargs):
pass
def get_params(self):
try:
params = request.form
if not params:
params = json.loads(request.data.decode())
except:
flask_restful.abort(400, message='请求体格式有误')
logger.info(request.remote_addr + ' >>> 请求参数:' + str(params))
return params
一个api的视图层代码,api类可以选择继承BaseResource或者MyBaseResource。其中继承BaseResource后,init方法内可以通过 self.parser.add_argument()定义请求体字段,一个参数为字段名,第二个为是否必填,type为数据类型,location为该字段在请求中的位置。如果请求体不符合规范,restful还会帮你响应默认的错误信息,当然也可以自定义。代码如下:
from app.base import BaseResource
class FirstApi(BaseResource):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.parser.add_argument('name', required=True, type=str,
location=['form', 'json', 'args', 'files', 'values', 'headers'])
def get(self):
return 'POST request only'
def post(self):
params = self.parser.parse_args()
name = params['name']
result = name+',hello!'
return result
完成一个api的view层代码后,在__init__.py中注册新的api,代码如下:
from app.extensions import api
from .firstApi import FirstApi
api.add_resource(FirstApi, '/hello')
一些公共的功能在这里,比如日志等,不细说了。
一些工具类,其实和common差不多,可有可无,不细说。
这里用于注册蓝本,在项目启动时会执行,代码基本不用改,这里就不贴了。
这里可以在项目启动时初始化一些额外的操作,我暂时没用到,代码不贴了。
配置文件。其中config.py可以写一个基类,和若干个继承基类的子类,来实现不同环境时加载不同的配置。
# -*- coding: utf-8 -*-
# Config里面算是通用配置,子类分别定义专属配置
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
pass
'''
此处为配置内容
'''
class DevelopmentConfig(Config):
DEBUG = True
@staticmethod
def init_app(app):
pass
config = {
'DevelopmentConfig': DevelopmentConfig
}
项目启动时,该文件夹下会生成一个内容是该项目的进程ID的文件,方便你kill掉。
这些都不说了,静态文件夹和临时文件夹。
配置要监听的端口号,以及gun本身的日志的配置,应该还能配置更多的内容,我这里比较简陋。
import gevent.monkey
gevent.monkey.patch_all()
import multiprocessing
debug = True
loglevel = 'deubg'
bind = '0.0.0.0:5000'
pidfile = 'gun_log/gunicorn.pid'
logfile = 'gun_log/debug.log'
#启动的进程数
workers = multiprocessing.cpu_count()
worker_class = 'gunicorn.workers.ggevent.GeventWorker'
x_forwarded_for_header = 'X-FORWARDED-FOR'
项目启动时,首先加载gun.py,然后就加载manage,这里创建app对象
from flask_script import Manager
from flask_migrate import MigrateCommand
from app import create_app
import os
# 环境 此处直接默认为生产环境
# config_name = os.environ.get('CONFIG_NAME') or 'default'
# 创建app
app = create_app()
manager = Manager(app)
# manager.add_command('db', MigrateCommand)
if __name__ == '__main__':
manager.run()
最后最后,启动的命令:
gunicorn -c gun.py manage:app