fix: Redis集群SCAN遍历所有master节点 + 查询结果布局调整\n\n1. ScanAllKeys(): 集群模式用ForEachMaster遍历所有master节点SCAN合并去重\n2. RedisScanKeys/KEYS命令均改用ScanAllKeys,确保集群不遗漏key\n3. QueriesPage布局: sidebar独立左侧,查询结果只在编辑器下方不被sidebar占据宽度

This commit is contained in:
logan committed 2026-05-30 15:28:38 +08:00
1 parent 430a667455
commit 4e6f9eb8fd
3 files changed
+97 -61

No files matched your search

+8 -18
View File
@@ -749,7 +749,7 @@ func executeRedisQuery(instance *model.Instance, dbSelect string, command string
args = append(args, p)
}
// For KEYS command, use SCAN with pagination (auto-append * if no wildcard)
// For KEYS command, use ScanAllKeys (cluster-aware) with pagination
if cmd == "KEYS" {
pattern := "*"
if len(parts) > 1 {
@@ -759,27 +759,17 @@ func executeRedisQuery(instance *model.Instance, dbSelect string, command string
if !strings.Contains(pattern, "*") && !strings.Contains(pattern, "?") && !strings.Contains(pattern, "[") {
pattern = pattern + "*"
}
var allKeys []string
cursor := uint64(0)
maxScan := offset + limit
for {
keys, nextCursor, err := client.Scan(ctx, cursor, pattern, int64(maxScan-len(allKeys))).Result()
if err != nil {
return nil, fmt.Errorf("SCAN failed: %v", err)
}
allKeys = append(allKeys, keys...)
cursor = nextCursor
if cursor == 0 || len(allKeys) >= maxScan {
break
}
allKeys, err := client.ScanAllKeys(ctx, pattern, limit+offset)
if err != nil {
return nil, fmt.Errorf("SCAN failed: %v", err)
}
total := len(allKeys)
end := offset + limit
if end > len(allKeys) {
end = len(allKeys)
if end > total {
end = total
}
if offset > len(allKeys) {
offset = len(allKeys)
if offset > total {
offset = total
}
pagedKeys := allKeys[offset:end]
rows := make([]interface{}, len(pagedKeys))
+50 -5
View File
@@ -59,6 +59,53 @@ func (w *redisClientWrapper) Scan(ctx context.Context, cursor uint64, match stri
return w.Client.Scan(ctx, cursor, match, count)
}
// ScanAllKeys scans keys across ALL master nodes in cluster mode, or single node in standalone.
// Returns merged deduplicated keys.
func (w *redisClientWrapper) ScanAllKeys(ctx context.Context, match string, count int) ([]string, error) {
if !w.isCluster {
// Standalone: iterate SCAN until cursor returns to 0
var allKeys []string
cursor := uint64(0)
for {
keys, nextCursor, err := w.Client.Scan(ctx, cursor, match, int64(count)).Result()
if err != nil {
return allKeys, err
}
allKeys = append(allKeys, keys...)
cursor = nextCursor
if cursor == 0 {
break
}
}
return allKeys, nil
}
// Cluster: iterate SCAN on every master node
var allKeys []string
seen := make(map[string]bool)
err := w.ClusterClient.ForEachMaster(ctx, func(ctx context.Context, node *redis.Client) error {
cursor := uint64(0)
for {
keys, nextCursor, err := node.Scan(ctx, cursor, match, int64(count)).Result()
if err != nil {
return err
}
for _, k := range keys {
if !seen[k] {
seen[k] = true
allKeys = append(allKeys, k)
}
}
cursor = nextCursor
if cursor == 0 {
break
}
}
return nil
})
return allKeys, err
}
func (w *redisClientWrapper) Type(ctx context.Context, key string) *redis.StatusCmd {
if w.isCluster {
return w.ClusterClient.Type(ctx, key)
@@ -321,7 +368,6 @@ func RedisScanKeys(c *gin.Context) {
pattern = pattern + "*"
}
count, _ := strconv.Atoi(c.DefaultQuery("count", "100"))
cursorParam := c.DefaultQuery("cursor", "0")
db := 0
if dbParam != "" {
@@ -342,8 +388,7 @@ func RedisScanKeys(c *gin.Context) {
defer client.Close()
ctx := context.Background()
cursor, _ := strconv.ParseUint(cursorParam, 10, 64)
keys, nextCursor, err := client.Scan(ctx, cursor, pattern, int64(count)).Result()
allKeys, err := client.ScanAllKeys(ctx, pattern, count)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
return
@@ -356,7 +401,7 @@ func RedisScanKeys(c *gin.Context) {
TTL int64 `json:"ttl"`
}
var keyInfos []KeyInfo
for _, key := range keys {
for _, key := range allKeys {
keyType, _ := client.Type(ctx, key).Result()
ttl, _ := client.TTL(ctx, key).Result()
ttlMs := int64(-1)
@@ -371,7 +416,7 @@ func RedisScanKeys(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"keys": keyInfos,
"cursor": nextCursor,
"cursor": 0,
"total": len(keyInfos),
})
}