自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

工程實(shí)踐:用Asyncio協(xié)程構(gòu)建高并發(fā)應(yīng)用

開發(fā) 架構(gòu)
在互聯(lián)網(wǎng)尚未普及的早期,一臺服務(wù)器同時(shí)在線 100 個(gè)用戶已經(jīng)算是非常大型的應(yīng)用了,工程上沒有什么挑戰(zhàn)。

[[399386]]

本文轉(zhuǎn)載自微信公眾號「小菜學(xué)編程」,作者 fasionchan。轉(zhuǎn)載本文請聯(lián)系小菜學(xué)編程公眾號。

C10K問題

在互聯(lián)網(wǎng)尚未普及的早期,一臺服務(wù)器同時(shí)在線 100 個(gè)用戶已經(jīng)算是非常大型的應(yīng)用了,工程上沒有什么挑戰(zhàn)。

隨著 Web 2.0 時(shí)代的到來,用戶群體成幾何倍數(shù)增長,服務(wù)器需要更強(qiáng)的并發(fā)處理能力才能承載海量的用戶。這時(shí),著名的 C10K 問題誕生了——如何讓單臺服務(wù)器同時(shí)支撐 1 萬個(gè)客戶端連接?

最初的服務(wù)器應(yīng)用編程模型,是基于進(jìn)程/線程的:當(dāng)一個(gè)新的客戶端連接上來,服務(wù)器就分配一個(gè)進(jìn)程或線程,來處理這個(gè)新連接。這意味著,想要解決 C10K 問題,操作系統(tǒng)需要同時(shí)運(yùn)行 1 萬個(gè)進(jìn)程或線程。

進(jìn)程和線程是操作系統(tǒng)中,開銷最大的資源之一。每個(gè)新連接都新開進(jìn)程/線程,將造成極大的資源浪費(fèi)。況且,受硬件資源制約,系統(tǒng)同一時(shí)間能運(yùn)行的進(jìn)程/線程數(shù)存在上限。

換句話講,在進(jìn)程/線程模型中,每臺服務(wù)器能處理的客戶端連接數(shù)是非常有限的。為支持海量的業(yè)務(wù),只能通過堆服務(wù)器這種簡單粗暴的方式來實(shí)現(xiàn)。但這樣的人海戰(zhàn)術(shù),既不穩(wěn)定,也不經(jīng)濟(jì)。

為了在單個(gè)進(jìn)程/線程中同時(shí)處理多個(gè)網(wǎng)絡(luò)連接,select 、 poll 、epoll 等 IO多路復(fù)用 技術(shù)應(yīng)運(yùn)而生。在IO多路復(fù)用模型,進(jìn)程/線程不再阻塞在某個(gè)連接上,而是同時(shí)監(jiān)控多個(gè)連接,只處理那些有新數(shù)據(jù)達(dá)到的活躍連接。

為什么需要協(xié)程

單純的IO多路復(fù)用編程模型,不像阻塞式編程模型那樣直觀,這為工程項(xiàng)目帶來諸多不便。最典型的像 JavaScript 中的回調(diào)式編程模型,程序中各種 callback 函數(shù)滿天飛,這不是一種直觀的思維方式。

為實(shí)現(xiàn)阻塞式那樣直觀的編程模型,協(xié)程(用戶態(tài)線程)的概念被提出來。協(xié)程在進(jìn)程/線程基礎(chǔ)之上,實(shí)現(xiàn)多個(gè)執(zhí)行上下文。由 epoll 等IO多路復(fù)用技術(shù)實(shí)現(xiàn)的事件循環(huán),則負(fù)責(zé)驅(qū)動協(xié)程的調(diào)度、執(zhí)行。

協(xié)程可以看做是IO多路復(fù)用技術(shù)更高層次的封裝。雖然與原始IO多路復(fù)用相比有一定的性能開銷,但與進(jìn)程/線程模型相比卻非常突出。協(xié)程占用資源比進(jìn)程/線程少,而且切換成本比較低。因此,協(xié)程在高并發(fā)應(yīng)用領(lǐng)域潛力無限。

然而,協(xié)程獨(dú)特的運(yùn)行機(jī)制,讓初學(xué)者吃了不少虧,錯(cuò)漏百出。

接下來,我們通過若干簡單例子,探索協(xié)程應(yīng)用之道,從中體會協(xié)程的作用,并揭示高并發(fā)應(yīng)用設(shè)計(jì)、部署中存在的常見誤區(qū)。由于 asyncio 是 Python 協(xié)程發(fā)展的主要趨勢,例子便以 asyncio 為講解對象。

第一個(gè)協(xié)程應(yīng)用

協(xié)程應(yīng)用由事件循環(huán)驅(qū)動,套接字必須是非阻塞模式,否則會阻塞事件循環(huán)。因此,一旦使用協(xié)程,就要跟很多類庫說拜拜了。以 MySQL 數(shù)據(jù)庫操作為例,如果我們使用 asyncio ,就要用 aiomysql 包來連數(shù)據(jù)庫。

