異步編程 - 實戰案例

實戰案例

爲了更好理解,上述所有示例的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搭建。

接下里的異步示例以FastAPIuvicorn來講解(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組件 等,用更少資源可以做處理更多的事,何樂而不爲呢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章