feat(monitor): NATS 集群 Raft 副本健康 + 基建 ping 延迟(监测组完善)
此前 /status 把 NATS 当一盏二元灯(连不上就 fatal 故恒真),看不出 3 节点集群里 哪个节点掉了、JetStream 持久流的 Raft 副本是否还齐(计费/状态/评测流不丢的关键)。 DB/Redis/MinIO 也只二元 ping、无延迟。 - bus.ClusterStatus:连的节点名 + 集群发现节点数(nc.Servers) + RTT(nc.RTT) + 6 条关键 持久流(tasks/status/usage/eval/ingest/approvals)的 Raft 副本健康(leader + healthy/total, 单节点部署记 1/1;某节点掉队 → healthy<total 记降级)。 - /status 新增 nats 集群对象 + NATS 灯改为'连接且无副本降级才绿'、detail 显示'N 节点·连 X· 流副本齐全/降级'、latency=RTT;PG/Redis/MinIO 加 ping 往返耗时。 - admin StatusPage:新增 NATS 集群面板(节点/RTT/连接 + 各流 leader/副本健康/消息数表), 基建行显示延迟。 sundynix-shared/gateway build+vet+test 绿;admin tsc 绿。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
sharedbus "github.com/sundynix/sundynix-shared/bus"
|
||||
"github.com/sundynix/sundynix-shared/contract"
|
||||
)
|
||||
|
||||
@@ -35,12 +36,13 @@ type toolGroup struct {
|
||||
Tools []toolInfo `json:"tools"`
|
||||
}
|
||||
|
||||
// systemStatus 是「服务状态」面板的聚合视图:基建 / 应用服务 / MCP 工具注册。
|
||||
// systemStatus 是「服务状态」面板的聚合视图:基建 / 应用服务 / MCP 工具注册 / NATS 集群。
|
||||
type systemStatus struct {
|
||||
CheckedAt string `json:"checked_at"`
|
||||
Infra []statusItem `json:"infra"`
|
||||
Services []statusItem `json:"services"`
|
||||
Tools []toolGroup `json:"tools"`
|
||||
CheckedAt string `json:"checked_at"`
|
||||
Infra []statusItem `json:"infra"`
|
||||
Services []statusItem `json:"services"`
|
||||
Tools []toolGroup `json:"tools"`
|
||||
Nats *sharedbus.NATSClusterStatus `json:"nats,omitempty"` // NATS 集群详情(节点/RTT/流副本 Raft 健康)
|
||||
}
|
||||
|
||||
// probeTimeout 是各探针的单次超时(无响应即判为下线)。
|
||||
@@ -68,9 +70,11 @@ func (h *Handler) AdminStatus(c *gin.Context) {
|
||||
dispLatency int // dispatcher 探针耗时
|
||||
|
||||
pgUp, redisUp, minioUp bool // 基建活性探针(实时 ping,非仅启动标志)
|
||||
pgMs, redisMs, minioMs int // 各基建 ping 往返耗时(ms)
|
||||
natsClu sharedbus.NATSClusterStatus
|
||||
)
|
||||
|
||||
wg.Add(5)
|
||||
wg.Add(6)
|
||||
|
||||
// 1) mcp-go health → milvus / neo4j 基建灯
|
||||
go func() {
|
||||
@@ -127,15 +131,25 @@ func (h *Handler) AdminStatus(c *gin.Context) {
|
||||
})
|
||||
}()
|
||||
|
||||
// 5) 基建活性探针:PG / Redis / MinIO 实时 ping(非仅启动标志,能反映中途掉线)。
|
||||
// 5) 基建活性探针:PG / Redis / MinIO 实时 ping(非仅启动标志,能反映中途掉线)+ 往返耗时。
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
safeCall("status-probe-infra", func() {
|
||||
ctx, cancel := context.WithTimeout(parent, probeTimeout)
|
||||
defer cancel()
|
||||
pgUp = h.db.Ping(ctx)
|
||||
redisUp = h.cache.Ping(ctx)
|
||||
minioUp = h.blob != nil && h.blob.Ping(ctx)
|
||||
pgUp, pgMs = pingLatency(func() bool { return h.db.Ping(ctx) })
|
||||
redisUp, redisMs = pingLatency(func() bool { return h.cache.Ping(ctx) })
|
||||
minioUp, minioMs = pingLatency(func() bool { return h.blob != nil && h.blob.Ping(ctx) })
|
||||
})
|
||||
}()
|
||||
|
||||
// 6) NATS 集群体检:节点数 / RTT / 各关键流 Raft 副本健康(不再是一盏二元灯)。
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
safeCall("status-probe-nats", func() {
|
||||
ctx, cancel := context.WithTimeout(parent, probeTimeout)
|
||||
defer cancel()
|
||||
natsClu = h.bus.ClusterStatus(ctx)
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -144,16 +158,18 @@ func (h *Handler) AdminStatus(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, systemStatus{
|
||||
CheckedAt: time.Now().Format(time.RFC3339),
|
||||
Infra: []statusItem{
|
||||
{Name: "postgres", Up: pgUp},
|
||||
{Name: "redis", Up: redisUp},
|
||||
{Name: "nats", Up: true}, // 网关连不上 NATS 即 fatal,能应答即在线
|
||||
{Name: "postgres", Up: pgUp, Latency: pgMs},
|
||||
{Name: "redis", Up: redisUp, Latency: redisMs},
|
||||
// NATS:不再是二元灯——上报连的节点、集群节点数、RTT、有几条流副本降级。
|
||||
{Name: "nats", Up: natsClu.Connected && natsClu.Degraded == 0, Detail: natsDetail(natsClu), Latency: natsClu.RTTMillis},
|
||||
{Name: "milvus", Up: milvus},
|
||||
{Name: "neo4j", Up: neo4j},
|
||||
{Name: "minio", Up: minioUp}, // 对象存储(报告/KB 正文/blob,126)
|
||||
{Name: "minio", Up: minioUp, Latency: minioMs}, // 对象存储(报告/KB 正文/blob,126)
|
||||
// 全文索引:mcp-go 本地 bleve,是唯一不在 128 集中存储上的检索路,
|
||||
// 也是唯一会"静默降级"的一路(退内存后重启清零,检索只是变差不报错)。
|
||||
{Name: "全文索引", Up: goUp && ftDisk, Detail: fulltextDetail(goUp, ftDisk)},
|
||||
},
|
||||
Nats: &natsClu,
|
||||
Services: []statusItem{
|
||||
{Name: "gateway", Up: true, Detail: "在线"},
|
||||
{Name: "dispatcher", Up: dispUp, Detail: serviceDetail(dispUp, dispDetail), Latency: dispLatency},
|
||||
@@ -183,6 +199,35 @@ func (h *Handler) probeTools(parent context.Context, subject string) (up bool, t
|
||||
return true, payload.Tools, int(time.Since(start).Milliseconds())
|
||||
}
|
||||
|
||||
// pingLatency 跑一次 ping 并计时,返回 (是否可达, 往返毫秒)。不可达则耗时记 0。
|
||||
func pingLatency(ping func() bool) (bool, int) {
|
||||
start := time.Now()
|
||||
ok := ping()
|
||||
if !ok {
|
||||
return false, 0
|
||||
}
|
||||
return true, int(time.Since(start).Milliseconds())
|
||||
}
|
||||
|
||||
// natsDetail 把 NATS 集群体检拼成一行人读摘要:节点数 · 连的节点 · 流副本健康。
|
||||
func natsDetail(s sharedbus.NATSClusterStatus) string {
|
||||
if !s.Connected {
|
||||
return "未连接"
|
||||
}
|
||||
d := fmt.Sprintf("%d 节点", s.KnownServers)
|
||||
if s.ConnectedTo != "" {
|
||||
d += " · 连 " + s.ConnectedTo
|
||||
}
|
||||
if n := len(s.Streams); n > 0 {
|
||||
if s.Degraded > 0 {
|
||||
d += fmt.Sprintf(" · %d/%d 流副本降级(有节点掉队)", s.Degraded, n)
|
||||
} else {
|
||||
d += fmt.Sprintf(" · %d 流副本齐全", n)
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func dispatcherDetail(model string, ready bool, uptimeS int) string {
|
||||
d := "运行 " + humanDuration(uptimeS)
|
||||
if model != "" {
|
||||
|
||||
Reference in New Issue
Block a user