From e91e2d1d5e4d8a27f0491d6a524b59a122004b7c Mon Sep 17 00:00:00 2001 From: vibe-code Date: Thu, 13 Aug 2026 10:05:02 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=89=8B=E5=8A=A8=E8=BD=AF=E8=B8=A2=20?= =?UTF-8?q?Redis=20key=20(manual=5Fevict)=20+=20redis=20=E5=8D=B7=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docker-compose: redis_data 卷加 name: litellm_redis_data, 卷名不再随 compose 项目名(目录名)漂移, 目录迁移不会新建空卷丢数据 - _check_request_block: 监控条目命中后检查 Redis manual_evict:{clean} key, 存在即返回 (clean,-2,-2) 恒 429; key 落持久卷, 重启后仍生效, 显式 DEL 才恢复, 幂等 - async_pre_call_hook: 识别 -2 标记, 返回 manually soft-evicted 429 - 用法: SET/DEL/KEYS manual_evict:* (docker exec litellm_redis redis-cli) --- docker-compose.yml | 3 +++ volc_limit_sync.py | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 830ed58..a9fc779 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -89,3 +89,6 @@ volumes: name: litellm_postgres_data redis_data: # 如果开启了 redis 持久化,这里也要打开 driver: local + # 固定卷名:卷名不再由 compose 项目名(目录名)派生, + # 目录改名/迁移后 up 不会新建空卷导致数据"丢失" + name: litellm_redis_data diff --git a/volc_limit_sync.py b/volc_limit_sync.py index 8cac368..c055b2d 100644 --- a/volc_limit_sync.py +++ b/volc_limit_sync.py @@ -862,6 +862,8 @@ def _check_request_block(model_name, reserve=0.0): 返回 (clean_model, usage, limit) 表示超限,None 表示放行。 fail-closed:Redis 不可用 / 限额获取失败 / 读取异常时,对监控条目返回 (clean_model, -1, -1) 标记拒绝(宁可错杀,不可漏放);非监控条目不受影响。 + 手动软踢:Redis key manual_evict:{clean} 存在时返回 (clean_model, -2, -2) + 恒 429,由运维 docker exec 设置/删除(见函数内注释)。 """ if not LITELLM_AVAILABLE: return None @@ -903,6 +905,20 @@ def _check_request_block(model_name, reserve=0.0): ) return (clean, -1, -1) + # ── 手动软踢:Redis manual_evict:{clean} 存在 → 恒 429 (usage=-2, limit=-2) ── + # 运维手段:模型踩采集上限/异常时无需等同步阈值,直接 docker exec 踢出: + # 软踢: docker exec litellm_redis redis-cli SET manual_evict: 1 + # 解除: docker exec litellm_redis redis-cli DEL manual_evict: + # 查看: docker exec litellm_redis redis-cli KEYS manual_evict:* + # key 落在 Redis 持久卷,重启 litellm 后仍生效(不依赖 router 内存态), + # 显式 DEL 才恢复。天然幂等:已存在时每次请求恒 429。 + if redis_client.get(f"manual_evict:{clean}"): + log( + f"🚫 [MANUAL-EVICT] {clean} manually soft-evicted (429)", + level="WARNING", + ) + return (clean, -2, -2) + effective_limit = get_effective_limit(clean, api_base) if effective_limit is None: # 监控条目取不到限额:拒绝而非放行(fail-closed) @@ -1023,6 +1039,17 @@ class DailyLimitHandler(CustomLogger): clean, usage, limit = blocked if usage < 0 or limit < 0: + if usage == -2: + # manual_evict Redis key 触发的运维手动软踢 + log( + f"🚫 [PRE-BLOCK] {data['model']} manually soft-evicted by operator (429)", + level="WARNING", + ) + raise RateLimitError( + message=f"Model '{data['model']}' manually soft-evicted by operator, request denied", + llm_provider="volcengine", + model=data["model"], + ) # fail-closed 标记:Redis 不可用/限额状态未知,拒绝防漏放 log( f"🚫 [PRE-BLOCK] {data['model']} denied: quota state unavailable (fail-closed)",