而想要開發(fā) Web 應(yīng)用,則可以用 aiohttp 包,它可以通過 pip 命令安裝:

  1. $ pip install aiohttp 

這個(gè)例子實(shí)現(xiàn)一個(gè)完整 Web 服務(wù)器,雖然它只有返回當(dāng)前時(shí)間的功能:

  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 行,實(shí)現(xiàn)處理函數(shù),獲取當(dāng)前時(shí)間并返回;

第 7 行,創(chuàng)建應(yīng)用對象,并將處理函數(shù)注冊到路由中;

第 13 行,將 Web 應(yīng)用跑起來,默認(rèn)端口是 8080 ;

當(dāng)一個(gè)新的請求到達(dá)時(shí),aiohttp 將創(chuàng)建一個(gè)新協(xié)程來處理該請求,它將負(fù)責(zé)執(zhí)行對應(yīng)的處理函數(shù)。因此,處理函數(shù)必須是合法的協(xié)程函數(shù),以 async 關(guān)鍵字開頭。

將程序跑起來后,我們就可以通過它獲悉當(dāng)前時(shí)間。在命令行中,可以用 curl 命令來發(fā)起請求:

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

壓力測試

研發(fā)高并發(fā)應(yīng)用,需要評估應(yīng)用的處理能力。我們可以在短時(shí)間內(nèi)發(fā)起大量的請求,并測算應(yīng)用的吞吐能力。然而,就算你手再快,一秒鐘也只能發(fā)起若干個(gè)請求呀。怎么辦呢?

我們需要借助一些壓力測試工具,例如 Apache 工具集中的 ab 。如何安裝使用 ab 不在本文的討論范圍,請參考這篇文章:Web壓力測試(https://network.fasionchan.com/zh_CN/latest/performance/web-pressure-test.html) 。

事不宜遲,我們先以 100 為并發(fā)數(shù),壓 10000 個(gè)請求看看結(jié)果:

  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 選項(xiàng),指定總請求數(shù),即總共發(fā)多少個(gè)請求;

-c 選項(xiàng),指定并發(fā)數(shù),即同時(shí)發(fā)多少個(gè)請求;

從 ab 輸出的報(bào)告中可以獲悉,10000 個(gè)請求全部成功,總共耗時(shí) 5.972 秒,處理速度可以達(dá)到 1674.43 個(gè)每秒。

現(xiàn)在,我們嘗試提供并發(fā)數(shù),看處理速度有沒有提升:

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

在 1000 并發(fā)數(shù)下,10000 個(gè)請求在 5.771 秒內(nèi)完成,處理速度是 1732.87 ,略有提升但很不明顯。這一點(diǎn)也不意外,例子中的處理邏輯絕大部分都是計(jì)算型,虛增并發(fā)數(shù)幾乎沒有任何意義。

協(xié)程擅長做什么

協(xié)程擅長處理 IO 型的應(yīng)用邏輯,舉個(gè)例子,當(dāng)某個(gè)協(xié)程在等待數(shù)據(jù)庫響應(yīng)時(shí),事件循環(huán)將喚醒另一個(gè)就緒協(xié)程來執(zhí)行,以此提高吞吐。為降低復(fù)雜性,我們通過在程序中睡眠來模擬等待數(shù)據(jù)庫的效果。

  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) 
并發(fā)數(shù) 請求總數(shù) 耗時(shí)(秒) 處理速度(請求/秒)
100 10000 102.310 97.74
500 10000 22.129 451.89
1000 10000 12.780 782.50

可以看到,隨著并發(fā)數(shù)的增加,處理速度也有明顯的提升,趨勢接近線性。

 

責(zé)任編輯:武曉燕 來源: 小菜學(xué)編程
相關(guān)推薦

2021-08-01 15:26:59

協(xié)程Asyncio并發(fā)數(shù)

2016-10-28 17:39:47

phpgolangcoroutine

2024-06-27 07:56:49

2024-12-23 08:00:45

2020-11-30 08:25:41

程序員高并發(fā)協(xié)程

2023-11-24 11:15:21

協(xié)程編程

2023-10-12 09:46:00

并發(fā)模型線程

2022-10-28 10:45:22

Go協(xié)程GoFrame

2021-09-16 09:59:13

PythonJavaScript代碼

2023-07-27 13:46:10

go開源項(xiàng)目

2024-02-05 09:06:25

Python協(xié)程Asyncio庫

2021-09-27 23:28:29

Go多協(xié)程并發(fā)

2021-06-03 14:08:03

開發(fā)技能代碼

2021-06-04 14:28:07

協(xié)程線程Android開發(fā)

2023-11-17 11:36:59

協(xié)程纖程操作系統(tǒng)

2023-11-06 14:13:51

asyncio開發(fā)

2022-07-15 12:58:02

鴻蒙攜程華為

2023-07-13 08:06:05

應(yīng)用協(xié)程阻塞

2021-10-28 09:36:12

高并發(fā)數(shù)據(jù)實(shí)踐

2016-12-28 14:16:25

京東高并發(fā)系統(tǒng)設(shè)計(jì)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號