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:
@@ -103,6 +103,78 @@ func waitConnected(nc *nats.Conn, d time.Duration) bool {
|
||||
// IsConnected 报告 NATS 连接此刻是否真的可用(供 readiness 探针;非仅启动时连过)。
|
||||
func (b *Bus) IsConnected() bool { return b.nc != nil && b.nc.IsConnected() }
|
||||
|
||||
// StreamHealth 是一条 JetStream 流的 Raft 副本健康。单节点部署 Cluster 为空 → 记 1/1。
|
||||
type StreamHealth struct {
|
||||
Name string `json:"name"`
|
||||
Leader string `json:"leader"`
|
||||
ReplicasHealthy int `json:"replicas_healthy"`
|
||||
ReplicasTotal int `json:"replicas_total"`
|
||||
Messages uint64 `json:"messages"`
|
||||
}
|
||||
|
||||
// NATSClusterStatus 是 NATS 骨干网的实时集群视图(供监测面板:不再是一盏二元灯)。
|
||||
type NATSClusterStatus struct {
|
||||
Connected bool `json:"connected"`
|
||||
ConnectedTo string `json:"connected_to"` // 当前连的节点名
|
||||
KnownServers int `json:"known_servers"` // 集群发现到的节点数(3 节点集群应为 3)
|
||||
RTTMillis int `json:"rtt_ms"` // 到当前节点的往返耗时
|
||||
Streams []StreamHealth `json:"streams"` // 关键持久流的副本健康
|
||||
Degraded int `json:"degraded"` // 副本未满(有节点掉队)的流数
|
||||
}
|
||||
|
||||
// clusterStreams 是要体检的关键持久流(计费/状态/评测/任务/入库/审批都不能丢)。
|
||||
var clusterStreams = []string{
|
||||
contract.StreamTasks, contract.StreamStatus, contract.StreamUsage,
|
||||
contract.StreamEval, contract.StreamIngest, contract.StreamApprovals,
|
||||
}
|
||||
|
||||
// ClusterStatus 探 NATS 集群实时状态:连的节点、已知节点数、RTT,以及各关键流的 Raft 副本健康。
|
||||
// 3 节点集群里某节点掉线 → 对应流 ReplicasHealthy < Total(仍有 quorum 可服务,但已降级须告警)。
|
||||
func (b *Bus) ClusterStatus(ctx context.Context) NATSClusterStatus {
|
||||
st := NATSClusterStatus{}
|
||||
if b.nc == nil {
|
||||
return st
|
||||
}
|
||||
st.Connected = b.nc.IsConnected()
|
||||
st.ConnectedTo = b.nc.ConnectedServerName()
|
||||
st.KnownServers = len(b.nc.Servers())
|
||||
if rtt, err := b.nc.RTT(); err == nil {
|
||||
st.RTTMillis = int(rtt.Milliseconds())
|
||||
}
|
||||
for _, name := range clusterStreams {
|
||||
s, err := b.js.Stream(ctx, name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
info, err := s.Info(ctx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
sh := StreamHealth{Name: name, Messages: info.State.Msgs}
|
||||
if info.Cluster != nil {
|
||||
sh.Leader = info.Cluster.Leader
|
||||
total := 1 + len(info.Cluster.Replicas) // leader + 副本
|
||||
healthy := 0
|
||||
if sh.Leader != "" {
|
||||
healthy++ // leader 在即算健康 1 个
|
||||
}
|
||||
for _, p := range info.Cluster.Replicas {
|
||||
if p.Current && !p.Offline {
|
||||
healthy++
|
||||
}
|
||||
}
|
||||
sh.ReplicasHealthy, sh.ReplicasTotal = healthy, total
|
||||
} else {
|
||||
sh.ReplicasHealthy, sh.ReplicasTotal, sh.Leader = 1, 1, st.ConnectedTo // 单节点
|
||||
}
|
||||
if sh.ReplicasHealthy < sh.ReplicasTotal {
|
||||
st.Degraded++
|
||||
}
|
||||
st.Streams = append(st.Streams, sh)
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
// Close 关闭底层连接。
|
||||
func (b *Bus) Close() {
|
||||
if b.nc != nil {
|
||||
|
||||
Reference in New Issue
Block a user