python异步IO结合多进程实现web高并发

python是一门非常主流的语言,在各个领域都有应用,但是python一直有个诟病,那就是GIL,这导致python无法开启真正的多线程,go、java、rust他们可以通过多线程的方式实现高并发,通过压榨多核性能更高的任务处理,如果在单线程里面对比python的异步并发能力还是比较强的,我一直想解决这个问题,这个问题也困扰了我很久,今天分享一下我的解决方案。

首先我们先认识一下python在有GIL的情况下如何提高IO并发能力的,python有一个异步库asyncio,这是一个异步非阻塞的IO模型,这个库在当前线程下维护了一个loop循环器,所有的异步任务都放在这个循环器上,每个任务都是一个协程(轻量级线程),这个和GO语言类似,asyncio原理就是当遇到有IO阻塞的任务,不会去等待,直接跳到下一个任务,尽量让操作系统给该线程分配的时间片上执行更多的计算任务,而非阻塞等待任务,既然python有这种解决方案为什么还有困扰呢?新的困扰就是他只能利用单核CPU,很容易出现一核有难八核围观,如果利用更多的CPU去执行计算任务,这一点是python的缺点,接下来我们就来解决这一问题

由于python每个进程中维护一个GIL全局解释锁,所以我们无法真正的使用多线程实现多核性能提升,但是多进程是可以的,所以我们的出发点是多进程,这是无疑的,虽然我们知道多进程和破解GIL的魔咒,但是又有一个新的问题,我们打开一个端口实现web服务监听,如何在多个进程监听同一个端口呢?很多人肯定认为这是不可能的,也没必要,可以启动多个进程多个端口,然后用nginx转发,这样也可以,但是不利于维护,其实python可以实现多进程监听同一个端口的问题,下面分享我的两种方案,一种是独占互斥端口,一种是共享复用端口

首先我们看一下独占互斥端口的实现方式,我们在主进程中创建一个网络连接服务,然后通过子进程共享该网络连接,在每个子进程中进行监听该网络接收,这样该端口的接入的请求分布在各个子进程中进行,从而提高业务并发能力

import multiprocessing
import socket, asyncio, uvloop
from aiohttp import web

async def home(request):
    return web.Response(text='hello!')

def worker(sock):
    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
    app = web.Application()
    app.add_routes([web.get('/', home)])
    web.run_app(app, sock=sock)

if __name__ == '__main__':
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('0.0.0.0', 8080))
    sock.listen(500)
    workers = []
    for i in range(7):
        p = multiprocessing.Process(target=worker, args=(sock,))
        workers.append(p)
    for p in workers: p.start()
    for p in workers: p.join()
    sock.close()

现在我们看第二种方式,共享复用端口,这种方式是非独占端口,无论启动多个进程,都可以使用同一个端口进程接收请求,也能实现把该端口的请求分布在各个子进程中,下面就是我们使用aiohttp的方式实现的效果,只需要在启动参数中加入reuse_address和reuse_port参数即可,将启动的端口和地址和复用的参数相同即可,这种方式还有一个优势,那就是平滑升级,例如业务启动八个子进程接收服务请求,升级后我们拉去最新的代码,然后再启动八个子进程的服务,当新的子进程服务正常运行后,我们在杀掉之前的八个子进程,这样就可以实现平滑升级

import multiprocessing, asyncio, uvloop
from aiohttp import web

async def home(self, request):
    return web.Response(text='hello!')

def worker():
    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
    app = web.Application()
    app.add_routes([web.get('/', View().home)])
    web.run_app(app,port=8080, host='0.0.0.0', reuse_address='0.0.0.0', reuse_port=8080)

if __name__ == '__main__':
    workers = []
    for i in range(7):
        p = multiprocessing.Process(target=worker)
        workers.append(p)
    for p in workers: p.start()
    for p in workers: p.join()

以上就是我们使用python实现高并发的原理,测试发现子进程数为CPU核数减一效果是最好的,可能需要一个进程进行分发任务吧,经过在8核8G的虚拟机环境中测试,并发能力和go(fiber)、rust(actix-web)基本相同,处于同一水平线,ab -n 30000 -c 160 http://127.0.0.1:8080/,测试中仅测试响应“hello”,复杂的情况未做测试,有兴趣的小伙伴可以自行尝试,如果pyhton解决了这个问题,那么python还有个优势,python开发效率更高,AI领域更容易结合。

你可能感兴趣的:(python,负载均衡,信息与通信)