feat(redis): optimize memory limit and cache cleanup

This commit is contained in:
InfinityPacer
2025-01-19 04:28:16 +08:00
parent 0a86b72110
commit 7a8f880dbe
2 changed files with 58 additions and 6 deletions
+56 -6
View File
@@ -1,4 +1,6 @@
import inspect import inspect
import json
import pickle
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from functools import wraps from functools import wraps
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
@@ -204,17 +206,60 @@ class RedisBackend(CacheBackend):
# 测试连接,确保 Redis 可用 # 测试连接,确保 Redis 可用
self.client.ping() self.client.ping()
logger.debug(f"Successfully connected to Redis") logger.debug(f"Successfully connected to Redis")
self.set_memory_limit()
except redis.RedisError as e: except redis.RedisError as e:
logger.error(f"Failed to connect to Redis: {e}") logger.error(f"Failed to connect to Redis: {e}")
raise RuntimeError("Redis connection failed") from e raise RuntimeError("Redis connection failed") from e
def set_memory_limit(self, policy: str = "allkeys-lru"):
"""
动态设置 Redis 最大内存和内存淘汰策略
:param policy: 淘汰策略(如 'allkeys-lru'
"""
try:
# 如果有显式值,则直接使用,为 0 时说明不限制,如果未配置,开启 BIG_MEMORY_MODE 时为 "512mb",未开启时为 "128mb"
maxmemory = settings.CACHE_REDIS_MAXMEMORY or ("512mb" if settings.BIG_MEMORY_MODE else "128mb")
self.client.config_set("maxmemory", maxmemory)
self.client.config_set("maxmemory-policy", policy)
logger.debug(f"Redis maxmemory set to {maxmemory}, policy: {policy}")
except redis.RedisError as e:
logger.error(f"Failed to set Redis maxmemory or policy: {e}")
@staticmethod
def serialize(value: Any) -> str:
"""
将非字符串类型的值序列化为字符串
"""
if isinstance(value, str):
return value
try:
# 尝试 JSON 序列化
return json.dumps(value)
except TypeError:
# 对象无法直接 JSON 序列化,使用 pickle,用 latin1 以确保它是可存储的字符串
serialized_value = pickle.dumps(value).decode("latin1")
return serialized_value
@staticmethod
def deserialize(value: str) -> Any:
"""
将存储的字符串反序列化为原始值
"""
try:
# 尝试 JSON 反序列化
return json.loads(value)
except (TypeError, json.JSONDecodeError):
# 如果 JSON 反序列化失败,尝试 pickle 反序列化
deserialized_value = pickle.loads(value.encode("latin1"))
return deserialized_value
def get_redis_key(self, region: str, key: str) -> str: def get_redis_key(self, region: str, key: str) -> str:
""" """
获取缓存 Key 获取缓存 Key
""" """
# 使用 region 作为缓存键的一部分 # 使用 region 作为缓存键的一部分
region = self.get_region(region) region = self.get_region(quote(region))
return quote(f"region:{region}:key:{key}") return f"{region}:key:{quote(key)}"
def set(self, key: str, value: Any, ttl: int = None, region: str = DEFAULT_CACHE_REGION, **kwargs) -> None: def set(self, key: str, value: Any, ttl: int = None, region: str = DEFAULT_CACHE_REGION, **kwargs) -> None:
""" """
@@ -229,8 +274,10 @@ class RedisBackend(CacheBackend):
try: try:
ttl = ttl or self.ttl ttl = ttl or self.ttl
redis_key = self.get_redis_key(region, key) redis_key = self.get_redis_key(region, key)
kwargs.pop("maxsize") # 对值进行序列化
self.client.set(redis_key, value, ex=ttl, **kwargs) serialized_value = self.serialize(value)
kwargs.pop("maxsize", None)
self.client.set(redis_key, serialized_value, ex=ttl, **kwargs)
except redis.RedisError as e: except redis.RedisError as e:
logger.error(f"Failed to set key: {key} in region: {region}, error: {e}") logger.error(f"Failed to set key: {key} in region: {region}, error: {e}")
@@ -245,7 +292,9 @@ class RedisBackend(CacheBackend):
try: try:
redis_key = self.get_redis_key(region, key) redis_key = self.get_redis_key(region, key)
value = self.client.get(redis_key) value = self.client.get(redis_key)
return value if value is not None:
return self.deserialize(value) # noqa
return None
except redis.RedisError as e: except redis.RedisError as e:
logger.error(f"Failed to get key: {key} in region: {region}, error: {e}") logger.error(f"Failed to get key: {key} in region: {region}, error: {e}")
return None return None
@@ -271,7 +320,8 @@ class RedisBackend(CacheBackend):
""" """
try: try:
if region: if region:
redis_key = self.get_redis_key(region, "*") cache_region = self.get_region(quote(region))
redis_key = f"{cache_region}:key:*"
# self.client.delete(*self.client.keys(redis_key)) # self.client.delete(*self.client.keys(redis_key))
with self.client.pipeline() as pipe: with self.client.pipeline() as pipe:
for key in self.client.scan_iter(redis_key): for key in self.client.scan_iter(redis_key):
+2
View File
@@ -75,6 +75,8 @@ class ConfigModel(BaseModel):
CACHE_BACKEND_TYPE: str = "cachetools" CACHE_BACKEND_TYPE: str = "cachetools"
# 缓存连接字符串,仅外部缓存(如 Redis、Memcached)需要 # 缓存连接字符串,仅外部缓存(如 Redis、Memcached)需要
CACHE_BACKEND_URL: Optional[str] = None CACHE_BACKEND_URL: Optional[str] = None
# Redis 缓存最大内存限制,未配置时,如开启大内存模式时为 "512mb",未开启时为 "128mb"
CACHE_REDIS_MAXMEMORY: Optional[str] = None
# 配置文件目录 # 配置文件目录
CONFIG_DIR: Optional[str] = None CONFIG_DIR: Optional[str] = None
# 超级管理员 # 超级管理员