工程实践:用Asyncio协程构建高并发应用

架构 2023-07-05 17:29:38
33阅读

文中转载微信公众平台「凉拌菜学习编程」,创作者 fasionchan。转截文中请联络凉拌菜学习编程微信公众号。

C10K难题

在互联网技术并未普及化的初期,一台网络服务器线上PK 100 个客户早已算作十分大中型的运用了,工程项目上没什么挑戰。

伴随着 Web 2.0 时期的来临,客户人群成几何倍数提高,网络服务器必须更强的高并发解决工作能力才可以安装大量的客户。这时候,知名的 C10K 难题问世了——怎么让每台网络服务器另外支撑点 1 万只手机客户端联接?

最开始的网络服务器运用程序编写实体模型,是根据过程/进程的:当一个新的手机客户端联接上去,网络服务器就分派一个过程或进程,来解决这一新联接。这代表着,要想处理 C10K 难题,电脑操作系统必须另外运作 1 万只过程或进程。

进程和线程是电脑操作系统中,花销较大 的資源之一。每一个新联接都新开业过程/进程,将导致巨大的資源消耗。更何况,受硬件平台牵制,系统软件同一时间能运作的过程/线程数存有限制。

换句话说讲,在过程/进程实体模型中,每台网络服务器能解决的手机客户端线程数是十分比较有限的。为适用大量的业务流程,只有根据堆网络服务器这类简单直接的方法来完成。但那样的地毯式轰炸,既不稳定,都不经济发展。

为了更好地在单独过程/进程中另外解决好几个数据连接,select 、 poll 、epoll 等 IO时分复用 技术性应时而生。在IO时分复用实体模型,过程/进程不会再堵塞在某一联接上,只是另外监管好几个联接,只解决这些有新数据做到的活跃性联接。

为何必须协同程序

单纯性的IO时分复用程序编写实体模型,并不像堵塞式程序编写实体模型那般形象化,这为建筑项目产生许多麻烦。最典型性的像 JavaScript 中的回调式程序编写实体模型,程序流程中各种各样 callback 涵数满天飞舞,这不是一种形象化的思维模式。

为完成堵塞式那般形象化的程序编写实体模型,协同程序(客户态进程)的定义被明确提出来。协同程序在过程/进程基本以上,完成好几个实行前后文。由 epoll 等IO时分复用技术性完成的事情循环系统,则承担驱动器协同程序的生产调度、实行。

协同程序能够看作是IO时分复用技术性更高端的封裝。尽管与初始IO时分复用对比有一定的特性花销,但与过程/进程实体模型对比却十分突显。协同程序占有資源比过程/进程少,并且转换成本费较为低。因而,协同程序在分布式系统主要用途前途无量。

殊不知,协同程序与众不同的管理机制,让新手吃完许多 亏,疏漏层出不穷。

下面,大家根据多个简易事例,探寻协同程序运用之道,从这当中感受协同程序的功效,并揭露分布式系统运用设计方案、布署中存有的普遍错误观念。因为 asyncio 是 Python 协同程序发展趋势的关键发展趋势,事例便以 asyncio 为解读目标。

第一个协同程序运用

协同程序运用由事情循环系统驱动器,tcp协议务必是是非非堵塞方式,不然会堵塞事情循环系统。因而,一旦应用协同程序,就需要跟许多类库说拜拜了。以 MySQL 数据库操作为例子,如果我们应用 asyncio ,就需要用 aiomysql 包来连数据库查询。

而要想开发设计 Web 运用,则可以用 aiohttp 包,它能够根据 pip 指令安裝:

 
  1. $ pip install aiohttp 

这一事例完成一个详细 Web 网络服务器,尽管它仅有回到获取当前时间的作用:

 
  1. from aiohttp import web 
  2. from datetime import datetime 
  3.  
  4. async def handle(request): 
  5.     return web.Response(text=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) 
  6.  
  7. app = web.Application() 
  8. app.add_routes([ 
  9.     web.get('/', handle), 
  10. ]) 
  11.  
  12. if __name__ == '__main__'
  13.     web.run_app(app) 

第 4 行,完成处理函数,获得获取当前时间并回到;

第 7 行,建立运用目标,并将处理函数申请注册到路由器中;

第 13 行,将 Web 运用跑起来,默认设置端口号是 8080 ;

当一个新的要求抵达时,aiohttp 将建立一个新协同程序来解决该要求,它将承担实行相匹配的处理函数。因而,处理函数务必是合理合法的协同程序涵数,以 async 关键词开始。

将程序流程跑起来后,大家就可以根据它获知获取当前时间。在cmd中,可以用 curl 指令来进行要求:

 
  1. $ curl http://127.0.0.1:8080/ 
  2. 2020-08-06 15:50:34 

