實戰案例
爲了更好理解,上述所有示例的IO情況都是以 asyncio.sleep
爲例,而真實的項目開發中會用到很多IO的情況
異步 - Reids
當通過python去操作redis時,鏈接、設置值、獲取值 這些都涉及網絡IO請求,使用asycio異步的方式可以在IO等待時去做一些其他任務,從而提升性能。
安裝Python異步操作redis模塊
pip3 install aioredis
示例1:異步操作redis。
import asyncio
import aioredis
async def execute(address, password):
print("開始執行", address)
# 網絡IO操作:創建redis連接
redis = await aioredis.create_redis(address, password=password)
# 網絡IO操作:在redis中設置哈希值car,內部在設三個鍵值對,即: redis = { car:{key1:1,key2:2,key3:3}}
await redis.hmset_dict('car', key1=1, key2=2, key3=3)
# 網絡IO操作:去redis中獲取值
result = await redis.hgetall('car', encoding='utf-8')
print(result)
redis.close()
# 網絡IO操作:關閉redis連接
await redis.wait_closed()
print("結束", address)
asyncio.run(execute('redis://47.93.4.198:6379', "root!2345"))
示例2:連接多個redis做操作(遇到IO會切換其他任務,提供了性能)。
import asyncio
import aioredis
async def execute(address, password):
print("開始執行", address)
# 網絡IO操作:先去連接 47.93.4.197:6379,遇到IO則自動切換任務,去連接47.93.4.198:6379
redis = await aioredis.create_redis_pool(address, password=password)
# 網絡IO操作:遇到IO會自動切換任務
await redis.hmset_dict('car', key1=1, key2=2, key3=3)
# 網絡IO操作:遇到IO會自動切換任務
result = await redis.hgetall('car', encoding='utf-8')
print(result)
redis.close()
# 網絡IO操作:遇到IO會自動切換任務
await redis.wait_closed()
print("結束", address)
task_list = [
execute('redis://47.93.4.197:6379', "root!2345"),
execute('redis://47.93.4.198:6379', "root!2345")
]
asyncio.run(asyncio.wait(task_list))
更多redis操作參考aioredis官網:https://aioredis.readthedocs.io/en/v1.3.0/start.html
異步 - MySQL
當通過python去操作MySQL時,連接、執行SQL、關閉都涉及網絡IO請求,使用asycio異步的方式可以在IO等待時去做一些其他任務,從而提升性能。
安裝Python異步操作redis模塊
pip3 install aiomysql
示例1:
import asyncio
import aiomysql
async def execute():
# 網絡IO操作:連接MySQL
conn = await aiomysql.connect(host='127.0.0.1', port=3306, user='root', password='123', db='mysql', )
# 網絡IO操作:創建CURSOR
cur = await conn.cursor()
# 網絡IO操作:執行SQL
await cur.execute("SELECT Host,User FROM user")
# 網絡IO操作:獲取SQL結果
result = await cur.fetchall()
print(result)
# 網絡IO操作:關閉鏈接
await cur.close()
conn.close()
asyncio.run(execute())
示例2:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import asyncio
import aiomysql
async def execute(host, password):
print("開始", host)
# 網絡IO操作:先去連接 47.93.40.197,遇到IO則自動切換任務,去連接47.93.40.198:6379
conn = await aiomysql.connect(host=host, port=3306, user='root', password=password, db='mysql')
# 網絡IO操作:遇到IO會自動切換任務
cur = await conn.cursor()
# 網絡IO操作:遇到IO會自動切換任務
await cur.execute("SELECT Host,User FROM user")
# 網絡IO操作:遇到IO會自動切換任務
result = await cur.fetchall()
print(result)
# 網絡IO操作:遇到IO會自動切換任務
await cur.close()
conn.close()
print("結束", host)
task_list = [
execute('47.93.40.197', "root!2345"),
execute('47.93.40.197', "root!2345")
]
asyncio.run(asyncio.wait(task_list))
FastAPI框架
FastAPI是一款用於構建API的高性能web框架,框架基於Python3.6+的 type hints
搭建。
接下里的異步示例以FastAPI
和uvicorn
來講解(uvicorn是一個支持異步的asgi)。
安裝FastAPI web 框架,
pip3 install fastapi
安裝uvicorn,本質上爲web提供socket server的支持的asgi(一般支持異步稱asgi、不支持異步稱wsgi)
pip3 install uvicorn
示例:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import asyncio
import uvicorn
import aioredis
from aioredis import Redis
from fastapi import FastAPI
app = FastAPI()
REDIS_POOL = aioredis.ConnectionsPool('redis://47.193.14.198:6379', password="root123", minsize=1, maxsize=10)
@app.get("/")
def index():
""" 普通操作接口 """
return {"message": "Hello World"}
@app.get("/red")
async def red():
""" 異步操作接口 """
print("請求來了")
await asyncio.sleep(3)
# 連接池獲取一個連接
conn = await REDIS_POOL.acquire()
redis = Redis(conn)
# 設置值
await redis.hmset_dict('car', key1=1, key2=2, key3=3)
# 讀取值
result = await redis.hgetall('car', encoding='utf-8')
print(result)
# 連接歸還連接池
REDIS_POOL.release(conn)
return result
if __name__ == '__main__':
uvicorn.run("luffy:app", host="127.0.0.1", port=5000, log_level="info")
在有多個用戶併發請求的情況下,異步方式來編寫的接口可以在IO等待過程中去處理其他的請求,提供性能。
例如:同時有兩個用戶併發來向接口 http://127.0.0.1:5000/red
發送請求,服務端只有一個線程,同一時刻只有一個請求被處理。 異步處理可以提供併發是因爲:當視圖函數在處理第一個請求時,第二個請求此時是等待被處理的狀態,當第一個請求遇到IO等待時,會自動切換去接收並處理第二個請求,當遇到IO時自動化切換至其他請求,一旦有請求IO執行完畢,則會再次回到指定請求向下繼續執行其功能代碼。
爬蟲
在編寫爬蟲應用時,需要通過網絡IO去請求目標數據,這種情況適合使用異步編程來提升性能,接下來我們使用支持異步編程的aiohttp模塊來實現。
安裝aiohttp模塊
pip3 install aiohttp
示例:
import aiohttp
import asyncio
async def fetch(session, url):
print("發送請求:", url)
async with session.get(url, verify_ssl=False) as response:
text = await response.text()
print("得到結果:", url, len(text))
async def main():
async with aiohttp.ClientSession() as session:
url_list = [
'https://python.org',
'https://www.baidu.com',
'https://www.pythonav.com'
]
tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]
await asyncio.wait(tasks)
if __name__ == '__main__':
asyncio.run(main())
總結
爲了提升性能越來越多的框架都在向異步編程靠攏,例如:sanic、tornado、django3.0、django channels組件 等,用更少資源可以做處理更多的事,何樂而不爲呢。