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 <noreply@anthropic.com>
This commit is contained in:
erio
2026-03-07 12:34:36 +08:00
co-authored by Claude Opus 4.6
parent 87e0a0f299
commit 7d276bc91d
2 changed files with 15 additions and 14 deletions
@@ -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)
}
}
@@ -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 {