191 lines
6.2 KiB
Python
191 lines
6.2 KiB
Python
from redis import Redis
|
|
from typing import Optional, Any
|
|
import pickle
|
|
import logging
|
|
from functools import wraps
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class RedisConnectionError(Exception):
|
|
"""Custom exception for Redis connection issues"""
|
|
|
|
pass
|
|
|
|
|
|
class RedisManager:
|
|
_instance = None
|
|
|
|
def __new__(cls):
|
|
if cls._instance is None:
|
|
cls._instance = super(RedisManager, cls).__new__(cls)
|
|
cls._instance._initialized = False
|
|
return cls._instance
|
|
|
|
def __init__(self):
|
|
if not self._initialized:
|
|
try:
|
|
self.client = Redis(
|
|
host="localhost",
|
|
port=6380,
|
|
db=0,
|
|
decode_responses=False,
|
|
socket_timeout=5,
|
|
)
|
|
self._initialized = True
|
|
logger.info("Redis connection established")
|
|
except Exception as e:
|
|
logger.error(f"Failed to connect to Redis: {str(e)}")
|
|
raise RedisConnectionError(f"Redis connection failed: {str(e)}")
|
|
|
|
self.default_ttl = 1800
|
|
|
|
def _handle_connection(func):
|
|
"""Decorator to handle Redis connection errors"""
|
|
|
|
@wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
try:
|
|
return func(self, *args, **kwargs)
|
|
except Exception as e:
|
|
logger.error(f"Redis operation failed: {str(e)}")
|
|
raise RedisConnectionError(f"Redis operation failed: {str(e)}")
|
|
|
|
return wrapper
|
|
|
|
@_handle_connection
|
|
def set_data(self, key: str, value: Any, expiry: int = 1800) -> bool:
|
|
"""Store data in Redis with expiry time"""
|
|
try:
|
|
pickled_value = pickle.dumps(value)
|
|
return self.client.set(key, pickled_value, ex=expiry)
|
|
except Exception as e:
|
|
logger.error(f"Failed to set data for key {key}: {str(e)}")
|
|
return False
|
|
|
|
@_handle_connection
|
|
def get_data(self, key: str) -> Optional[Any]:
|
|
"""Retrieve data from Redis"""
|
|
try:
|
|
data = self.client.get(key)
|
|
return pickle.loads(data) if data else None
|
|
except Exception as e:
|
|
logger.error(f"Failed to get data for key {key}: {str(e)}")
|
|
return None
|
|
|
|
@_handle_connection
|
|
def delete_data(self, key: str) -> bool:
|
|
"""Delete data from Redis"""
|
|
try:
|
|
return bool(self.client.delete(key))
|
|
except Exception as e:
|
|
logger.error(f"Failed to delete key {key}: {str(e)}")
|
|
return False
|
|
|
|
@_handle_connection
|
|
def clear_user_data(self, user_id: str) -> bool:
|
|
"""Clear all data for a specific user"""
|
|
try:
|
|
pattern = f"user:{user_id}:*"
|
|
keys = self.client.keys(pattern)
|
|
if keys:
|
|
return bool(self.client.delete(*keys))
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to clear data for user {user_id}: {str(e)}")
|
|
return False
|
|
|
|
@_handle_connection
|
|
def get_memory_usage(self) -> dict:
|
|
"""Get Redis memory statistics"""
|
|
try:
|
|
info = self.client.info(section="memory")
|
|
return {
|
|
"used_memory": info["used_memory_human"],
|
|
"peak_memory": info["used_memory_peak_human"],
|
|
"fragmentation": info["mem_fragmentation_ratio"],
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Failed to get memory usage: {str(e)}")
|
|
return {}
|
|
|
|
@_handle_connection
|
|
def refresh_user_ttl(self, user_id: str) -> bool:
|
|
"""Refresh TTL for all keys belonging to a user"""
|
|
try:
|
|
# Get all keys for this user
|
|
pattern = f"user:{user_id}:*"
|
|
user_keys = self.client.keys(pattern)
|
|
|
|
if not user_keys:
|
|
return False
|
|
|
|
# Update TTL for all user's keys
|
|
pipeline = self.client.pipeline()
|
|
for key in user_keys:
|
|
pipeline.expire(key, self.default_ttl)
|
|
|
|
# Execute all EXPIRE commands atomically
|
|
results = pipeline.execute()
|
|
|
|
# Check if all operations succeeded
|
|
success = all(results)
|
|
if not success:
|
|
logger.warning(f"Some TTL updates failed for user {user_id}")
|
|
|
|
return success
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to refresh TTL for user {user_id}: {str(e)}")
|
|
return False
|
|
|
|
@_handle_connection
|
|
def refresh_key_ttl(self, key: str, ttl: int = None) -> bool:
|
|
"""Refresh TTL for a specific key"""
|
|
try:
|
|
return self.client.expire(key, ttl or self.default_ttl)
|
|
except Exception as e:
|
|
logger.error(f"Failed to refresh TTL for key {key}: {str(e)}")
|
|
return False
|
|
|
|
def is_connected(self) -> bool:
|
|
"""Check if Redis connection is alive"""
|
|
try:
|
|
return self.client.ping()
|
|
except Exception:
|
|
return False
|
|
|
|
def get_keys_by_pattern(self, pattern: str = "*") -> list:
|
|
"""Get all keys matching pattern"""
|
|
try:
|
|
return [key.decode("utf-8") for key in self.client.keys(pattern)]
|
|
except Exception as e:
|
|
logger.error(f"Error getting keys: {e}")
|
|
return []
|
|
|
|
def get_key_info(self, key: str) -> dict:
|
|
"""Get detailed information about a key"""
|
|
try:
|
|
return {
|
|
"type": self.client.type(key).decode("utf-8"),
|
|
"ttl": self.client.ttl(key),
|
|
"memory": self.client.memory_usage(key),
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Error getting key info: {e}")
|
|
return {}
|
|
|
|
def monitor_user_data(self, user_id: str) -> dict:
|
|
"""Monitor all data for a specific user"""
|
|
try:
|
|
user_keys = self.get_keys_by_pattern(f"user:{user_id}:*")
|
|
return {
|
|
"total_keys": len(user_keys),
|
|
"keys": {key: self.get_key_info(key) for key in user_keys},
|
|
"memory_usage": self.get_memory_usage(),
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Error monitoring user data: {e}")
|
|
return {}
|