您现在的位置是:亿华云 > IT科技
工程实践:用Asyncio协程构建高并发应用
亿华云2025-10-09 01:26:32【IT科技】7人已围观
简介本文转载自微信公众号「小菜学编程」,作者 fasionchan。转载本文请联系小菜学编程公众号。C10K问题在互联网尚未普及的早期,一台服务器同时在线 100 个用户已经算是非常大型的应用了,工程上没
本文转载自微信公众号「小菜学编程」,工程构建高并作者 fasionchan。实践转载本文请联系小菜学编程公众号。用A用
C10K问题
在互联网尚未普及的协程早期,一台服务器同时在线 100 个用户已经算是工程构建高并非常大型的应用了,工程上没有什么挑战。实践
随着 Web 2.0 时代的用A用到来,用户群体成几何倍数增长,协程服务器需要更强的工程构建高并并发处理能力才能承载海量的用户。这时,实践著名的用A用 C10K 问题诞生了——如何让单台服务器同时支撑 1 万个客户端连接?
最初的服务器应用编程模型,是协程基于进程/线程的:当一个新的客户端连接上来,服务器就分配一个进程或线程,工程构建高并来处理这个新连接。实践这意味着,用A用想要解决 C10K 问题,操作系统需要同时运行 1 万个进程或线程。
进程和线程是操作系统中,开销最大的资源之一。每个新连接都新开进程/线程,将造成极大的香港云服务器资源浪费。况且,受硬件资源制约,系统同一时间能运行的进程/线程数存在上限。
换句话讲,在进程/线程模型中,每台服务器能处理的客户端连接数是非常有限的。为支持海量的业务,只能通过堆服务器这种简单粗暴的方式来实现。但这样的人海战术,既不稳定,也不经济。
为了在单个进程/线程中同时处理多个网络连接,select 、 poll 、epoll 等 IO多路复用 技术应运而生。在IO多路复用模型,进程/线程不再阻塞在某个连接上,而是同时监控多个连接,只处理那些有新数据达到的活跃连接。
为什么需要协程
单纯的IO多路复用编程模型,不像阻塞式编程模型那样直观,这为工程项目带来诸多不便。高防服务器最典型的像 JavaScript 中的回调式编程模型,程序中各种 callback 函数满天飞,这不是一种直观的思维方式。
为实现阻塞式那样直观的编程模型,协程(用户态线程)的概念被提出来。协程在进程/线程基础之上,实现多个执行上下文。由 epoll 等IO多路复用技术实现的事件循环,则负责驱动协程的调度、执行。
协程可以看做是IO多路复用技术更高层次的封装。虽然与原始IO多路复用相比有一定的性能开销,但与进程/线程模型相比却非常突出。协程占用资源比进程/线程少,而且切换成本比较低。因此,协程在高并发应用领域潜力无限。
然而,协程独特的运行机制,让初学者吃了不少亏,错漏百出。亿华云计算
接下来,我们通过若干简单例子,探索协程应用之道,从中体会协程的作用,并揭示高并发应用设计、部署中存在的常见误区。由于 asyncio 是 Python 协程发展的主要趋势,例子便以 asyncio 为讲解对象。
第一个协程应用
协程应用由事件循环驱动,套接字必须是非阻塞模式,否则会阻塞事件循环。因此,一旦使用协程,就要跟很多类库说拜拜了。以 MySQL 数据库操作为例,如果我们使用 asyncio ,就要用 aiomysql 包来连数据库。
而想要开发 Web 应用,则可以用 aiohttp 包,它可以通过 pip 命令安装:
$ pip install aiohttp这个例子实现一个完整 Web 服务器,虽然它只有返回当前时间的功能:
from aiohttp import web from datetime import datetime async def handle(request): return web.Response(text=datetime.now().strftime(%Y-%m-%d %H:%M:%S)) app = web.Application() app.add_routes([ web.get(/, handle), ]) if __name__ == __main__: web.run_app(app)第 4 行,实现处理函数,获取当前时间并返回;
第 7 行,创建应用对象,并将处理函数注册到路由中;
第 13 行,将 Web 应用跑起来,默认端口是 8080 ;
当一个新的请求到达时,aiohttp 将创建一个新协程来处理该请求,它将负责执行对应的处理函数。因此,处理函数必须是合法的协程函数,以 async 关键字开头。
将程序跑起来后,我们就可以通过它获悉当前时间。在命令行中,可以用 curl 命令来发起请求:
$ curl http://127.0.0.1:8080/ 2020-08-06 15:50:34压力测试
研发高并发应用,需要评估应用的处理能力。我们可以在短时间内发起大量的请求,并测算应用的吞吐能力。然而,就算你手再快,一秒钟也只能发起若干个请求呀。怎么办呢?
我们需要借助一些压力测试工具,例如 Apache 工具集中的 ab 。如何安装使用 ab 不在本文的讨论范围,请参考这篇文章:Web压力测试(https://network.fasionchan.com/zh_CN/latest/performance/web-pressure-test.html) 。
事不宜迟,我们先以 100 为并发数,压 10000 个请求看看结果:
$ ab -n 10000 -c 100 http://127.0.0.1:8080/ This is ApacheBench, Version 2.3 <$Revision: 1706008 $> 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) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Python/3.8 Server Hostname: 127.0.0.1 Server Port: 8080 Document Path: / Document Length: 19 bytes Concurrency Level: 100 Time taken for tests: 5.972 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 1700000 bytes HTML transferred: 190000 bytes Requests per second: 1674.43 [#/sec] (mean) Time per request: 59.722 [ms] (mean) Time per request: 0.597 [ms] (mean, across all concurrent requests) Transfer rate: 277.98 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 2 1.5 1 15 Processing: 43 58 5.0 57 89 Waiting: 29 47 6.3 47 85 Total: 43 60 4.8 58 90 Percentage of the requests served within a certain time (ms) 50% 58 66% 59 75% 60 80% 61 90% 65 95% 69 98% 72 99% 85 100% 90 (longest request)-n 选项,指定总请求数,即总共发多少个请求;
-c 选项,指定并发数,即同时发多少个请求;
从 ab 输出的报告中可以获悉,10000 个请求全部成功,总共耗时 5.972 秒,处理速度可以达到 1674.43 个每秒。
现在,我们尝试提供并发数,看处理速度有没有提升:
$ ab -n 10000 -c 100 http://127.0.0.1:8080/在 1000 并发数下,10000 个请求在 5.771 秒内完成,处理速度是 1732.87 ,略有提升但很不明显。这一点也不意外,例子中的处理逻辑绝大部分都是计算型,虚增并发数几乎没有任何意义。
协程擅长做什么
协程擅长处理 IO 型的应用逻辑,举个例子,当某个协程在等待数据库响应时,事件循环将唤醒另一个就绪协程来执行,以此提高吞吐。为降低复杂性,我们通过在程序中睡眠来模拟等待数据库的效果。
import asyncio from aiohttp import web from datetime import datetime async def handle(request): # 睡眠一秒钟 asyncio.sleep(1) return web.Response(text=datetime.now().strftime(%Y-%m-%d %H:%M:%S)) app = web.Application() app.add_routes([ web.get(/, handle), ]) if __name__ == __main__: web.run_app(app) 并发数请求总数耗时(秒)处理速度(请求/秒)100 10000 102.310 97.74 500 10000 22.129 451.89 1000 10000 12.780 782.50可以看到,随着并发数的增加,处理速度也有明显的提升,趋势接近线性。
很赞哦!(2)
上一篇: 四、一定要仔细阅读细节
下一篇: 四、一定要仔细阅读细节
相关文章
- 2、根据用户基础选择访问提供程序。由于互联问题的存在,接入商的选择也非常重要,如果用户群主要在联通,尽量选择联通接入较好的接入商,如果用户群主要在电信,那么选择电信接入较好的接入商。如果用户组位于国家/地区,则选择更好的访问提供程序进行交互。
- MySQL 全局锁、表级锁、行级锁,你搞清楚了吗?
- 使用 DeepSpeech 在你的应用中实现语音转文字
- 浅析Mysql和Oracle分页的区别
- 4、待所有域名查询结束后可在右侧点击导出结果,即可以excel的文件方式将查询到的结果导出。
- K8s 都开始放弃 Docker了,Containerd 命令走起
- 浅析数据库的历史,你了解了吗?
- 毫秒间查询千亿级Trace数据,SkyWalking上链路追踪这么强?
- 当投资者经过第二阶段的认真学习之后又充满了信心,认为自己可以在市场上叱咤风云地大干一场了。但没想到“看花容易绣花难”,由于对理论知识不会灵活运用.从而失去灵活应变的本能,就经常会出现小赢大亏的局面,结果往往仍以失败告终。这使投资者很是困惑和痛苦,不知该如何办,甚至开始怀疑这个市场是不是不适合自己。在这种情况下,有的人选择了放弃,但有的意志坚定者则决定做最后的尝试。
- MySQL 加行级锁的规则终于被我说清楚了!
站长推荐
便宜域名使用如何?小白可以买到便宜域名吗?
12 个不容错过的 Vue UI 组件库,请查收!
Go:基于 MongoDB 构建 REST API -Fiber 版
手写 JS 引擎来解释一道赋值面试题
3、不明先知,根据相关征兆预测可能发生的事件,以便提前做好准备,赶紧注册相关域名。;不差钱域名;buchaqian抢先注册,就是这种敏感类型。预言是最敏感的状态。其次,你应该有眼力。所谓眼力,就是善于从社会上时不时出现的各种热点事件中获取与事件相关的域名资源。眼力的前提是对域名领域的熟悉和丰富的知识。
Redis通用命令介绍以及key的层级结构讲解
当我准备用SpringEvent优雅的解耦时,连续两个Bug把我搞懵了
Go 语言的 Array 和 Slice