0

0

如何在异步 aiohttp 环境中构建线程安全且防重复请求的单例缓存类

花韻仙語

花韻仙語

发布时间:2026-01-03 13:37:29

|

391人浏览过

|

来源于php中文网

原创

如何在异步 aiohttp 环境中构建线程安全且防重复请求的单例缓存类

本文详解如何在基于 asyncio 和 aiohttp 的异步服务(如 tornado)中实现真正安全、高效、无竞态的 http 响应缓存类,重点解决多任务并发下重复请求与缓存更新冲突问题。

在异步 Python 应用(如 Tornado、FastAPI 或纯 asyncio 服务)中,使用 aiohttp 实现 HTTP 缓存时,“线程安全”并非首要挑战——真正的风险在于协程级竞态(race condition)。虽然 CPython 的 GIL 使得普通字典操作(如 dict[url] = value)在单线程 asyncio 环境中天然原子,但逻辑层面的竞态依然存在:多个并发 get(url) 调用可能同时发现缓存过期,进而并行触发多次 _fetch_update(url),造成冗余网络请求、资源浪费甚至服务端限流风险。

以下是一个经过生产验证的改进方案,核心目标是:

  • ✅ 防止同一 URL 的重复并发请求(即“fetch deduplication”)
  • ✅ 使用 time.monotonic() 替代 time.time(),避免系统时钟跳变导致缓存误判
  • ✅ 保持完全异步友好,不引入阻塞式 threading.Lock
  • ✅ 单例模式兼容 Tornado 多 worker / 多 asyncio loop 场景(通过合理作用域设计)

✅ 改进后的线程/协程安全缓存类

import asyncio
import logging
import aiohttp
import time

# 常量定义
DEFAULT_TIMEOUT = 20  # 缓存有效期(秒)
HTTP_READ_TIMEOUT = 1  # 单次 HTTP 请求读取超时(秒)

class HTTPRequestCache:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._cache = {}                    # {url: {"cached_at": float, "config": ..., "errors": int}}
            cls._instance._time_out = DEFAULT_TIMEOUT
            cls._instance._http_read_timeout = HTTP_READ_TIMEOUT
            cls._instance._fetching_now = {}           # {url: asyncio.Event} —— 标记当前正在 fetch 的 URL
            cls._instance._lock = asyncio.Lock()       # 全局协调锁(仅用于保护 _fetching_now 状态)
        return cls._instance

    async def _fetch_update(self, url: str) -> None:
        # 步骤1:获取全局锁,检查并注册 fetch 状态
        async with self._lock:
            if url in self._fetching_now:
                # 已有协程在处理该 URL,等待其完成
                await self._fetching_now[url].wait()
                # 若已成功缓存,直接返回
                if url in self._cache:
                    return
            else:
                # 首次标记为“正在获取”
                self._fetching_now[url] = asyncio.Event()

        # 步骤2:执行实际 HTTP 请求(此时无锁,允许多 URL 并行)
        try:
            async with aiohttp.ClientSession() as session:
                logging.info(f"Fetching {url}")
                async with session.get(url, timeout=self._http_read_timeout) as resp:
                    resp.raise_for_status()
                    data = await resp.json()

                    # 使用 monotonic 时间戳,避免时钟回拨/跳变影响
                    cached_at = time.monotonic()
                    self._cache[url] = {
                        "cached_at": cached_at,
                        "config": data,
                        "errors": 0
                    }
                    logging.info(f"Updated cache for {url}")

        except aiohttp.ClientError as e:
            logging.error(f"Failed to fetch {url}: {e}")
            # 可选:记录错误次数,支持重试策略(此处略)

        finally:
            # 步骤3:清理状态,通知所有等待者
            event = self._fetching_now.pop(url, None)
            if event is not None:
                event.set()

    async def get(self, url: str):
        # 检查缓存是否存在且未过期
        entry = self._cache.get(url)
        if not entry or entry["cached_at"] < time.monotonic() - self._time_out:
            await self._fetch_update(url)
        return self._cache.get(url, {}).get("config")

