""" 异步 Redis 缓存封装 —— 100% 异步化基建 ======================================== Django 的 cache 框架(django-redis)没有异步 API,本模块基于 redis.asyncio 直连, 复用 settings 中的 REDIS 配置,提供与 django cache 对等的异步接口。 用法: from utils.async_cache import aget_cache, aset_cache, adelete_cache value = await aget_cache('key') await aset_cache('key', obj, timeout=3600) await adelete_cache('key') 序列化:msgpack(快速紧凑)+ JSON 兜底;与 django-redis 缓存池互不干扰(独立 key 前缀)。 """ import asyncio import json import logging from django.conf import settings from django.core.cache import caches from asgiref.sync import sync_to_async import msgpack logger = logging.getLogger(__name__) _cache_pool = None _lock = asyncio.Lock() async def _get_pool(): """懒初始化 redis.asyncio 连接池(复用 settings.REDIS 配置)""" global _cache_pool async with _lock: if _cache_pool is None: import redis.asyncio as aioredis _cache_pool = aioredis.Redis( host=settings.REDIS_HOST, port=int(settings.REDIS_PORT), db=int(getattr(settings, 'REDIS_DB', 0)), password=getattr(settings, 'REDIS_PASSWORD', '') or None, decode_responses=False, socket_connect_timeout=5, socket_timeout=5, max_connections=50, ) return _cache_pool def _pack(value): try: return b'async-cache:' + msgpack.packb(value, use_bin_type=True, default=str) except (TypeError, ValueError): return b'async-cache-json:' + json.dumps(value, ensure_ascii=False, default=str).encode() def _unpack(raw): if raw is None: return None if raw.startswith(b'async-cache-json:'): return json.loads(raw[17:].decode()) return msgpack.unpackb(raw[12:], raw=False) async def aget_cache(key, default=None): try: pool = await _get_pool() raw = await pool.get(f'async:{key}') if raw is None: return default return _unpack(raw) except Exception: logger.warning('aget_cache failed for %s, falling back to sync cache', key) try: return await sync_to_async(caches['default'].get)(key, default) except Exception: return default async def aset_cache(key, value, timeout=300): try: pool = await _get_pool() await pool.set(f'async:{key}', _pack(value), ex=timeout) return True except Exception: logger.warning('aset_cache failed for %s, falling back to sync cache', key) try: await sync_to_async(caches['default'].set)(key, value, timeout) return True except Exception: return False async def adelete_cache(key): try: pool = await _get_pool() await pool.delete(f'async:{key}') return True except Exception: logger.warning('adelete_cache failed for %s', key) try: await sync_to_async(caches['default'].delete)(key) return True except Exception: return False async def aget_or_set(key, factory, timeout=300): """原子性不保证(与 django cache 用法一致),value 为 await factory() 结果""" v = await aget_cache(key) if v is not None: return v v = await factory() if asyncio.iscoroutinefunction(factory) else factory() await aset_cache(key, v, timeout) return v