From f19a84f37ea7db7b15e4cf5be40ede0576dfca61 Mon Sep 17 00:00:00 2001 From: opsx Date: Mon, 26 Oct 2026 18:01:44 +0000 Subject: [PATCH] =?UTF-8?q?fix(redis):=20=E9=9B=86=E7=BE=A4=E6=8B=93?= =?UTF-8?q?=E6=89=91=E8=A7=A3=E6=9E=90=E9=94=99=E8=AF=AF=EF=BC=8C=E8=B7=A8?= =?UTF-8?q?=E6=9C=BA=E9=9B=86=E7=BE=A4=E8=8A=82=E7=82=B9=E8=A2=AB=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E6=8C=87=E5=90=91=E7=A7=8D=E5=AD=90=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【现象】Redis 集群实例查 keys 报 500: Redis cluster connection failed: dial tcp 172.19.16.16:6005: connect: connection refused 【根因】handler/redis.go 解析 CLUSTER SLOTS: nodeAddr := slot.Nodes[0].Addr // Redis 上报的真实地址 172.19.16.30:6005 if idx := strings.LastIndex(nodeAddr, ":"); idx >= 0 { nodeAddr = instance.Host + nodeAddr[idx:] // 强制改写成 种子机主机 + 原端口 } 该逻辑假设「集群所有节点在同一台机器、只是端口不同」。 对跨机集群(16/30/59 三台)会把所有节点地址破坏成 172.19.16.16:6002 / :6003 / :6005 —— 这些端口在 16 上根本不存在 → refused。 【修复】 仅当 Redis 上报空 IP / 127.0.0.1 / 0.0.0.0 时才用配置 host 兜底(NAT/端口映射场景), 其余情况一律保留 Redis 上报的真实主机地址。 【附带数据修复(非代码)】 实例 5 host: pre-mysql.astx.club → 172.19.16.2 原因:pre-mysql.astx.club 公网 DNS 解析到 43.135.53.180(另一环境 MySQL), 认证失败 → 前端数据库下拉为空、无法选择数据库。 【验证】 GET /api/v1/redis/4/keys?db=0&pattern=* → 200 {"cluster":true,"keys":[{"key":"24H_rate_limit_10001_...","type":"string","ttl":42901000}, {"key":"CEX:FOLLOW:FOLLOWERS:TRADFI:97127836","type":"hash"},...]} GET /api/v1/redis/4/dbs → 200 {"cluster":true,"dbs":[0]} GET /api/v1/instances/5/databases → 200 返回 25 个库 ["cex_activity","cex_admin","cex_agent",...,"cex_user","cex_xxl_job"] 镜像 sha256:165992ef79d5 --- backend/internal/handler/redis.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/internal/handler/redis.go b/backend/internal/handler/redis.go index 4112ea6..f931b5e 100644 --- a/backend/internal/handler/redis.go +++ b/backend/internal/handler/redis.go @@ -3,6 +3,7 @@ package handler import ( "context" "fmt" + "net" "net/http" "strconv" "strings" @@ -393,10 +394,15 @@ func getRedisClient(instance *model.Instance, db int) (*redisClientWrapper, erro if len(slot.Nodes) == 0 { continue } - nodeAddr := slot.Nodes[0].Addr - if idx := strings.LastIndex(nodeAddr, ":"); idx >= 0 { - nodeAddr = instance.Host + nodeAddr[idx:] - } + nodeAddr := slot.Nodes[0].Addr + // 只在 Redis 上报空/回环地址时才用配置 host 兜底(NAT / 端口映射场景)。 + // 多机集群必须保留 Redis 上报的真实主机,否则会把跨机节点全部指向种子机, + // 导致 "dial tcp <种子机>:<其他机器的端口>: connection refused"。 + if h, p, err := net.SplitHostPort(nodeAddr); err == nil { + if h == "" || h == "127.0.0.1" || h == "0.0.0.0" { + nodeAddr = net.JoinHostPort(instance.Host, p) + } + } if !seen[nodeAddr] { seen[nodeAddr] = true addrs = append(addrs, nodeAddr)