Files
fastapi-best-architecture/backend/common/queue.py
T
Wu Clan 5352f98c13 Optimize database operations within loops (#1177)
* Optimize database operations within loops

* Optimize opera log
2026-05-14 11:38:38 +08:00

86 lines
2.5 KiB
Python

import asyncio
import time
from asyncio import Queue
from collections.abc import Awaitable, Callable
from typing import TypeVar
from backend.common.log import log
from backend.common.observability.prometheus.queue import (
inc_queue_exception,
observe_batch_dequeue_cost,
observe_queue_size,
)
T = TypeVar('T')
async def batch_dequeue(queue: Queue[T], max_items: int, timeout: float, *, queue_name: str = 'default') -> list[T]:
"""
从异步队列中获取多个项目
:param queue: 用于获取项目的 `asyncio.Queue` 队列
:param max_items: 从队列中获取的最大项目数量
:param timeout: 总的等待超时时间(秒)
:param queue_name: 队列名称,用于 Prometheus 标签
:return:
"""
items = []
start = time.perf_counter()
async def collector() -> None:
while len(items) < max_items:
item = await queue.get()
items.append(item)
observe_queue_size(queue, queue_name=queue_name)
try:
await asyncio.wait_for(collector(), timeout=timeout)
except asyncio.TimeoutError:
pass
except Exception as e:
inc_queue_exception(queue_name=queue_name)
log.error(f'队列批量获取失败: {e}')
finally:
observe_batch_dequeue_cost(start, queue_name=queue_name)
observe_queue_size(queue, queue_name=queue_name)
return items
async def batch_consume(
queue: Queue[T],
max_items: int,
timeout: float,
handler: Callable[..., Awaitable[None]],
*,
queue_name: str = 'default',
error_message: str = '队列批量处理失败',
item_name: str = '数据',
) -> None:
"""
持续批量消费队列
:param queue: 用于获取项目的 `asyncio.Queue` 队列
:param max_items: 从队列中获取的最大项目数量
:param timeout: 总的等待超时时间(秒)
:param handler: 批量处理函数
:param queue_name: 队列名称,用于 Prometheus 标签
:param error_message: 处理失败日志消息
:param item_name: 队列数据名称
:return:
"""
while True:
items = await batch_dequeue(queue, max_items=max_items, timeout=timeout, queue_name=queue_name)
if not items:
continue
try:
await handler(items)
except Exception as e:
log.error(f'{error_message},丢失 {len(items)}{item_name}: {e}')
finally:
for _ in items:
queue.task_done()
observe_queue_size(queue, queue_name=queue_name)