稳定性测试

产品研发分布式系统运用,必须评定运用的解决工作能力。我们可以在短期内内进行很多的要求,并计算运用的吞吐量。殊不知,即使你手快一点,一秒钟也只有进行数个要求呀。怎么办呢?

大家必须依靠一些工作压力检测工具,比如 Apache 专用工具集中化的 ab 。如何安装应用 ab 没有文中的探讨范畴,请参照本文:Web稳定性测试(https://network.fasionchan.com/zh_CN/latest/performance/web-pressure-test.html) 。

事不宜迟,大家先以 100 为并发数,压 10000 个要求看一下結果:

 
  1. $ ab -n 10000 -c 100 http://127.0.0.1:8080/ 
  2. This is ApacheBench, Version 2.3 <$Revision: 1706008 $> 
  3. Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 
  4. Licensed to The Apache Software Foundation, http://www.apache.org/ 
  5.  
  6. Benchmarking 127.0.0.1 (be patient) 
  7. Completed 1000 requests 
  8. Completed 2000 requests 
  9. Completed 3000 requests 
  10. Completed 4000 requests 
  11. Completed 5000 requests 
  12. Completed 6000 requests 
  13. Completed 7000 requests 
  14. Completed 8000 requests 
  15. Completed 9000 requests 
  16. Completed 10000 requests 
  17. Finished 10000 requests 
  18.  
  19.  
  20. Server Software:        Python/3.8 
  21. Server Hostname:        127.0.0.1 
  22. Server Port:            8080 
  23.  
  24. Document Path:          / 
  25. Document Length:        19 bytes 
  26.  
  27. Concurrency Level:      100 
  28. Time taken for tests:   5.972 seconds 
  29. Complete requests:      10000 
  30. Failed requests:        0 
  31. Total transferred:      1700000 bytes 
  32. HTML transferred:       190000 bytes 
  33. Requests per second:    1674.43 [#/sec] (mean) 
  34. Time per request:       59.722 [ms] (mean) 
  35. Time per request:       0.597 [ms] (mean, across all concurrent requests) 
  36. Transfer rate:          277.98 [Kbytes/sec] received 
  37.  
  38. Connection Times (ms) 
  39.               min  mean[ /-sd] median   max 
  40. Connect:        0    2   1.5      1      15 
  41. Processing:    43   58   5.0     57      89 
  42. Waiting:       29   47   6.3     47      85 
  43. Total:         43   60   4.8     58      90 
  44.  
  45. Percentage of the requests served within a certain time (ms) 
  46.   50%     58 
  47.   66%     59 
  48.   75%     60 
  49.   80%     61 
  50.   90%     65 
  51.   95%     69 
  52.   98%     72 
  53.   99%     85 
  54.  100%     90 (longest request) 

-n 选择项,特定总要求数,即一共发多少个要求;

-c 选择项,特定并发数,即另外发多少个要求;

从 ab 輸出的汇报中能够获知,10000 个要求所有取得成功,一共用时 5.972 秒,响应速度能够做到 1674.43 个每秒钟。

如今,大家试着出示并发数,看响应速度是否有提高:

 
  1. $ ab -n 10000 -c 100 http://127.0.0.1:8080/ 

在 1000 并发数下,10000 个要求在 5.771 秒内进行,响应速度是 1732.87 ,略微提高但很不显著。这一点也不出现意外,事例中的解决逻辑性绝大多数全是测算型,虚报并发数基本上沒有一切实际意义。

协同程序善于干什么

协同程序善于解决 IO 型的应用逻辑,举个事例,当某一协同程序等待数据库查询回应时,事情循环系统将唤起另一个准备就绪协同程序来实行,为此提升 吞吐。为减少多元性,大家根据在程序流程中睡眠质量来仿真模拟等候数据库查询的实际效果。

 
  1. import asyncio 
  2.  
  3. from aiohttp import web 
  4. from datetime import datetime 
  5.  
  6. async def handle(request): 
  7.     # 睡眠质量一秒钟 
  8.     asyncio.sleep(1) 
  9.     return web.Response(text=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) 
  10.  
  11. app = web.Application() 
  12. app.add_routes([ 
  13.     web.get('/', handle), 
  14. ]) 
  15.  
  16. if __name__ == '__main__'
  17.     web.run_app(app) 
并发数 要求数量 用时(秒) 响应速度(要求/秒)
100 10000 102.310 97.74
500 10000 22.129 451.89
1000 10000 12.780 782.50

能够见到,伴随着并发数的提升,响应速度也是有显著的提高,发展趋势贴近线形。

the end
免责声明:本文不代表本站的观点和立场,如有侵权请联系本站删除!本站仅提供信息存储空间服务。