Asyncio 协议Protocol 与 传输Transport

Asyncio 协议Protocol 与 传输Transport,第1张

python在asyncio库中,提供了一种简单的网络传输模型,协议与传输。

协议和传输,在socket的基础上进行了封装,是更高一层次的应用。

所以说: ASGI服务器并不是从socket基础层面实现通信,而是使用了asyncio中原生提供的一种网络通信方式。

Transport 类位于 asyncio.transports 中,有例如 BaseTransport , WriteTransport 只写, ReadTransport 只读, Transport 继承于前两个只写和只读的Transport

位于 asyncio.Protocol

接受protocol_factory,可以调用的工厂函数,其返回一个协议Protocol实例

server 对象是 asyncio.base_events.Server 的实例

我简单写了个小例子,使用协议和传输,制作一个C/S

为了方便观看调整了下key顺序

可以明确看到,使用了socket,说明socket的建立,已经是封装到内部的。

s端和c端的socket是完全对应的。

而H11是一个实现 http协议

uvicorn 用了HTTP协议库做了相应的 Protocol 。交由asyncio提供的网络应用服务处理

诚实的说,直到目前为止,我并不欣赏django。在我的认知它并不是多么精巧的设计。只是由功能堆积起来的"成熟方案"。但每一样东西的崛起都是时代的选择。无论你多么不喜欢,但它被需要。希望有一天,python能有更多更丰富的成熟方案,且不再被诟病性能和可维护性。(屁话结束)

取其精华去其糟粕,django的优点是方便,我们这次源码阅读的目的是探究其方便的本质。计划上本次源码阅读不会精细到每一处,而是大体以功能为单位进行解读。

django-admin startproject HelloWorld 即可生成django项目,命令行是exe格式的。

manage.py 把参数交给命令行解析。

execute_from_command_line() 通过命令行参数,创建一个管理类。然后运行他的 execute() 。

如果设置了reload,将会在启动前先 check_errors 。

check_errors() 是个闭包,所以上文结尾是 (django.setup)() 。

直接看最后一句 settings.INSTALLED_APPS 。从settings中抓取app

注意,这个settings还不是我们项目中的settings.py。而是一个对象,位于 django\conf\__init__.py

这是个Settings类的懒加载封装类,直到 __getattr__ 取值时才开始初始化。然后从Settings类的实例中取值。且会讲该值赋值到自己的 __dict__ 上(下次会直接在自己身上找到,因为 __getattr__ 优先级较低)

为了方便debug,我们直接写个run.py。不用命令行的方式。

项目下建个run.py,模拟runserver命令

debug抓一下setting_module

回到 setup() 中的最后一句 apps.populate(settings.INSTALLED_APPS)

开始看 apps.populate()

首先看这段

这些App最后都会封装成为AppConfig。且会装载到 self.app_configs 字典中

随后,分别调用每个appConfig的 import_models() 和 ready() 方法。

App的装载部分大体如此

为了方便debug我们改写下最后一句

res的类型是 Command <django.contrib.staticfiles.management.commands.runserver.Command object at 0x00000101ED5163A0>

重点是第二句,让我们跳到 run_from_argv() 方法,这里对参数进行了若干处理。

用pycharm点这里的handle会进入基类的方法,无法得到正确的走向。实际上子类Commond重写了这个方法。

这里分为两种情况,如果是reload重载时,会直接执行 inner_run() ,而项目启动需要先执行其他逻辑。

django 项目启动时,实际上会启动两次,如果我们在项目入口(manage.py)中设置个print,会发现它会打印两次。

第一次启动时, DJANGO_AUTORELOAD_ENV 为None,无法进入启动逻辑。会进入 restart_with_reloader() 。

在这里会将 DJANGO_AUTORELOAD_ENV 置为True,随后重启。

第二次时,可以进入启动逻辑了。

这里创建了一个django主线程,将 inner_run() 传入。

随后本线程通过 reloader.run(django_main_thread) ,创建一个轮询守护进程。

我们接下来看django的主线程 inner_run() 。

当我们看到wsgi时,django负责的启动逻辑,就此结束了。接下来的工作交由wsgi服务器了

这相当于我们之前在fastapi中说到的,将fastapi的app交由asgi服务器。(asgi也是django提出来的,两者本质同源)

那么这个wsgi是从哪来的?让我们来稍微回溯下

这个settings是一个对象,在之前的操作中已经从 settings.py 配置文件中获得了自身的属性。所以我们只需要去 settings.py 配置文件中寻找。

我们来寻找这个 get_wsgi_application() 。

它会再次调用 setup() ,重要的是,返回一个 WSGIHandler 类的实例。

这就是wsgiapp本身。

load_middleware() 为构建中间件堆栈,这也是wsgiapp获取setting信息的唯一途径。导入settings.py,生成中间件堆栈。

如果看过我之前那篇fastapi源码的,应该对中间件堆栈不陌生。

app入口→中间件堆栈→路由→路由节点→endpoint

所以,wsgiapp就此构建完毕,服务器传入请求至app入口,即可经过中间件到达路由进行分发。

使用进程管理器确保你以弹性方式运行运行多个进程,你可以执行服务器升级而不会丢弃客户端的请求。

一个进程管理器将会处理套接字设置,启动多个服务器进程,监控进程活动,监听进程重启、关闭等信号。

Uvicorn 提供一个轻量级的方法来运行多个工作进程,比如 --workers 4 ,但并没有提供进行的监控。

Gunicorn 是成熟的,功能齐全的服务器,Uvicorn 内部包含有 Guicorn 的 workers 类,允许你运行 ASGI 应用程序,这些 workers 继承了所有 Uvicorn 高性能的特点,并且给你使用 Guicorn 来进行进程管理。

这样的话,你可能动态增加或减少进程数量,平滑地重启工作进程,或者升级服务器而无需停机。

在生产环境中,Guicorn 大概是最简单的方式来管理 Uvicorn 了,生产环境部署我们推荐使用 Guicorn 和 Uvicorn 的 worker 类:

执行上述命令将开户 4 个工作进程,其中 UvicornWorker 的实现使用 uvloop 和httptools 实现。在 PyPy 下运行,你可以使用纯 Python 实现,可以通过使用UvicornH11Worker 类来做到这一点。

Gunicorn 为 Uvicorn 提供了不同的配置选项集,但是一些配置暂不支持,如--limit-concurrency 。

要supervisor用作流程管理器,您应该:

一个简单的主管配置可能看起来像这样: administratord.conf:

要circus用作流程管理器,您应该:

使用 Circus 与 Supervisor 很类似。配置文件 circus.ini 如下:

然后运行 circusd circus.ini 。

Nginx 作为 Uvicorn 进程的代理并不是必须的,你可以使用 Nginx 做为负载均衡。推荐使用 Nginx 时配置请求头,如 X-Forwarded-For,X-Forwarded-Proto,以便 Uvicorn 识别出真正的客户端信息,如 IP 地址,scheme 等。这里有一个配置文件的样例:

要使用https运行uvicorn,需要证书和私钥。推荐的获取方法是使用 Let's Encrypt 。

对于使用https进行本地开发,可以使用 mkcert 生成有效的证书和私钥。

也可以与uvicorn的工人一起使用证书来获取gunicorn


欢迎分享,转载请注明来源:夏雨云

原文地址:https://www.xiayuyun.com/zonghe/468851.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-06-05
下一篇2023-06-05

发表评论

登录后才能评论

评论列表(0条)

    保存