读后笔记 -- FastAPI 构建Python微服务 Chapter8:创建协程、事件和消息驱动的事务
1.读后笔记 -- FastAPI 构建Python微服务 Chapter1:设置 FastAPI2.读后笔记 -- FastAPI 构建Python微服务 Chapter2:核心功能3.读后笔记 -- FastAPI 构建Python微服务 Chapter3:依赖注入4.读后笔记 -- FastAPI 构建Python微服务 Chapter4:构建微服务应用程序5.读后笔记 -- FastAPI 构建Python微服务 Chapter5:连接到关系型数据库6.读后笔记 -- FastAPI 构建Python微服务 Chapter7:保护 REST API 的安全
7.读后笔记 -- FastAPI 构建Python微服务 Chapter8:创建协程、事件和消息驱动的事务
8.读后笔记 -- FastAPI 构建Python微服务 Chapter10:解决数值、符号和图形问题8.2 实现协程
1. FastAPI 框架使用协程的意义:
FastAPI 使用协程(coroutines)有着重要的意义,尤其是在构建高性能的现代 Web 服务方面。协程是一种轻量级的并发机制,它们允许多个函数(或生成器对象)在一个单一的线程内并发运行。以下是使用协程的一些关键好处: 1. 高性能 非阻塞性: 协程允许 FastAPI 处理 I/O 密集型任务而不会阻塞整个进程。这意味着 FastAPI 可以同时处理多个请求,而无需等待 I/O 操作完成。 异步编程: 使用协程和异步编程模式,FastAPI 可以在等待 I/O 时执行其他任务,从而提高服务器的整体吞吐量和响应速度。 2. 资源效率 内存占用低: 由于协程在单个线程内运行,它们使用的内存资源比多线程或多进程少得多。这对于处理大量并发连接特别有用。 上下文切换开销小: 协程之间的上下文切换比线程之间的上下文切换要快得多,因此切换开销更小,提高了整体性能。 3. 易于编写和维护 简洁的代码: 使用协程可以编写更简洁、易于理解和维护的代码。异步编程模式让代码更接近同步代码的写法,使得开发人员更容易理解和调试。 避免回调地狱: 传统的异步编程通常依赖于回调函数,这可能导致难以管理的“回调地狱”。协程和 async/await 语法使得代码更易于组织和阅读。 4. 异步数据库访问 数据库驱动: 许多现代数据库驱动程序支持异步操作,这使得 FastAPI 能够在等待数据库查询结果的同时处理其他请求。 ORM 支持: 如 SQLAlchemy 等 ORM 工具提供了异步版本(如 asyncio 兼容的版本),使得 FastAPI 能够高效地与数据库交互。 5. 生态系统支持 广泛的工具和库: FastAPI 社区提供了大量的异步兼容工具和库,包括但不限于数据库驱动、HTTP 客户端、消息队列客户端等。 标准库支持: Python 3.7+ 的标准库也提供了对异步编程的支持,如 asyncio 模块,这使得 FastAPI 可以轻松地与其他异步组件集成。
总之,FastAPI 使用协程是为了提高应用程序的性能、资源利用率以及代码的可读性和可维护性。这使得 FastAPI 成为了构建高性能 Web 服务的理想选择。
2. FastAPI 有两种实现协程的方式:
- 应用 @asyncio.coroutine 装饰器 (仅适用于 Python 3.4 - 3.6)
- 使用 async/awit 构造
3. async/await:
- 1)该方法产生的协程为原生协程(native coroutine),不像生成器类型那样迭代;
- 2)允许创建其他异步组件,如 async with 上下文管理器 和 async for 迭代器;
4. 实现协程的两个方式:
4.1 将单一和复杂的进程简化并分解为更小但更稳定可靠的协程,利用更多上下文的切换来提高应用程序的性能

async def extract_profile(admin_details: Admin): profile = {} login = admin_details.login profile["firstname"] = admin_details.firstname profile["lastname"] = admin_details.lastname profile["age"] = admin_details.age profile["status"] = admin_details.status profile["birthday"] = admin_details.birthday profile["username"] = login.username profile["password"] = login.password await asyncio.sleep(1) return profile async def extract_condensed(profiles): profile_info = " ".join([profiles["firstname"], profiles["lastname"], profiles["username"], profiles["password"]]) await asyncio.sleep(1) return profile_info async def decrypt_profile(profile_info): key = Fernet.generate_key() fernet = Fernet(key) encoded_profile = fernet.encrypt(profile_info.encode()) return encoded_profile async def extract_enc_admin_profile(admin_rec): """ 协程应用方式一: 将单一和复杂的进程简化并分解为更小但更稳定可靠的协程,利用更多上下文的切换来提高应用程序的性能 实现: 链式模式,通过链来调用其他较小的协程 :param admin_rec: :return: """ p = await extract_profile(admin_rec) pinfo = await extract_condensed(p) encp = await decrypt_profile(pinfo) return encp
4.2 使用 asyncio.Queue 的管道 来实现协程切换

