分享一个flask高并发部署方案

  • Post author:
  • Post category:其他


python作为服务端语言来说还是比较吃力的,毕竟不像java有那么完善的解决方案。这里分享一个用flask+gunicorn+gevent来实现高并发的后台。代码只是一个功能的抽象表达,只不过刚好可以在计算机上运行而已,所以这个项目结构很多地方是可以个性化修改的,我这里只是展示了我的用法。

关键词:python、flask、restful、gunicorn、gevent

项目地址:

https://github.com/zmy537565154/flask-project

flask是一个比较轻的框架,当然很多功能都要自己实现。用flask自带的webserver,每收到一个请求会新建一个线程,所以请求并发数高的话会很慢。gunicorn来实现webserver,会启动多个进程,通过gevent模式来运行,会达到协程的效果,每来一个请求会自动分配给不同的进程来执行。

上图:

解释一下项目的结构:

app

没什么好说的,django、tornado都有这个文件夹,整个项目的逻辑代码都在这里

app.__init__

在这里可以写入项目启动时要加载的内容,以及注册蓝本。额外初始化的内容可以根据不同的环境进行不同的初始化操作。

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

app.base

这里我写了接收请求时的操作,对请求数据进行校验,并且可以针对不同的场景有两种实现,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

app.firstApi

一个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

app._apis

完成一个api的view层代码后,在__init__.py中注册新的api,代码如下:

from app.extensions import api
from .firstApi import FirstApi

api.add_resource(FirstApi, '/hello')

app.common

一些公共的功能在这里,比如日志等,不细说了。

app.utils

一些工具类,其实和common差不多,可有可无,不细说。

app.views

这里用于注册蓝本,在项目启动时会执行,代码基本不用改,这里就不贴了。

app.extensions

这里可以在项目启动时初始化一些额外的操作,我暂时没用到,代码不贴了。

conf

配置文件。其中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
}

gun_log

项目启动时,该文件夹下会生成一个内容是该项目的进程ID的文件,方便你kill掉。

static/templates

这些都不说了,静态文件夹和临时文件夹。

gun.py

配置要监听的端口号,以及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'

manage.py

项目启动时,首先加载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



版权声明:本文为zmy941110原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。