From 7d276bc91d6af9026314128fc5afd81184fa10da Mon Sep 17 00:00:00 2001 From: erio Date: Sat, 7 Mar 2026 12:34:36 +0800 Subject: [PATCH] fix: ensure Lua scripts are loaded before pipeline EVALSHA calls Pipeline mode only sends EVALSHA (no automatic fallback to EVAL). If Redis restarts and script cache is lost, all pipeline Lua calls silently fail with NOSCRIPT. Add ensureScriptLoaded() to pre-load scripts before pipeline execution. Co-Authored-By: Claude Opus 4.6 --- backend/internal/handler/admin/account_handler.go | 15 +-------------- backend/internal/repository/gateway_cache.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index bfa46de05b..eb9c370d7f 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -8,7 +8,6 @@ import ( "encoding/json" "errors" "fmt" - "log/slog" "net/http" "strconv" "strings" @@ -370,19 +369,7 @@ func (h *AccountHandler) List(c *gin.Context) { } } if len(accountGroups) > 0 { - var affinityErr error - affinityClients, affinityErr = h.gatewayCache.GetAccountAffinityClientsBatch(c.Request.Context(), accountGroups, service.ClientAffinityTTL) - if affinityErr != nil { - slog.Error("GetAccountAffinityClientsBatch failed", - "error", affinityErr, - "account_count", len(accountGroups), - ) - } else { - slog.Info("GetAccountAffinityClientsBatch success", - "account_count", len(accountGroups), - "result_count", len(affinityClients), - ) - } + affinityClients, _ = h.gatewayCache.GetAccountAffinityClientsBatch(c.Request.Context(), accountGroups, service.ClientAffinityTTL) } } diff --git a/backend/internal/repository/gateway_cache.go b/backend/internal/repository/gateway_cache.go index a226e7e435..8aca561ae2 100644 --- a/backend/internal/repository/gateway_cache.go +++ b/backend/internal/repository/gateway_cache.go @@ -41,6 +41,16 @@ func NewGatewayCache(rdb *redis.Client) service.GatewayCache { return &gatewayCache{rdb: rdb} } +// ensureScriptLoaded 确保 Lua 脚本已加载到 Redis 服务器的脚本缓存中。 +// Pipeline 中的 Script.Run 只发送 EVALSHA,如果 Redis 重启过导致脚本缓存丢失, +// EVALSHA 会返回 NOSCRIPT 错误。此方法提前加载脚本以避免该问题。 +func ensureScriptLoaded(ctx context.Context, rdb *redis.Client, script *redis.Script) { + exists, err := script.Exists(ctx, rdb).Result() + if err != nil || len(exists) == 0 || !exists[0] { + _ = script.Load(ctx, rdb).Err() + } +} + // buildSessionKey 构建 session key,包含 groupID 实现分组隔离 // 格式: sticky_session:{groupID}:{sessionHash} func buildSessionKey(groupID int64, sessionHash string) string { @@ -125,6 +135,8 @@ func (c *gatewayCache) GetAccountAffinityCountBatch(ctx context.Context, groupID now := time.Now().Unix() expireThreshold := now - int64(ttl.Seconds()) + ensureScriptLoaded(ctx, c.rdb, getAffinityCountScript) + pipe := c.rdb.Pipeline() cmds := make([]*redis.Cmd, len(accountIDs)) for i, accID := range accountIDs { @@ -166,6 +178,8 @@ func (c *gatewayCache) GetAccountAffinityClientsBatch(ctx context.Context, accou } } + ensureScriptLoaded(ctx, c.rdb, getAffinityClientsScript) + pipe := c.rdb.Pipeline() cmds := make([]*redis.Cmd, len(queries)) for i, q := range queries {