Python Web 服务

Web 服务器

用于完成和客户端建立链接,接受并解析请求,转发请求,调用 Web 框架处理业务并生成返回内容,组织并返回内容给客户端,关闭链接等工作,比如 Nginx,Gunicorn,uWSGI 等就是 Web 服务器

Web 框架

对 Web 服务的常用功能提取、组织、简化使用,令开发人员可以专注于业务逻辑,比如 Flask、Django 就是 Web 框架

WSGI

为了使得任意 Web 服务器都可以和任意 Web 框架搭配,设计出了 WSGI(Web Server Gateway Interface)接口协议,凡是符合 WSGI 协议的 Web 服务器和 Web 框架都可以搭配使用(注意:uWSGI 和 WSGI 是两个概念,uWSGI 是一个实现了 WSGI 协议的 Web 服务器)

prefork

prefork 是一种服务端编程模型,Gunicorn, uWSGI 都是这种模型的实现
prefork 就是有一个 master 进程 fork 出多个 worker 子进程,由子进程处理请求,master 进程只负责监控worker 子进程状态,如果子进程出现问题,可以重启一个,子进程监听同一端口,可以配置 reuse_port 参数在worker 进程间负载均衡,为了避免多线程编程带来的问题,通常设置一个 worker 就一个线程用于处理请求,同时使用协程模块比如 gevent 使得一个线程可以高效地处理高并发 IO 请求

Gunicorn

Green Unicorn(绿色独角兽)是一个满足 WSGI 协议的、轻量的、简易的、由 Python 实现的、pre-fork 模型的 Web 服务器,支持 sync、eventlet、gevent、tornado、gthread、gaiohttp 等多种 worker 类型

uWSGI

uWSGI 是由 C 语言实现的、满足 WSGI 的、pre-fork 模型的 Web 服务器,支持 uGreen,Greenlet,Stackless,Gevent,Coro::AnyEvent,Tornado,Goroutines,Fibers 等技术,实际上 uWSGI 不仅是一个 Web 服务器,也可以作为 Web 框架,只不过一般都只是当 Web 服务器使用

Nginx

实际上 Gunicorn 或 uWSGI 搭配 Flask 或 Django 就可以提供 Web 服务了,但实际生产环境上经常会在前面再加一个 Nginx 服务器,哪怕 Nginx 和 Gunicorn/uWSGI 是一对一的关系,主要因为 Nginx 有一些其他服务器不具备的强大功能:

  • 负载均衡,Nginx 后面可以有多个节点处理同一个业务,可以在不同节点的服务器之间实现负载均衡
  • 地址映射和端口映射,对于处理不同业务的多个节点,Nginx 可以作为统一入口,通过地址映射隐藏后面的多个服务器
  • 静态文件支持,经过配置之后,Nginx 可以直接处理静态文件请求,不需要经过 Gunicorn/uWSGI 服务器,Gunicorn/uWSGI 服务器负责处理动态请求
  • 伪静态,通过 rewrite 配置实现伪静态,比如把 index.html 指向到一个 test.php?v=1 的动态请求,伪静态会增加性能损耗
  • 缓存静态和伪静态页面,对于静态页面,或是一定时间内不会变化的伪静态页面,可以缓存起来,设置一个超时时间,这样 Nginx 就可以不用每次都去读文件,或是每次都要动态生成页面
  • 缓冲请求和响应,如果由于网络问题,导致请求和响应比较慢,可能会占用 Web 服务器的资源,影响业务逻辑的处理,而 Nginx 可以做缓冲,等收到完整的连接、请求消息后,再发给后端处理,收到后端返回后,立刻响应后端,再将消息响应到前端,这样在后端的 Web 服务器看起来,网络的请求响应都非常快,自己主要时间都是在处理业务逻辑,而相应的缓冲请求响应的工作又是 Nginx 比较擅长的,这样就提高了性能
  • Nginx 的高可用性,高并发抗压能力,都比较强
  • 访问控制、限速等功能
  • 避免直接暴露 WSGI 服务器,可以同时作为防火墙防御网络攻击

Supervisor

为了防止 Gunicorn/uWSGI 等意外挂了,通常可以加一个 Supervisor 做监控 master 进程

Flask

一个 Python 的 Web 框架,主要基于 Werkzeug 和 jinja2

