- 新增DistributedLock类,支持唯一标识防解锁冲突 - 实现自动续期、超时、重试、上下文管理器功能 - 提供手动 acquire、release 和 extend 接口 - 增加异步上下文管理器便利函数distributed_lock - 实现分布式锁装饰器distributed_lock_decorator支持灵活调用 - 编写示例模块,展示多种锁的使用方式和自动续期示例 - 支持锁状态查询,演示锁冲突与延长锁超时操作 - 保证锁的线程/进程安全与Redis操作原子性
250 lines
6.0 KiB
Python
250 lines
6.0 KiB
Python
"""
|
|
Redis integration with async client and connection pooling.
|
|
Provides shared Redis instance for all applications.
|
|
"""
|
|
|
|
from typing import Optional, Any
|
|
import json
|
|
from redis import asyncio as aioredis
|
|
from redis.asyncio import Redis, ConnectionPool
|
|
from core.config import settings
|
|
|
|
|
|
# Create Redis connection pool
|
|
redis_pool: Optional[ConnectionPool] = None
|
|
redis_client: Optional[Redis] = None
|
|
|
|
|
|
async def init_redis() -> Redis:
|
|
"""
|
|
Initialize Redis client with connection pool.
|
|
|
|
This should be called at application startup.
|
|
|
|
Returns:
|
|
Redis: Async Redis client
|
|
"""
|
|
global redis_pool, redis_client
|
|
|
|
redis_pool = ConnectionPool.from_url(
|
|
settings.redis_url,
|
|
max_connections=settings.redis_max_connections,
|
|
decode_responses=settings.redis_decode_responses,
|
|
)
|
|
|
|
redis_client = Redis(connection_pool=redis_pool)
|
|
|
|
return redis_client
|
|
|
|
|
|
async def get_redis() -> Redis:
|
|
"""
|
|
Dependency that provides Redis client.
|
|
|
|
Usage in FastAPI:
|
|
@app.get("/cache")
|
|
async def get_cache(redis: Redis = Depends(get_redis)):
|
|
...
|
|
|
|
Returns:
|
|
Redis: Async Redis client
|
|
|
|
Raises:
|
|
RuntimeError: If Redis client is not initialized
|
|
"""
|
|
if redis_client is None:
|
|
raise RuntimeError("Redis client not initialized. Call init_redis() at startup.")
|
|
return redis_client
|
|
|
|
|
|
async def check_redis_connection() -> bool:
|
|
"""
|
|
Check if Redis connection is healthy.
|
|
|
|
Returns:
|
|
bool: True if connection is healthy, False otherwise
|
|
"""
|
|
try:
|
|
if redis_client is None:
|
|
return False
|
|
redis_client.ping()
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
async def close_redis_connection() -> None:
|
|
"""
|
|
Close Redis connection pool.
|
|
|
|
This should be called at application shutdown.
|
|
"""
|
|
global redis_pool, redis_client
|
|
|
|
if redis_client:
|
|
await redis_client.aclose()
|
|
redis_client = None
|
|
|
|
if redis_pool:
|
|
await redis_pool.disconnect()
|
|
redis_pool = None
|
|
|
|
|
|
class RedisCache:
|
|
"""
|
|
Redis cache utility with common operations.
|
|
|
|
Provides helper methods for caching with TTL and JSON serialization.
|
|
"""
|
|
|
|
def __init__(self, redis: Redis, prefix: str = ""):
|
|
"""
|
|
Initialize cache with Redis client and optional key prefix.
|
|
|
|
Args:
|
|
redis: Redis client instance
|
|
prefix: Optional key prefix for namespace isolation
|
|
"""
|
|
self.redis = redis
|
|
self.prefix = prefix
|
|
|
|
def _make_key(self, key: str) -> str:
|
|
"""Generate prefixed key."""
|
|
return f"{self.prefix}:{key}" if self.prefix else key
|
|
|
|
async def get(self, key: str) -> Optional[str]:
|
|
"""
|
|
Get value by key.
|
|
|
|
Args:
|
|
key: Cache key
|
|
|
|
Returns:
|
|
Optional[str]: Cached value or None if not found
|
|
"""
|
|
return await self.redis.get(self._make_key(key))
|
|
|
|
async def set(
|
|
self,
|
|
key: str,
|
|
value: str,
|
|
ttl: Optional[int] = None
|
|
) -> bool:
|
|
"""
|
|
Set value with optional TTL.
|
|
|
|
Args:
|
|
key: Cache key
|
|
value: Value to cache
|
|
ttl: Time to live in seconds (optional)
|
|
|
|
Returns:
|
|
bool: True if successful
|
|
"""
|
|
return await self.redis.set(
|
|
self._make_key(key),
|
|
value,
|
|
ex=ttl
|
|
)
|
|
|
|
async def get_json(self, key: str) -> Optional[Any]:
|
|
"""
|
|
Get JSON value by key.
|
|
|
|
Args:
|
|
key: Cache key
|
|
|
|
Returns:
|
|
Optional[Any]: Deserialized JSON value or None
|
|
"""
|
|
value = await self.get(key)
|
|
if value is None:
|
|
return None
|
|
try:
|
|
return json.loads(value)
|
|
except json.JSONDecodeError:
|
|
return None
|
|
|
|
async def set_json(
|
|
self,
|
|
key: str,
|
|
value: Any,
|
|
ttl: Optional[int] = None
|
|
) -> bool:
|
|
"""
|
|
Set JSON value with optional TTL.
|
|
|
|
Args:
|
|
key: Cache key
|
|
value: Value to serialize and cache
|
|
ttl: Time to live in seconds (optional)
|
|
|
|
Returns:
|
|
bool: True if successful
|
|
"""
|
|
json_value = json.dumps(value)
|
|
return await self.set(key, json_value, ttl)
|
|
|
|
async def delete(self, key: str) -> int:
|
|
"""
|
|
Delete key.
|
|
|
|
Args:
|
|
key: Cache key
|
|
|
|
Returns:
|
|
int: Number of keys deleted
|
|
"""
|
|
return await self.redis.delete(self._make_key(key))
|
|
|
|
async def exists(self, key: str) -> bool:
|
|
"""
|
|
Check if key exists.
|
|
|
|
Args:
|
|
key: Cache key
|
|
|
|
Returns:
|
|
bool: True if key exists
|
|
"""
|
|
return await self.redis.exists(self._make_key(key)) > 0
|
|
|
|
async def expire(self, key: str, ttl: int) -> bool:
|
|
"""
|
|
Set TTL for existing key.
|
|
|
|
Args:
|
|
key: Cache key
|
|
ttl: Time to live in seconds
|
|
|
|
Returns:
|
|
bool: True if successful
|
|
"""
|
|
return await self.redis.expire(self._make_key(key), ttl)
|
|
|
|
async def increment(self, key: str, amount: int = 1) -> int:
|
|
"""
|
|
Increment numeric value.
|
|
|
|
Args:
|
|
key: Cache key
|
|
amount: Increment amount (default 1)
|
|
|
|
Returns:
|
|
int: New value after increment
|
|
"""
|
|
return await self.redis.incrby(self._make_key(key), amount)
|
|
|
|
async def decrement(self, key: str, amount: int = 1) -> int:
|
|
"""
|
|
Decrement numeric value.
|
|
|
|
Args:
|
|
key: Cache key
|
|
amount: Decrement amount (default 1)
|
|
|
|
Returns:
|
|
int: New value after decrement
|
|
"""
|
|
return await self.redis.decrby(self._make_key(key), amount)
|