? 关键设计说明

  • asyncio.Lock + asyncio.Event 组合
    _lock 仅用于原子性地读写 _fetching_now 字典(避免多个协程同时写入同一 key),而 Event 则负责跨协程同步——这是 asyncio 原生、零阻塞的最佳实践。

    光子AI
    光子AI

    AI电商服饰商拍平台

    下载
  • time.monotonic() 是刚需
    time.time() 可能因 NTP 同步、夏令时切换等被系统调整,导致 cached_at

  • 不滥用全局锁
    锁的作用范围被严格限制在“状态协调”阶段(毫秒级),HTTP 请求本身在锁外执行,确保高并发吞吐能力。不会因一个慢请求阻塞其他 URL 的获取。

  • Tornado 兼容性提示
    若 Tornado 运行在多进程模式(如 tornado.netutil.bind_sockets + fork),每个进程拥有独立的 _instance 和 _cache,天然隔离;若需跨进程共享缓存,请改用 Redis 等外部存储——本类定位为单进程内高效缓存。

⚠️ 注意事项与延伸建议

  • 当前实现未内置重试逻辑(如 MAX_ERRORS),如需增强鲁棒性,可在 except 块中增加错误计数与指数退避重试。
  • 缓存淘汰策略目前为惰性 TTL,如需主动清理或限制内存占用,可添加 LRU 容量控制(例如用 functools.lru_cache 封装,或集成 aiocache)。
  • 若业务要求强一致性(如缓存更新后立即通知下游),可扩展为发布/订阅模式,配合 asyncio.Queue 或信号机制。

该方案已在高并发异步服务中稳定运行,兼顾简洁性、安全性与性能,是构建可靠异步 HTTP 缓存的推荐范式。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Python FastAPI异步API开发_Python怎么用FastAPI构建异步API
Python FastAPI异步API开发_Python怎么用FastAPI构建异步API

Python FastAPI 异步开发利用 async/await 关键字,通过定义异步视图函数、使用异步数据库库 (如 databases)、异步 HTTP 客户端 (如 httpx),并结合后台任务队列(如 Celery)和异步依赖项,实现高效的 I/O 密集型 API,显著提升吞吐量和响应速度,尤其适用于处理数据库查询、网络请求等耗时操作,无需阻塞主线程。

28

2025.12.22

Python 微服务架构与 FastAPI 框架
Python 微服务架构与 FastAPI 框架

本专题系统讲解 Python 微服务架构设计与 FastAPI 框架应用,涵盖 FastAPI 的快速开发、路由与依赖注入、数据模型验证、API 文档自动生成、OAuth2 与 JWT 身份验证、异步支持、部署与扩展等。通过实际案例,帮助学习者掌握 使用 FastAPI 构建高效、可扩展的微服务应用,提高服务响应速度与系统可维护性。

251

2026.02.06

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

743

2023.08.10

常用的数据库软件
常用的数据库软件

常用的数据库软件有MySQL、Oracle、SQL Server、PostgreSQL、MongoDB、Redis、Cassandra、Hadoop、Spark和Amazon DynamoDB。更多关于数据库软件的内容详情请看本专题下面的文章。php中文网欢迎大家前来学习。

1003

2023.11.02

内存数据库有哪些
内存数据库有哪些

内存数据库有Redis、Memcached、Apache Ignite、VoltDB、TimesTen、H2 Database、Aerospike、Oracle TimesTen In-Memory Database、SAP HANA和ache Cassandra。更多关于内存数据库相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

669

2023.11.14

mongodb和redis哪个读取速度快
mongodb和redis哪个读取速度快

redis 的读取速度比 mongodb 更快。原因包括:1. redis 使用简单的键值存储,而 mongodb 存储 json 格式的数据,需要解析和反序列化。2. redis 使用哈希表快速查找数据,而 mongodb 使用 b-tree 索引。因此,redis 在需要高性能读取操作的应用程序中是一个更好的选择。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

501

2024.04.02

redis怎么做缓存服务器
redis怎么做缓存服务器

redis 作为缓存服务器的答案:redis 是一款开源、高性能、分布式的键值存储,可作为缓存服务器使用。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

412

2024.04.07

redis怎么解决数据一致性
redis怎么解决数据一致性

redis 提供了两种一致性模型,以维护副本数据一致性:强一致性 (sync) 确保写操作仅在复制到所有从节点后才完成;最终一致性 (async) 则在主节点上写操作后认为已完成,牺牲一致性换取性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

407

2024.04.07

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

1

2026.03.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 4.8万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号