Werkzeug 是一个 WSGI 工具包,它实现了请求,响应对象和实用函数,Flask 通过 Werkzeug 实现 WSGI 协议,同时 Werkzeug 还提供了一个 WSGI 服务器,Flask 默认就是使用 Werkzeug 服务器,但一般只是用于开发调式,生产环境还是用 Gunicorn 等服务器,因为 Werkzeug 服务器性能差一些

jinja2 是 Python 的一个模板引擎,用于渲染生成 HTML 页面

Flask + Gunicorn + Nginx 简例

安装 flask

pip install flask
pip install flask_restplus     ## 方便实现 REST API 的扩展,不是必须的

flask 代码 flask_test.py

import time

from flask import Flask
from flask import request
from flask import Response

from flask_restplus import Api
from flask_restplus import Resource

from functools import wraps

app = Flask(__name__)

api = Api(app, version='1.0', doc='/doc', title='Test API', description='Test API')
'''
api = Api(None, version='1.0', doc='/doc', title='Test API', description='Test API')
api.init_app(app)
'''


def time_it(func):
    @wraps(func)
    def time_it_decorated(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"time consumption : {end_time - start_time}
")
        return result

    return time_it_decorated


count = {}


def count_it(url_type):
    def count_it_decorated(func):
        @wraps(func)
        def new_func(*args, **kwargs):
            if url_type not in count:
                count[url_type] = 0

            count[url_type] += 1

            print(f"receive {url_type} request : {count[url_type]}
")
            return func(*args, **kwargs)

        return new_func

    return count_it_decorated


ns = api.namespace('api_v1', path='/api/v1/', description='Test Interface', decorators=[time_it])

USE_NAMESPACE = False

if USE_NAMESPACE:
    @ns.route('/test')
    class Test(Resource):
        @count_it("test_1")
        def get(self):
            return Response(f"Hello {request.remote_addr}
current time is {time.time()}
")

        @count_it("test_2")
        def post(self):
            time.sleep(5)
            return Response(f"Hello {request.remote_addr}
")
else:
    @app.route('/test/1', methods=['GET'])
    @time_it
    @count_it("test_1")
    def test():
        return f"Hello {request.remote_addr}
current time is {time.time()}
"

    @app.route('/test/2', methods=['POST'])
    @time_it
    @count_it("test_2")
    def test_2():
        time.sleep(5)
        return f"Hello {request.remote_addr}
"


if __name__ == '__main__':
    app.run()

可以直接运行这个 flask 程序,这时用的是 Werkzeug 自带的服务器

python3.6 flask_test.py

安装 Gunicorn

pip3.6 install gunicorn
pip3.6 install gevent     # 使用 gevent 模式才需要,不是必须的

创建 Gunicorn 配置文件 gunicorn_config.py (也可以通过 gunicorn 参数指定,但通过配置文件更方便)

import gevent.monkey
gevent.monkey.patch_all()       # 使用 gevent 模式才需要这步

import multiprocessing

debug = True
loglevel = "debug"
accesslog = "./access.log"
errorlog = "./error.log"
#daemon = True
#capture_output = True

bind = '127.0.0.1:8000'

workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'gevent'

preload_app = True
reload = True

x_forwarded_for_header = 'X-FORWARDED-FOR'
proxy_allow_ips = '*'

启动 gunicorn 服务器

sudo gunicorn -c gunicorn_config.py flask_test:app

安装 Nginx

sudo apt-get install nginx

Nginx 配置

/etc/nginx/nginx.conf

这个配置文件会引入下面两个目录的配置文件

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

创建 /etc/nginx/conf.d/flask_test.conf (配置文件可以有多个)

server {
    listen       80;
    server_name  localhost;

    # location 的 URL 匹配语法
    # ~* 表示正则表达式,不区分大小写
    # ~  表示正则表达式,要区分大小写
    # =  表示精确匹配
    # 没有修饰符的,以指定模式开始,比如 location / 匹配所有以 / 开始的 URL

    # 静态页面,直接读取 html 文件
    location ~* .*.html$ {
        gzip on;
        root /usr/share/nginx/html;
    }
        
    # 动态页面,转发给 gunicorn
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

检测配置

sudo nginx -t

启动

sudo nginx

sudo nginx -s reload

如果需要负载均衡,配置如下

upstream flask_test {
    server 192.168.1.2:8001;
    server 192.168.1.3:8002;
}

server {
    listen       80;
    server_name  localhost;

    # 动态页面,转发给 gunicorn
    location / {
        proxy_pass http://flask_test;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}


原文地址:https://www.cnblogs.com/moonlight-lin/p/12828452.html