Bebebus 集群完善记:从 Crude 到 Production-Ready 的最后一段路

0 阅读1分钟

Bebebus 集群完善记:从 Crude 到 Production-Ready 的最后一段路

前三篇文章记录了集群搭建、故障注入、核心修复。本文记录最后的完善——把"能跑"的集群做到"生产级"。

1. MQTT 5.0 协议完整性

审计发现 5 个 CRITICAL 级别缺口:

PUBACK/PUBREC/PUBREL/PUBCOMP V5 decoder 缺失

MQTT 5.0 客户端发来的 QoS2 ACK 包的 ReasonCode 和 Properties 被静默丢弃——DecodePacketV5 里漏了这 4 个 packet type。

// 修复:在 switch 里补上 4 个 case
case *PubAckPacket: return p.decodeV5(bodyReader, header)
case *PubRecPacket: return p.decodeV5(bodyReader, header)
case *PubRelPacket: return p.decodeV5(bodyReader, header)
case *PubCompPacket: return p.decodeV5(bodyReader, header)

// 新增 decoder: 读 PacketID + ReasonCode + Properties
func (p *PubAckPacket) decodeV5(r io.Reader, header FixedHeader) (*PubAckPacket, error) {
    p.PacketID, _ = DecodeUint16(r)
    if header.RemainingLength > 2 {
        p.ReasonCode, _ = DecodeByte(r)
        p.Properties, _ = DecodeProperties(r)
    }
    return p, nil
}

CONNACK 不发送服务器能力

MQTT 5.0 客户端连上后不知道 broker 的限制——ReceiveMaximum=64? TopicAlias=10? Retain 支持? 全部没告诉客户端。

// 修复:CONNACK properties 包含全部能力
ack.Properties.Add(packet.PropReceiveMaximum, uint16(64))
ack.Properties.Add(packet.PropTopicAliasMaximum, uint16(10))
ack.Properties.Add(packet.PropMaximumQoS, byte(2))
ack.Properties.Add(packet.PropRetainAvailable, byte(1))
ack.Properties.Add(packet.PropWildcardSubscriptionAvailable, byte(1))
ack.Properties.Add(packet.PropSubscriptionIdentifierAvailable, byte(1))
ack.Properties.Add(packet.PropSharedSubscriptionAvailable, byte(1))
ack.Properties.Add(packet.PropMaximumPacketSize, uint32(268435456))

UNSUBACK 无 reason codes、Topic Alias 未解析、ReceiveMaximum 流控未执行

全部修复。

2. OpenTelemetry 全链路 Tracing

从零到完整 OTEL 集成:

配置层:
  --trace-exporter none|stdout|otlp

代码层:
  handleClient 入口:
    sessionCtx = WithTrace(bg)  → trace_id=AAA (无 span)

  read loop:
    pktCtx, pktSpan = Tracer.Start(sessionCtx, "mqtt.recv")
    handlePacket(pktCtx, pkt)       → ctx 作为第一参数透传
    pktSpan.End()

  handler:
    FromCtx(ctx).Info("processing") → 自动带 trace_id

跨节点:
    ForwardMessageRequest.trace_id ← TraceIDFromCtx(ctx)

关键设计决策:

  • session 级不创建 span(避免跨小时的 span)— 仅注入 trace_id
  • 每个 MQTT 包创建短 span(<10ms)
  • ctx 通过函数参数传递(不挂 struct field)
  • exporter 默认 none(日志带 trace),可切换 stdout/OTLP

3. 可观测性:API + Health + Dashboard

Cluster Info API 之前硬编码 nodes: 1。现在:

mgrServer.WithClusterProvider(&grpc.ClusterProvider{
    NodeCount: func() int { return len(clusterNode.Membership().Members()) },
    HealthChecks: func() map[string]string {
        return map[string]string{"cluster": "ok"}
    },
})

Dashboard 自动生效——之前请求 /api/v1/cluster 永远返回 1 节点,修完后自动显示 3 节点。

4. 并发控制 + goroutine 安全

位置修复
router.forwardToNode去掉了每条消息 go 的开销(Batcher.Send 是非阻塞的)
shard_store session syncasyncSubmit semaphore 限制 16 并发
RaftNode.Stopselect/channel 防 double-close panic
handleDisconnectnil session 检查移到 trace 创建之前

5. 测试体系

层级现有新增
单元测试77 文件core/node_test.go, transport/grpc/server_test.go
集成测试integration_test.goscripts/integration-test-phase1.sh
压力测试手动持续运行脚本

最终清单

能力状态
MQTT 3.1.1 + 5.0 全协议✅ CRITICAL 0 残留
QoS 0/1/2 跨节点✅ 批量 stream + 同步确认
MultiRaft 选举✅ 跨节点 leader election
Session 跨节点✅ Raft async replication
OTEL Tracing✅ 三档 exporter
日志系统✅ 组件 logger + trace ctx
Dashboard✅ 3 节点显示
压力测试✅ 4800 msg/s, 0 errors
测试覆盖⚠️ 核心路径已覆盖
文档✅ 3 篇掘金 + README + 测试计划

项目地址:github.com/JuniaWonter…