celery任务改为异步访问数据库
This commit is contained in:
@@ -1,15 +1,212 @@
|
||||
import redis
|
||||
import asyncio
|
||||
import uuid
|
||||
from typing import Optional
|
||||
from contextlib import asynccontextmanager
|
||||
import aioredis
|
||||
from app.config import settings
|
||||
from app.logger import get_redis_logger
|
||||
|
||||
logger = get_redis_logger()
|
||||
|
||||
|
||||
class AsyncRedisLock:
|
||||
"""异步Redis分布式锁"""
|
||||
def __init__(self, redis_pool, key: str, timeout: int = None):
|
||||
self.redis_pool = redis_pool
|
||||
self.key = f"lock:{key}"
|
||||
self.timeout = timeout or settings.redis_lock_timeout
|
||||
self.identifier = str(uuid.uuid4())
|
||||
self.acquired = False
|
||||
|
||||
async def acquire(self, blocking: bool = False) -> bool:
|
||||
"""获取分布式锁"""
|
||||
logger.debug(f"🔒 尝试获取Redis锁: {self.key}")
|
||||
|
||||
try:
|
||||
# 使用SET命令的NX和EX选项原子性地获取锁
|
||||
result = await self.redis_pool.set(
|
||||
self.key,
|
||||
self.identifier,
|
||||
expire=self.timeout,
|
||||
exist=self.redis_pool.SET_IF_NOT_EXIST
|
||||
)
|
||||
|
||||
self.acquired = result
|
||||
if self.acquired:
|
||||
logger.debug(f"✅ Redis锁获取成功: {self.key}")
|
||||
else:
|
||||
logger.debug(f"❌ Redis锁获取失败: {self.key}")
|
||||
|
||||
return self.acquired
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 获取Redis锁失败: {e}")
|
||||
return False
|
||||
|
||||
async def release(self) -> bool:
|
||||
"""释放分布式锁"""
|
||||
if not self.acquired:
|
||||
return False
|
||||
|
||||
try:
|
||||
# 使用Lua脚本确保只有锁的持有者才能释放锁
|
||||
lua_script = """
|
||||
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||
return redis.call("DEL", KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
"""
|
||||
|
||||
result = await self.redis_pool.eval(
|
||||
lua_script,
|
||||
1,
|
||||
self.key,
|
||||
self.identifier
|
||||
)
|
||||
|
||||
self.acquired = False
|
||||
released = bool(result)
|
||||
if released:
|
||||
logger.debug(f"🔓 Redis锁释放成功: {self.key}")
|
||||
else:
|
||||
logger.warning(f"⚠️ Redis锁释放失败,可能已过期: {self.key}")
|
||||
|
||||
return released
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 释放Redis锁失败: {e}")
|
||||
return False
|
||||
|
||||
async def __aenter__(self):
|
||||
"""异步上下文管理器入口"""
|
||||
retries = 0
|
||||
while retries < settings.redis_lock_max_retries:
|
||||
if await self.acquire(blocking=False):
|
||||
return self
|
||||
await asyncio.sleep(settings.redis_lock_retry_delay)
|
||||
retries += 1
|
||||
|
||||
raise TimeoutError(f"Failed to acquire lock {self.key} after {retries} retries")
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""异步上下文管理器出口"""
|
||||
await self.release()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def distributed_lock(redis_pool, lock_key: str, timeout: int = None):
|
||||
"""分布式锁上下文管理器
|
||||
|
||||
Args:
|
||||
redis_pool: Redis连接池
|
||||
lock_key: 锁键名
|
||||
timeout: 锁超时时间(秒)
|
||||
|
||||
Yields:
|
||||
bool: 是否成功获取锁
|
||||
"""
|
||||
full_lock_key = f"lock:{lock_key}"
|
||||
lock_timeout = timeout or settings.redis_lock_timeout
|
||||
|
||||
try:
|
||||
# 尝试获取锁,使用setnx命令,并设置过期时间
|
||||
identifier = str(uuid.uuid4())
|
||||
lock_acquired = await redis_pool.set(
|
||||
full_lock_key,
|
||||
identifier,
|
||||
expire=lock_timeout,
|
||||
exist=redis_pool.SET_IF_NOT_EXIST
|
||||
)
|
||||
|
||||
logger.debug(f"🔒 尝试获取分布式锁: {full_lock_key}, 结果: {lock_acquired}")
|
||||
|
||||
if lock_acquired:
|
||||
try:
|
||||
yield True
|
||||
finally:
|
||||
# 使用Lua脚本安全释放锁,确保只有锁的持有者才能释放
|
||||
lua_script = """
|
||||
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||
return redis.call("DEL", KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
"""
|
||||
|
||||
result = await redis_pool.eval(
|
||||
lua_script,
|
||||
1,
|
||||
full_lock_key,
|
||||
identifier
|
||||
)
|
||||
|
||||
if result:
|
||||
logger.debug(f"🔓 分布式锁释放成功: {full_lock_key}")
|
||||
else:
|
||||
logger.warning(f"⚠️ 分布式锁释放失败,可能已过期: {full_lock_key}")
|
||||
else:
|
||||
yield False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 分布式锁操作失败: {e}")
|
||||
yield False
|
||||
|
||||
|
||||
class AsyncRedisManager:
|
||||
def __init__(self):
|
||||
self.redis_pool: Optional[aioredis.Redis] = None
|
||||
|
||||
async def connect(self) -> bool:
|
||||
"""连接Redis
|
||||
|
||||
Returns:
|
||||
bool: 连接是否成功
|
||||
"""
|
||||
try:
|
||||
logger.info(f"🔴 正在连接Redis: {settings.redis_url}")
|
||||
self.redis_pool = await aioredis.create_redis_pool(
|
||||
settings.redis_url,
|
||||
encoding="utf-8",
|
||||
minsize=1,
|
||||
maxsize=settings.redis_max_connections,
|
||||
timeout=settings.redis_timeout
|
||||
)
|
||||
|
||||
# 测试连接
|
||||
result = await self.redis_pool.ping()
|
||||
if result:
|
||||
logger.info("✅ Redis连接成功")
|
||||
return True
|
||||
else:
|
||||
logger.error("❌ Redis ping失败")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Redis连接失败: {e}")
|
||||
return False
|
||||
|
||||
async def disconnect(self):
|
||||
"""断开Redis连接"""
|
||||
if self.redis_pool:
|
||||
self.redis_pool.close()
|
||||
await self.redis_pool.wait_closed()
|
||||
logger.info("🔴 Redis连接已关闭")
|
||||
|
||||
async def create_lock(self, key: str, timeout: int = None) -> AsyncRedisLock:
|
||||
"""创建分布式锁"""
|
||||
if not self.redis_pool:
|
||||
raise RuntimeError("Redis client not connected")
|
||||
return AsyncRedisLock(self.redis_pool, key, timeout)
|
||||
|
||||
async def get_redis_pool(self):
|
||||
"""获取Redis连接池"""
|
||||
if not self.redis_pool:
|
||||
await self.connect()
|
||||
return self.redis_pool
|
||||
|
||||
|
||||
# 为了保持向后兼容,保留同步版本但标记为废弃
|
||||
class RedisLock:
|
||||
"""Redis分布式锁"""
|
||||
def __init__(self, redis_client: redis.Redis, key: str, timeout: int = None):
|
||||
"""Redis分布式锁 (已废弃,请使用AsyncRedisLock)"""
|
||||
def __init__(self, redis_client, key: str, timeout: int = None):
|
||||
self.redis_client = redis_client
|
||||
self.key = f"lock:{key}"
|
||||
self.timeout = timeout or settings.redis_lock_timeout
|
||||
@@ -86,14 +283,15 @@ class RedisLock:
|
||||
|
||||
class RedisManager:
|
||||
def __init__(self):
|
||||
self.redis_client: Optional[redis.Redis] = None
|
||||
self.redis_client = None
|
||||
|
||||
def connect(self) -> bool:
|
||||
"""连接Redis
|
||||
"""连接Redis (已废弃,请使用AsyncRedisManager)
|
||||
|
||||
Returns:
|
||||
bool: 连接是否成功
|
||||
"""
|
||||
import redis
|
||||
try:
|
||||
logger.info(f"🔴 正在连接Redis: {settings.redis_url}")
|
||||
self.redis_client = redis.from_url(
|
||||
@@ -127,5 +325,6 @@ class RedisManager:
|
||||
return RedisLock(self.redis_client, key, timeout)
|
||||
|
||||
|
||||
# 全局Redis管理器实例
|
||||
redis_manager = RedisManager()
|
||||
# 全局Redis管理器实例(保持向后兼容)
|
||||
redis_manager = RedisManager()
|
||||
async_redis_manager = AsyncRedisManager()
|
||||
Reference in New Issue
Block a user