2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
内存缓存插件
|
|
|
|
|
|
基于Python字典的简单内存缓存实现
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-01-30 14:30:57 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
import asyncio
|
|
|
|
|
|
import time
|
|
|
|
|
|
from collections import OrderedDict
|
2026-01-30 03:10:21 +08:00
|
|
|
|
from typing import Any
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
from .base import CachePlugin
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MemoryCachePlugin(CachePlugin):
|
|
|
|
|
|
"""
|
|
|
|
|
|
内存缓存插件
|
|
|
|
|
|
使用OrderedDict实现LRU缓存
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-01-30 14:30:57 +08:00
|
|
|
|
def __init__(self, name: str = "memory", config: dict[str, Any] | None = None):
|
2025-12-10 20:52:44 +08:00
|
|
|
|
super().__init__(name, config)
|
|
|
|
|
|
self._cache: OrderedDict = OrderedDict()
|
2026-01-30 03:10:21 +08:00
|
|
|
|
self._expiry: dict[str, float] = {}
|
2026-02-14 16:34:52 +08:00
|
|
|
|
self._lock = asyncio.Lock()
|
2025-12-10 20:52:44 +08:00
|
|
|
|
self._hits = 0
|
|
|
|
|
|
self._misses = 0
|
|
|
|
|
|
self._evictions = 0
|
|
|
|
|
|
self._cleanup_task = None
|
|
|
|
|
|
self._cleanup_interval = 60 # 默认值
|
|
|
|
|
|
|
|
|
|
|
|
# 启动清理任务
|
|
|
|
|
|
if config is not None:
|
|
|
|
|
|
self._cleanup_interval = config.get("cleanup_interval", 60)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._start_cleanup_task()
|
2026-03-10 14:42:33 +08:00
|
|
|
|
except Exception:
|
|
|
|
|
|
pass # 事件循环尚未启动,将在首次 set 时延迟启动
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
2026-01-30 14:30:57 +08:00
|
|
|
|
def _start_cleanup_task(self) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""启动后台清理任务"""
|
|
|
|
|
|
|
2026-01-30 14:30:57 +08:00
|
|
|
|
async def cleanup_loop() -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
while self.enabled:
|
|
|
|
|
|
await asyncio.sleep(self._cleanup_interval)
|
|
|
|
|
|
await self._cleanup_expired()
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
try:
|
|
|
|
|
|
loop = asyncio.get_running_loop()
|
|
|
|
|
|
self._cleanup_task = loop.create_task(cleanup_loop())
|
|
|
|
|
|
except RuntimeError:
|
|
|
|
|
|
# 没有运行的事件循环,稍后再启动
|
|
|
|
|
|
pass
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
2026-01-30 14:30:57 +08:00
|
|
|
|
async def _cleanup_expired(self) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""清理过期的缓存项"""
|
|
|
|
|
|
now = time.time()
|
|
|
|
|
|
expired_keys = []
|
|
|
|
|
|
|
2026-02-14 16:34:52 +08:00
|
|
|
|
async with self._lock:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
for key, expiry in self._expiry.items():
|
|
|
|
|
|
if expiry < now:
|
|
|
|
|
|
expired_keys.append(key)
|
|
|
|
|
|
|
|
|
|
|
|
for key in expired_keys:
|
|
|
|
|
|
self._cache.pop(key, None)
|
|
|
|
|
|
self._expiry.pop(key, None)
|
|
|
|
|
|
self._evictions += 1
|
|
|
|
|
|
|
2026-01-30 14:30:57 +08:00
|
|
|
|
def _check_size(self) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""检查并维护缓存大小限制"""
|
|
|
|
|
|
if len(self._cache) >= self.max_size:
|
|
|
|
|
|
# 删除最老的项(LRU)
|
|
|
|
|
|
key = next(iter(self._cache))
|
|
|
|
|
|
self._cache.pop(key)
|
|
|
|
|
|
self._expiry.pop(key, None)
|
|
|
|
|
|
self._evictions += 1
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
async def get(self, key: str) -> Any | None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""获取缓存值"""
|
2026-02-14 16:34:52 +08:00
|
|
|
|
async with self._lock:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
# 检查是否过期
|
|
|
|
|
|
if key in self._expiry:
|
|
|
|
|
|
if self._expiry[key] < time.time():
|
|
|
|
|
|
# 已过期,删除
|
|
|
|
|
|
self._cache.pop(key, None)
|
|
|
|
|
|
self._expiry.pop(key)
|
|
|
|
|
|
self._misses += 1
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
# 获取值并更新访问顺序(LRU)
|
|
|
|
|
|
if key in self._cache:
|
|
|
|
|
|
value = self._cache.pop(key)
|
|
|
|
|
|
self._cache[key] = value # 移到末尾
|
|
|
|
|
|
self._hits += 1
|
|
|
|
|
|
return self.deserialize(value) if isinstance(value, str) else value
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._misses += 1
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
2026-03-10 14:42:33 +08:00
|
|
|
|
def _ensure_cleanup_task(self) -> None:
|
|
|
|
|
|
"""确保清理任务已启动(延迟启动,在事件循环可用后调用)"""
|
|
|
|
|
|
if self._cleanup_task is None or self._cleanup_task.done():
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._start_cleanup_task()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
async def set(self, key: str, value: Any, ttl: int | None = None) -> bool:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""设置缓存值"""
|
2026-03-10 14:42:33 +08:00
|
|
|
|
self._ensure_cleanup_task()
|
2026-02-14 16:34:52 +08:00
|
|
|
|
async with self._lock:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
# 检查大小限制
|
|
|
|
|
|
if key not in self._cache:
|
|
|
|
|
|
self._check_size()
|
|
|
|
|
|
|
|
|
|
|
|
# 序列化值
|
|
|
|
|
|
if not isinstance(value, str):
|
|
|
|
|
|
value = self.serialize(value)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置值
|
|
|
|
|
|
self._cache[key] = value
|
|
|
|
|
|
self._cache.move_to_end(key) # 移到末尾(最新)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置过期时间
|
|
|
|
|
|
if ttl is None:
|
|
|
|
|
|
ttl = self.default_ttl
|
|
|
|
|
|
if ttl > 0:
|
|
|
|
|
|
self._expiry[key] = time.time() + ttl
|
|
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
async def delete(self, key: str) -> bool:
|
|
|
|
|
|
"""删除缓存项"""
|
2026-02-14 16:34:52 +08:00
|
|
|
|
async with self._lock:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
if key in self._cache:
|
|
|
|
|
|
self._cache.pop(key)
|
|
|
|
|
|
self._expiry.pop(key, None)
|
|
|
|
|
|
return True
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
async def exists(self, key: str) -> bool:
|
|
|
|
|
|
"""检查缓存项是否存在"""
|
2026-02-14 16:34:52 +08:00
|
|
|
|
async with self._lock:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
# 检查是否过期
|
|
|
|
|
|
if key in self._expiry:
|
|
|
|
|
|
if self._expiry[key] < time.time():
|
|
|
|
|
|
# 已过期,删除
|
|
|
|
|
|
self._cache.pop(key, None)
|
|
|
|
|
|
self._expiry.pop(key)
|
|
|
|
|
|
return False
|
|
|
|
|
|
return key in self._cache
|
|
|
|
|
|
|
|
|
|
|
|
async def clear(self) -> bool:
|
|
|
|
|
|
"""清空所有缓存"""
|
2026-02-14 16:34:52 +08:00
|
|
|
|
async with self._lock:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
self._cache.clear()
|
|
|
|
|
|
self._expiry.clear()
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
async def get_many(self, keys: list[str]) -> dict[str, Any]:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""批量获取缓存值"""
|
|
|
|
|
|
result = {}
|
|
|
|
|
|
for key in keys:
|
|
|
|
|
|
value = await self.get(key)
|
|
|
|
|
|
if value is not None:
|
|
|
|
|
|
result[key] = value
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
async def set_many(self, items: dict[str, Any], ttl: int | None = None) -> bool:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""批量设置缓存值"""
|
|
|
|
|
|
success = True
|
|
|
|
|
|
for key, value in items.items():
|
|
|
|
|
|
if not await self.set(key, value, ttl):
|
|
|
|
|
|
success = False
|
|
|
|
|
|
return success
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
async def get_stats(self) -> dict[str, Any]:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""获取缓存统计信息"""
|
|
|
|
|
|
total_requests = self._hits + self._misses
|
|
|
|
|
|
hit_rate = self._hits / total_requests if total_requests > 0 else 0
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"type": "memory",
|
|
|
|
|
|
"size": len(self._cache),
|
|
|
|
|
|
"max_size": self.max_size,
|
|
|
|
|
|
"hits": self._hits,
|
|
|
|
|
|
"misses": self._misses,
|
|
|
|
|
|
"hit_rate": hit_rate,
|
|
|
|
|
|
"evictions": self._evictions,
|
|
|
|
|
|
"cleanup_interval": self._cleanup_interval,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-30 14:30:57 +08:00
|
|
|
|
async def _do_shutdown(self) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""清理资源"""
|
|
|
|
|
|
# 取消清理任务
|
|
|
|
|
|
if self._cleanup_task:
|
|
|
|
|
|
self._cleanup_task.cancel()
|
|
|
|
|
|
try:
|
|
|
|
|
|
await self._cleanup_task
|
|
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-01-30 14:30:57 +08:00
|
|
|
|
def __del__(self) -> None:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""清理资源"""
|
|
|
|
|
|
if hasattr(self, "_cleanup_task") and self._cleanup_task:
|
|
|
|
|
|
self._cleanup_task.cancel()
|