从Python协程到高性能服务器

380 阅读3分钟

从Python协程到高性能服务器

1.如何用携程实现多任务?

1.1 通过yield

import time

def task01():

    while True:

        print("---1---")

        time.sleep(0.2)

        yield

def task02():

    while True:

        print("---2---")

        time.sleep(0.2)

        yield

def main():

    t1 = task01()

    t2 = task02()

    while True:

        # next(t1)

        # next(t2)

        t1.__next__()

        t2.__next__()

if __name__ == "__main__":

    main()

1.2 通过greenlet

# 下载greenlet
pip3 install greenlet
# coding=utf-8

from greenlet import greenlet

import time


def task01():

    while True:

        print("---1---")

        time.sleep(0.2)

        gr2.switch()

def task02():

    while True:

        print("---2---")

        time.sleep(0.2)

        gr1.switch()

gr1 = greenlet(task01)

gr2 = greenlet(task02)

gr1.switch()

1.3 通过gevent

# 下载gevnet
pip3 install gevent
import gevent

def task01(n):

    for i in range(n):

        print("task01", i)

        gevent.sleep(0.1)

def task02(n):

    for i in range(n):

        print("task02", i)

        gevent.sleep(0.1)

def task03(n):

    for i in range(n):

        print("task03", i)

        gevent.sleep(0.1)


print("---1---")

g1 = gevent.spawn(task01, 5)

print("---2---")

g2 = gevent.spawn(task02, 5)

print("---3---")

g3 = gevent.spawn(task03, 5)

print("---4---")

g1.join()

g2.join()

g3.join()

问题:只有gevnet中的io才能被gevent利用,我们代码中所有的io都不是gevent中的,这样我们写好的代码怎么办?答案是打补丁 gevent.monkey.patch_all()

1.4 gevent最终方案

import gevent

from gevent import monkey

import time

monkey.patch_all()  # 自动将time.sleep() 替换未 gevent.sleep()

def task01(n):

    for i in range(n):

        print("task01", i)

        time.sleep(0.1)

        # gevent.sleep(0.1)


def task02(n):

    for i in range(n):

        print("task02", i)

        time.sleep(0.1)

        # gevent.sleep(0.1)


def task03(n):

    for i in range(n):

        print("task03", i)

        time.sleep(0.1)

        # gevent.sleep(0.1)


if __name__ == "__main__":

    gevent.joinall([

        gevent.spawn(task01, 5),

        gevent.spawn(task02, 5),

        gevent.spawn(task03, 5)

    ])

2.协程的web服务器

我们传统的框架比如flask,在高并发初使用的是线程或者进程,但这两个方案都在python的高并发中不适用,多进程(非常多)不实用的原因是没有那么多资源,多线程不能使用的原因是GIL,所以python高并发服务器的最佳解决方案是 多进程 + 协程的方式。

fastapi框架部署的时候使用的Gunicorn和uvicorn就是 多进程 + 协程 的方式。

在gevnet模块的pywsgi中有一个封装好的协程web服务器 WSGIServer,开箱即用,是遵循wsgi的协程服务器。

#!/usr/bin/python

"""WSGI server example"""

from __future__ import print_function

from gevent.pywsgi import WSGIServer


def application(env, start_response):

    if env['PATH_INFO'] == '/':

        start_response('200 OK', [('Content-Type', 'text/html')])

        return [b"<b>hello world</b>"]



    start_response('404 Not Found', [('Content-Type', 'text/html')])

    return [b'<h1>Not Found</h1>']


if __name__ == '__main__':

    print('Serving on 8000...')

    WSGIServer(('127.0.0.1', 8000), application).serve_forever()

3.uvicorn+fastapi和gevent.pywsgi.WSGIserver的性能对比

PS C:\Users\NINGMEI\Downloads\httpd-2.4.52-o111m-x86-vc15\Apache24\bin> .\ab.exe -c 100 -n 1000 http://127.0.0.1:8001/
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)

Server Software:        uvicorn
Server Hostname:        127.0.0.1
Server Port:            8001

Document Path:          /
Document Length:        13 bytes

Concurrency Level:      100
Time taken for tests:   0.740 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      138000 bytes
HTML transferred:       13000 bytes
Requests per second:    1351.95 [#/sec] (mean)
Time per request:       73.968 [ms] (mean)
Time per request:       0.740 [ms] (mean, across all concurrent requests)
Transfer rate:          182.20 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.3      0       1
Processing:    12   71  13.3     69     106
Waiting:        0   55  14.2     55      91
Total:         12   71  13.3     69     106

Percentage of the requests served within a certain time (ms)
  50%     69
  66%     75
  75%     77
  80%     82
  90%     94
  95%     94
  98%     94
  99%     95
 100%    106 (longest request)
 
PS C:\Users\NINGMEI\Downloads\httpd-2.4.52-o111m-x86-vc15\Apache24\bin> .\ab.exe -c 100 -n 1000 http://127.0.0.1:8000/
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)

Server Software:
Server Hostname:        127.0.0.1
Server Port:            8000

Document Path:          /
Document Length:        18 bytes

Concurrency Level:      100
Time taken for tests:   0.643 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      138000 bytes
HTML transferred:       18000 bytes
Requests per second:    1556.17 [#/sec] (mean)
Time per request:       64.260 [ms] (mean)
Time per request:       0.643 [ms] (mean, across all concurrent requests)
Transfer rate:          209.72 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.3      0       1
Processing:    12   61   8.5     61      84
Waiting:        0   61   8.6     61      84
Total:         12   61   8.5     61      84

Percentage of the requests served within a certain time (ms)
  50%     61
  66%     62
  75%     64
  80%     65
  90%     71
  95%     76
  98%     79
  99%     83
 100%     84 (longest request)

跑在8001端口的服务是fastapi+uvicorn的helloworld 跑在8000端口的服务是WSGIServer 可以看出 WSGIServer 还是比 单uvicorn好一些,但这两种方案已经比原来的纯django或者flask的性能高很多了

个人认为如果用wsgi部署项目可以使用 WSGIServer 如果用asgi部署项目可以用 Gunicorn + uvicorn

本人仅个人观点,如有错误,请指出,共同进步。谢谢!