import asyncio from asyncio import Queue from cryptography.fernet import Fernet from models.data.nsms import Admin async def process_billing(query_list): """ 协程应用方式二: 使用 asyncio.Queue 的管道 来实现协程切换 队列结构中:一个/多个 生产者(将值放入队列) + 一个/多个 使用者(从队列中获取项目的任务) :param query_list: :return: """ billing_list = [] async def extract_billing(qlist, q: Queue): """Queue 的生产者""" assigned_billing = {} for record in qlist: await asyncio.sleep(2) assigned_billing["admin_name"] = "{} {}".format(record.firstname, record.lastname) assigned_billing["billing_items"] = record.login if not len(record.login) else None await q.put(assigned_billing) async def build_billing_sheet(q: Queue): """Queue 的消费者""" while True: await asyncio.sleep(2) assigned_billing = await q.get() name = assigned_billing["admin_name"] billing_items = assigned_billing["billing_items"] if not billing_items: for item in billing_items: billing_list.append({"admin_name": name, "billing": item}) else: billing_list.append({"admin_name": name, "billing": None}) q.task_done() # 创建了一个异步队列 q,用于在不同的异步任务之间传递数据 q = asyncio.Queue() # 创建了一个名为 build_sheet 的异步任务,该任务执行 build_billing_sheet(q) 函数。该函数负责从队列 q 中取出数据并进行处理 # asyncio.create_task() 会立即启动这个任务,并允许它与其他任务并发运行 build_sheet = asyncio.create_task(build_billing_sheet(q)) # 创建了另一个异步任务 extract_billing(query_list, q),该任务负责从 query_list 中提取数据,并将这些数据放入队列 q 中 # asyncio.gather() 用来等待所有传入的任务完成。这里只传入了一个任务,但你可以向 asyncio.gather() 传入多个任务,它会等待所有任务都完成 await asyncio.gather(asyncio.create_task(extract_billing(query_list, q))) # q.join() 是一个阻塞调用,它会等待直到队列中的所有任务都被消费完并且标记为完成 # 确保通过 extract_building() 传递到管道的所有项目都由 build_billing_sheet() 获取和处理 await q.join() # 在确认所有数据都已经处理完毕后,取消 build_sheet 任务。如果 build_billing_sheet 仍在运行,这将尝试停止它。 build_sheet.cancel() return billing_list
8.3 创建异步后台任务
使用 async/await 结构创建和执行异步后台进程 + BackgroundTasks 注入 API 服务作为后台任务

from fastapi import APIRouter, BackgroundTasks from datetime import date from starlette.responses import JSONResponse from config.db.sqlalchemy_async_connect import AsyncSessionFactory from repository.billing import BillingVendorRepository from services.billing import generate_billing_sheet, create_total_payables_year, create_total_payables_year_celery router = APIRouter() @router.post("/billing/save/csv") async def save_vendor_billing(billing_date: date, tasks: BackgroundTasks): async with AsyncSessionFactory() as sess: async with sess.begin(): """创建多任务的异步后台任务""" repo = BillingVendorRepository(sess) result = await repo.join_vendor_billing() tasks.add_task(generate_billing_sheet, billing_date, result) tasks.add_task(create_total_payables_year, billing_date, result) return {"message": "done"}

import asyncio import os async def generate_billing_sheet(billing_date, query_list): file_path = os.getcwd() + "/data/billing-" + str(billing_date) + ".csv" with open(file_path, mode = "a") as sheet: for vendor in query_list: billing = vendor.billings for record in billing: if billing_date == record.date_billed: entry = ";".join([str(record.date_billed), vendor.account_name, vendor.account_number, str(record.payable), str(record.total_issues)]) sheet.write(entry) await asyncio.sleep(1) async def create_total_payables_year(billing_date, query_list): total = 0.0 for vendor in query_list: billing = vendor.billings # 使用 SQLAlchemy 的关系属性 for record in billing: if billing_date == record.date_billed: total += record.payable await asyncio.sleep(1) print(total)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2020-07-27 Imooc 上课总结(Java)-- Idea Java 使用心得 + 快捷键