feat(bus): JetStream 流/KV 副本数可配(NATS_STREAM_REPLICAS,默认1)

三机/单机多节点集群部署需 Replicas=3,durable 流才有 Raft quorum 容错(计费/状态/
用量/评测/审批/入库回写不因单节点挂掉而丢)。此前 6 处 CreateOrUpdateStream + 1 处
KV 桶都未设 Replicas(默认1),集群等于白搭。加 streamReplicas() 读 env(合法值 1/3/5),
7 处统一带上。默认 1 保证单机/CI 不变。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-18 14:45:24 +08:00
parent 28a1543249
commit 2e02a12260
+19
View File
@@ -113,6 +113,7 @@ func (b *Bus) EnsureTaskStream(ctx context.Context) error {
Name: contract.StreamTasks,
Subjects: []string{contract.SubjectTasksAll},
Storage: jetstream.FileStorage,
Replicas: streamReplicas(),
})
return err
}
@@ -341,6 +342,7 @@ func (b *Bus) EnsureStatusStream(ctx context.Context) error {
Name: contract.StreamStatus,
Subjects: []string{contract.SubjectTaskStatus},
Storage: jetstream.FileStorage,
Replicas: streamReplicas(),
MaxAge: 24 * time.Hour, // 状态一天内必被消费,过期回收防无限增长
})
return err
@@ -399,6 +401,7 @@ func (b *Bus) EnsureEvalStream(ctx context.Context) error {
Name: contract.StreamEval,
Subjects: []string{contract.SubjectEval},
Storage: jetstream.FileStorage,
Replicas: streamReplicas(),
MaxAge: 24 * time.Hour, // 评测一天内必被消费,过期回收防无限增长
})
return err
@@ -453,6 +456,7 @@ func (b *Bus) EnsureUsageStream(ctx context.Context) error {
Name: contract.StreamUsage,
Subjects: []string{contract.SubjectUsage},
Storage: jetstream.FileStorage,
Replicas: streamReplicas(),
MaxAge: 72 * time.Hour, // 计费留足追赶窗口(网关长时间离线后仍能补齐)
})
return err
@@ -520,6 +524,7 @@ func (b *Bus) EnsureApprovalStream(ctx context.Context) error {
Name: contract.StreamApprovals,
Subjects: []string{contract.SubjectApprovalAll},
Storage: jetstream.FileStorage,
Replicas: streamReplicas(),
MaxAge: 24 * time.Hour,
})
return err
@@ -774,6 +779,18 @@ func DrainTimeout() time.Duration {
return 30 * time.Second
}
// streamReplicas 是 JetStream 流/KV 桶的副本数,经 NATS_STREAM_REPLICAS 配置,缺省 1。
// 单机(单节点 NATS)保持 1;三机/单机多节点集群部署设 3,durable 流才有 Raft quorum 容错
// (计费/状态/用量/评测/审批/入库回写不会因单节点挂掉而丢)。合法值 1/3/5(Raft 需奇数)。
func streamReplicas() int {
if v := os.Getenv("NATS_STREAM_REPLICAS"); v != "" {
if n, err := strconv.Atoi(v); err == nil && (n == 1 || n == 3 || n == 5) {
return n
}
}
return 1
}
// drainWait 停止接新活后,等待在途 WaitGroup 跑完,或 dctx 到期放弃。
func drainWait(wg *sync.WaitGroup, dctx context.Context) {
done := make(chan struct{})
@@ -890,6 +907,7 @@ func (b *Bus) EnsureIngestStream(ctx context.Context) error {
Name: contract.StreamIngest,
Subjects: []string{contract.SubjectIngestAll},
Storage: jetstream.FileStorage,
Replicas: streamReplicas(),
MaxAge: ingestStreamMaxAge,
})
return err
@@ -1004,6 +1022,7 @@ func (b *Bus) Checkpoints(ctx context.Context, bucket string, ttl time.Duration)
kv, err := b.js.CreateOrUpdateKeyValue(ctx, jetstream.KeyValueConfig{
Bucket: bucket,
Storage: jetstream.FileStorage,
Replicas: streamReplicas(),
TTL: ttl,
})
if err != nil {