Python并发网络编程

TODO https://www.v2ex.com/t/1110155#reply10

为什么要并发? 在等待中多做点有用的事情, 在不确定的世界中填满空虚的内心

业务场景:

主要瓶颈在于不可控的外部环节: 目标网站访问速度, 数据库查询耗时, 数据网络传输

坚守朴素的单进程模式, 通过队列系统 / 负载均衡等手段, 也可以撑到很高, 但是资源利用率太低

这里不考虑并行计算相关加速目的

对于可控系统, 首先优化单任务耗时解决来瓶颈问题, 如批量化等, 最后再考虑并发的手段!

进程 VS 线程 VS 协程

多进程 / multiprocessing

多线程 / multithreading

共同的缺点: 操作系统层面调度, 中断过多, 内核态占比高, 无效计算占比高, 额外内存的开销, 导致单机可以多开的数量有上限. 因此需要用户态”线程”, 及更轻量的任务调度机制

协程 / coroutine, co is for cooperative. 用户态的最小计算调度任务单元, 由于实际上是单个执行, 共享数据访问不需要加锁等手段保护. 一个任务的计算状态保存可以做到很小

网络编程模型

socket编程

IO模型

事件驱动 (event loop)

事件 (event) 种类

回调 = 对应事件的处理动作

注意, 非IO相关代码仍然是串行化执行的, 调度本身的检查计算开销损失, 因此并不能实现完全的线性加速

调度器 (scheduler), 可以是单线程, 也可以是多线程, 从事件任务队列中选取->执行->放回.

事件任务队列可以是每个调度器独有一个, 从而减少共享数据读写, 或者全局一个, 或者两者兼有. 从而这里可以进一步细化任务分配策略 (push / pull / job scheduling / …).

调度策略

一些实现的协程, 如果没有遇到IO调度点, 可以永远霸占执行, 因此一些死循环会导致无法调度. Goroutine 是部分抢占式的, safe-point才可被抢占, 还是可以做到单Goroutine拖垮整个程序.

https://go.dev/src/runtime/preempt.go

抢占式的调度需要语言层面的支持, 如虚拟机指令执行层面. Erlang的Process, 基于虚拟机指令执行次数调度, 从而实现”真”实时调度.

libev vs libuv

跨操作系统的事件驱动库

libuv: 非阻塞, 回调方式, 带线程池, 最初为了 Node.js 写的

libev: 阻塞, epoll封装

libevent: libev前任

GIL

global interpreter lock

CPython字节码执行层面同时只能执行一个, 为了简化GC, 不能充分利用多核, 纯PY代码的计算密集型的任务比较受限 (C扩展代码可以自行实现多线程计算逻辑)

不过也有新的提案去掉GIL, 不过是个困难的工程

https://peps.python.org/pep-0703/

Python Coroutines

yield

iterator

generator

asyncio

gevent

gevent = greenlet + libev/libuv

greenlet: C扩展实现的coroutine, 不带调度功能

此外, 标准库patching / 驱动事件调度

好处: 不用改代码, monkey.patch_all() save the day, 巨大的实施优势

PY 的模块和普通对象一样, 任人玩弄. monkey patch, 类似测试时的mock手段, 直接目标模块替换改写相关实现方法, 维护工作很高, 得随时跟进目标模块接口及内在实现逻辑). 以及必须得尽早执行, 否则其他模块引入的是原始的目标模块方法.

ref

tornado

基于asyncio

https://www.tornadoweb.org/en/stable/

twisted

scrapy框架, 因此爬虫方向才关心

TODO

事件驱动模式下, 多进程worker是必要的么? 是的, 多核系统场景下 n worker + c courine > 1 worker + n * c coroutine

docker / k8s 时代, 多进程管控挪到 pod 层面管控, 框架本身的master管理进程相对鸡肋了

WSGI

https://www.python.org/dev/peps/pep-3333/

WSGI: (Python) Web Server Gateway Interface / 针对PY语言的web协议

HTTP -> Web Server -> WSGI server (with app code)

ref Servlet

采集场景打满带宽, 提高资源利用率, 外部目标站点的请求耗时波动非常大 (不可控的网路, 目标服务器处理速度, 触发限流等等); WEB场景, 不涉及外部API调用的情况下, 主要是数据库/缓存读写, IO相对可控, 主要不是抗请求并发 (多起来数据库也受不了了), 再宽的高速堵点车也难受, 主要还是解决单个慢查询不要影响其他的短请求, 类似堵车后有个快速分流的支路.

WSGI servers (or Python Web Framework ???)

https://www.fullstackpython.com/wsgi-servers.html

NGINX也实现了WSGI协议, 部署主要是为了分离一些静态请求, 和方便运维吧

werkzeug

flask use werkzeug, 主要是开发用, 缺少生产环境的严肃考量, 足够简单, 对于量少的服务也堪用了

uWSGI

uWSGI: 实现了WSGI协议的web服务器 / 类似NGINX 还实现了HTTP协议, 裸跑的时候用的是这个, 自己搞了一套uwsgi协议(注意区别大小写), 不过应该废掉了

gunicorn

Gunicorn is a robust web server that implements process monitoring and automatic restarts. This can be useful when running Uvicorn in a production environment.

https://docs.gunicorn.org/en/latest/design.html

https://github.com/tiangolo/fastapi

ASGI

https://www.python.org/dev/peps/pep-33333/ https://asgi.readthedocs.io/en/latest/

Uvicorn is an ASGI server based on uvloop

结语

Python并发编程是个后补的, 底子不好, 远没有语言内置的来的方便.

HOME