mengqiankang hai 1 mes
pai
achega
9a83dc529c
Modificáronse 3 ficheiros con 64 adicións e 12 borrados
  1. 6 2
      alien_gateway/config.py
  2. 10 0
      alien_gateway/main.py
  3. 48 10
      alien_util/redis_client.py

+ 6 - 2
alien_gateway/config.py

@@ -24,11 +24,13 @@ class Settings(BaseSettings):
     # Redis Sentinel 高可用配置
     # 例: REDIS_SENTINELS=192.168.2.251:36379,192.168.2.252:36379,192.168.2.253:36379
     REDIS_SENTINELS: str = os.getenv("REDIS_SENTINELS", "")
-    REDIS_MASTER_NAME: str = os.getenv("REDIS_MASTER_NAME", "")
+    REDIS_MASTER_NAME: str = os.getenv("REDIS_MASTER_NAME", "mymaster")
     REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD", "")
     REDIS_DB: int = int(os.getenv("REDIS_DB", "0"))
     REDIS_SENTINEL_USERNAME: str = os.getenv("REDIS_SENTINEL_USERNAME", "")
     REDIS_SENTINEL_PASSWORD: str = os.getenv("REDIS_SENTINEL_PASSWORD", "")
+    REDIS_SOCKET_TIMEOUT: float = float(os.getenv("REDIS_SOCKET_TIMEOUT", "0.5"))
+    REDIS_CONNECT_TIMEOUT: float = float(os.getenv("REDIS_CONNECT_TIMEOUT", "1.0"))
     # 下游服务地址
     STORE_BASE_URL: str = os.getenv("STORE_BASE_URL")  # alien_store 服务地址
 
@@ -51,7 +53,9 @@ class Settings(BaseSettings):
             entry = item.strip()
             if not entry:
                 continue
-            host, port = entry.split(":")
+            if ":" not in entry:
+                raise ValueError(f"Invalid REDIS_SENTINELS entry: {entry}")
+            host, port = entry.split(":", 1)
             nodes.append((host.strip(), int(port.strip())))
         return nodes
 

+ 10 - 0
alien_gateway/main.py

@@ -6,6 +6,7 @@ from fastapi import FastAPI, Request, Response, HTTPException
 from fastapi.middleware.cors import CORSMiddleware
 from starlette.status import HTTP_502_BAD_GATEWAY
 from alien_gateway.config import settings
+from alien_util.redis_client import check_redis_connection
 
 app = FastAPI(
     title=f"{settings.PROJECT_NAME} - Gateway & Auth Service",
@@ -55,6 +56,15 @@ logger = _init_logger()
 async def health():
     return {"service": "alien_gateway", "status": "ok"}
 
+
+@app.get("/health/redis")
+async def redis_health():
+    try:
+        return check_redis_connection()
+    except Exception as exc:
+        logger.error("redis health check failed err=%s", exc)
+        raise HTTPException(status_code=HTTP_502_BAD_GATEWAY, detail="Redis unavailable")
+
 # 此模块未来将承担 JWT 签发、权限校验中间件、路由聚合等核心功能
 @app.post("/auth/login")
 async def login():

+ 48 - 10
alien_util/redis_client.py

@@ -1,3 +1,5 @@
+from functools import lru_cache
+
 from redis import Redis
 from redis.asyncio import Redis as AsyncRedis
 from redis.asyncio.sentinel import Sentinel as AsyncSentinel
@@ -10,17 +12,40 @@ def _sentinel_kwargs() -> dict:
     return settings.REDIS_SENTINEL_KWARGS
 
 
+def _redis_connection_kwargs() -> dict:
+    return {
+        "password": settings.REDIS_PASSWORD or None,
+        "db": settings.REDIS_DB,
+        "socket_timeout": settings.REDIS_SOCKET_TIMEOUT,
+        "socket_connect_timeout": settings.REDIS_CONNECT_TIMEOUT,
+        "retry_on_timeout": True,
+        "decode_responses": True,
+    }
+
+
+def _ensure_sentinel_nodes() -> None:
+    if not settings.REDIS_SENTINEL_NODES:
+        raise RuntimeError("REDIS_SENTINELS 未配置,无法通过 Sentinel 连接 Redis。")
+
+
+@lru_cache(maxsize=1)
 def get_sentinel_client() -> Sentinel:
+    _ensure_sentinel_nodes()
     return Sentinel(
         settings.REDIS_SENTINEL_NODES,
-        socket_timeout=0.5,
+        socket_timeout=settings.REDIS_SOCKET_TIMEOUT,
+        socket_connect_timeout=settings.REDIS_CONNECT_TIMEOUT,
         sentinel_kwargs=_sentinel_kwargs(),
     )
 
+
+@lru_cache(maxsize=1)
 def get_async_sentinel_client() -> AsyncSentinel:
+    _ensure_sentinel_nodes()
     return AsyncSentinel(
         settings.REDIS_SENTINEL_NODES,
-        socket_timeout=0.5,
+        socket_timeout=settings.REDIS_SOCKET_TIMEOUT,
+        socket_connect_timeout=settings.REDIS_CONNECT_TIMEOUT,
         sentinel_kwargs=_sentinel_kwargs(),
     )
 
@@ -28,32 +53,28 @@ def get_async_sentinel_client() -> AsyncSentinel:
 def get_redis_master() -> Redis:
     return get_sentinel_client().master_for(
         service_name=settings.REDIS_MASTER_NAME,
-        password=settings.REDIS_PASSWORD or None,
-        db=settings.REDIS_DB,
+        **_redis_connection_kwargs(),
     )
 
 
 def get_redis_slave() -> Redis:
     return get_sentinel_client().slave_for(
         service_name=settings.REDIS_MASTER_NAME,
-        password=settings.REDIS_PASSWORD or None,
-        db=settings.REDIS_DB,
+        **_redis_connection_kwargs(),
     )
 
 
 def get_async_redis_master() -> AsyncRedis:
     return get_async_sentinel_client().master_for(
         service_name=settings.REDIS_MASTER_NAME,
-        password=settings.REDIS_PASSWORD or None,
-        db=settings.REDIS_DB,
+        **_redis_connection_kwargs(),
     )
 
 
 def get_async_redis_slave() -> AsyncRedis:
     return get_async_sentinel_client().slave_for(
         service_name=settings.REDIS_MASTER_NAME,
-        password=settings.REDIS_PASSWORD or None,
-        db=settings.REDIS_DB,
+        **_redis_connection_kwargs(),
     )
 
 
@@ -65,3 +86,20 @@ def get_redis() -> Redis:
 def get_async_redis() -> AsyncRedis:
     # 默认返回主节点客户端,统一写入入口
     return get_async_redis_master()
+
+
+def check_redis_connection() -> dict:
+    """
+    通过 Sentinel 获取当前主节点并执行 ping,返回连接状态与主节点地址。
+    """
+    sentinel = get_sentinel_client()
+    redis_client = get_redis_master()
+    pong = redis_client.ping()
+    host, port = sentinel.discover_master(settings.REDIS_MASTER_NAME)
+    return {
+        "ok": bool(pong),
+        "master_name": settings.REDIS_MASTER_NAME,
+        "master_host": host,
+        "master_port": port,
+        "db": settings.REDIS_DB,
+    }