第一章:Go微服务聊天室架构白皮书概述
本白皮书定义一套基于 Go 语言构建的高可用、可扩展、易观测的微服务聊天室系统,面向实时消息传递场景,兼顾低延迟、强一致性与水平伸缩能力。系统采用领域驱动设计(DDD)划分核心边界,将用户管理、会话路由、消息投递、在线状态、通知推送等职责解耦为独立服务,各服务通过 gRPC 进行同步通信,借助 Redis Streams 和 Kafka 实现异步事件分发。
设计哲学与核心原则
- 轻量优先:每个服务二进制体积控制在
- 契约先行:所有 gRPC 接口使用
.proto文件定义,配合buf工具链校验兼容性; - 可观测即内置:默认集成 OpenTelemetry SDK,自动注入 trace ID 与 metrics 标签(如
service.name,message.type); - 故障隔离:服务间调用强制超时(默认 800ms),熔断器基于 circuitbreaker-go 实现,错误率阈值设为 5%。
关键组件技术选型
| 组件类别 | 选型 | 理由说明 |
|---|---|---|
| 服务注册发现 | Consul + DNS SRV | 支持健康检查、KV 存储配置、零依赖部署 |
| 消息中间件 | Kafka(3节点集群) | 高吞吐(≥50k msg/s)、精确一次语义保障 |
| 状态存储 | Redis Cluster | 在线状态/房间成员列表采用 Hash 结构,TTL 自动清理 |
| API 网关 | Kong + Go Plugin | 路由鉴权交由自研 JWT 插件处理,支持 WebSocket 协议透传 |
快速验证本地环境
执行以下命令一键拉起最小可行集群(需 Docker Desktop + Docker Compose v2.20+):
# 克隆并初始化示例仓库
git clone https://github.com/go-chatroom/architecture.git && cd architecture
# 启动 Consul、Kafka、Redis 及网关基础组件
docker compose -f docker-compose.infra.yml up -d
# 编译并运行用户服务(含 Swagger UI)
cd services/user && go build -o user-svc . && ./user-svc --config config.yaml
# 访问 http://localhost:8080/swagger/index.html 查看 REST API 文档
该流程验证服务注册、配置加载、HTTP/gRPC 双协议暴露及基础健康检查端点 /healthz 的连通性,为后续多服务联调奠定基础。
第二章:核心基础设施选型与Go语言集成实践
2.1 etcd分布式配置与服务发现的Go客户端深度封装
核心抽象:ClientBuilder模式
封装etcd/client/v3原始API,屏蔽连接池、重试策略、上下文超时等重复逻辑:
type ClientBuilder struct {
endpoints []string
dialTimeout time.Duration
keepAlive bool
}
func (b *ClientBuilder) Build() (*clientv3.Client, error) {
return clientv3.New(clientv3.Config{
Endpoints: b.endpoints,
DialTimeout: b.dialTimeout,
// 自动启用KeepAlive以维持长连接
DialKeepAliveTime: 10 * time.Second,
})
}
逻辑分析:
DialKeepAliveTime避免TCP连接被中间设备(如NAT网关)静默断开;DialTimeout防止初始化阻塞,需结合服务发现端点动态刷新机制。
关键能力矩阵
| 能力 | 原生API支持 | 封装后默认启用 | 说明 |
|---|---|---|---|
| 会话租约自动续期 | ✅ 手动调用 | ✅ 自动托管 | 绑定LeaseID与Key生命周期 |
| Watch事件去重 | ❌ | ✅ 基于Revision | 避免重复处理历史变更 |
| 批量原子操作 | ✅ | ✅ 封装为TxnBuilder |
支持条件写+多Key事务 |
数据同步机制
采用Watch监听 + Range全量校验双轨模型,确保配置强一致:
graph TD
A[Watch /config/] -->|Event Stream| B{Key变更}
B --> C[更新本地缓存]
B --> D[触发回调函数]
E[定时Range /config/] --> F[比对Revision]
F -->|不一致| G[强制全量重载]
2.2 gRPC服务定义与双向流式通信的Go实现范式
服务定义:.proto 中的双向流声明
service ChatService {
rpc BidirectionalChat(stream ChatMessage) returns (stream ChatMessage);
}
message ChatMessage {
string user_id = 1;
string content = 2;
int64 timestamp = 3;
}
该定义声明了全双工流式 RPC:客户端与服务端均可持续发送/接收 ChatMessage,无需请求-响应配对。stream 关键字在入参和返回类型中同时出现,是双向流的语法标志。
Go 服务端核心逻辑片段
func (s *chatServer) BidirectionalChat(stream pb.ChatService_BidirectionalChatServer) error {
for {
msg, err := stream.Recv() // 阻塞接收客户端消息
if err == io.EOF { return nil }
if err != nil { return err }
// 广播逻辑(简化)
if err := stream.Send(&pb.ChatMessage{
UserId: "server",
Content: "ACK: " + msg.Content,
Timestamp: time.Now().Unix(),
}); err != nil {
return err
}
}
}
Recv() 和 Send() 在同一 stream 实例上并发安全调用,底层基于 HTTP/2 流复用。注意需主动判 io.EOF 终止循环——这是 gRPC 流关闭的标准信号。
双向流典型适用场景对比
| 场景 | 单向流(Client/Server) | 双向流 |
|---|---|---|
| 实时日志推送 | ✅ 客户端单向订阅 | ⚠️ 冗余(无需回控) |
| 协同编辑会话 | ❌ 无法实时反馈光标位置 | ✅ 全链路低延迟同步 |
| IoT 设备远程诊断 | ❌ 无法动态下发指令 | ✅ 指令+状态双向交织 |
数据同步机制
双向流天然支持事件驱动的状态同步:任一端可随时触发 Send(),另一端通过 Recv() 实时消费。无须轮询或额外信令通道,HTTP/2 帧级多路复用保障吞吐与顺序性。
2.3 Redis Pub/Sub消息总线在Go中的可靠订阅与重连机制
核心挑战
网络抖动、Redis服务重启或客户端短暂失联,均会导致 SUBSCRIBE 连接中断且丢失消息——Pub/Sub 本身不提供消息持久化与投递确认。
可靠重连策略
- 使用指数退避(1s → 2s → 4s → max 30s)避免雪崩重连
- 在
redis.Conn断开后,先 UNSUBSCRIBE 再重建连接并重订阅,防止旧连接残留 - 维护订阅主题列表,确保重连后完整恢复监听
带心跳的订阅管理示例
func (s *Subscriber) reconnectLoop() {
for s.running {
if err := s.subscribeAll(); err != nil {
log.Printf("subscribe failed: %v, retry in %v", err, s.backoff)
time.Sleep(s.backoff)
s.backoff = min(s.backoff*2, 30*time.Second)
continue
}
s.backoff = time.Second // reset on success
break
}
}
逻辑说明:
subscribeAll()内部调用conn.Subscribe(topic...)并启动p.Subscribe().Channel()监听;backoff初始为1s,每次失败翻倍,上限 30s。重连成功即重置退避周期,保障快速恢复与系统友好性。
| 机制 | 是否解决消息丢失 | 备注 |
|---|---|---|
| 纯重连 | ❌ | 重连前发布的消息不可达 |
| 订阅前检查连接 | ✅(部分) | 需配合服务端消息缓冲设计 |
| 客户端ACK+重发 | ✅(需自建) | 超出Pub/Sub原生能力范畴 |
2.4 JWT+RBAC鉴权体系在Go微服务间的统一落地
统一鉴权网关层设计
所有微服务前置统一API网关,由其完成JWT解析、RBAC权限校验与上下文注入,避免各服务重复实现。
JWT载荷结构规范
type Claims struct {
jwt.StandardClaims
UserID uint `json:"uid"`
Username string `json:"username"`
Roles []string `json:"roles"` // 如 ["user", "admin"]
Scopes []string `json:"scopes"` // 如 ["order:read", "user:write"]
}
StandardClaims 提供标准过期(exp)、签发(iss)等字段;Roles 表示用户角色层级,Scopes 表示细粒度资源操作权限,二者协同实现RBAC+ABAC混合控制。
权限决策流程
graph TD
A[收到HTTP请求] --> B[网关解析JWT]
B --> C{Token有效?}
C -->|否| D[返回401]
C -->|是| E[提取Roles+Scopes]
E --> F[查策略引擎:角色→权限映射]
F --> G[匹配请求路径+方法]
G --> H[放行/403]
微服务间调用信任链
| 调用方 | 是否透传JWT | 是否校验Scope | 备注 |
|---|---|---|---|
| 网关→订单服务 | ✅ | ✅ | 强制校验 order:* |
| 订单服务→用户服务 | ✅ | ❌ | 内部调用仅验 internal 角色 |
| 用户服务→通知服务 | ❌ | — | 使用服务级mTLS+固定API Key |
2.5 Go Module依赖管理与多环境构建策略(dev/staging/prod)
Go Module 是 Go 官方依赖管理标准,通过 go.mod 声明模块路径与依赖版本,配合 go.sum 保障校验一致性。
环境感知构建
利用 -ldflags 注入编译时变量:
go build -ldflags="-X 'main.Env=prod' -X 'main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" -o myapp .
-X 将字符串常量注入 main 包的未导出变量,实现零配置环境标识;BuildTime 支持审计追踪。
多环境配置分层
| 环境 | GOPROXY | GOSUMDB | 构建标签 |
|---|---|---|---|
| dev | https://proxy.golang.org,direct |
off |
debug |
| prod | https://goproxy.cn,direct |
sum.golang.org |
release |
构建流程自动化
graph TD
A[git checkout] --> B[GOOS=linux GOARCH=amd64 go build]
B --> C{Env == prod?}
C -->|Yes| D[-ldflags -s -w]
C -->|No| E[-gcflags '-N -l']
D & E --> F[output binary]
第三章:聊天室核心微服务设计与Go实现
3.1 用户在线状态服务:基于etcd TTL与Watch机制的实时同步
核心设计思想
利用 etcd 的租约(Lease)自动过期能力,为每个用户会话绑定带 TTL 的 key;客户端定期续租,断连后 key 自动删除,天然表达“在线/离线”语义。
数据同步机制
客户端通过 Watch 监听 /online/{uid} 路径,任一节点变更(新增、删除、续租)均实时推送:
// 启动 Watch 并处理事件
watchChan := client.Watch(ctx, "/online/", clientv3.WithPrefix())
for resp := range watchChan {
for _, ev := range resp.Events {
switch ev.Type {
case mvccpb.PUT:
log.Printf("User %s online", path.Base(string(ev.Kv.Key)))
case mvccpb.DELETE:
log.Printf("User %s offline", path.Base(string(ev.Kv.Key)))
}
}
}
WithPrefix() 实现批量监听;ev.Type 区分状态变更类型;path.Base() 提取 UID —— 避免硬编码解析逻辑。
关键参数对照表
| 参数 | 推荐值 | 说明 |
|---|---|---|
| TTL | 30s | 网络抖动容忍窗口,兼顾实时性与稳定性 |
| 续租间隔 | 15s | TTL 的 1/2,留出网络延迟余量 |
| Watch 连接超时 | 60s | 防止长连接假死导致状态滞后 |
状态流转流程
graph TD
A[客户端上线] --> B[创建 Lease 并 Put /online/u123]
B --> C[启动 Watch 监听 /online/ 前缀]
C --> D{心跳续租?}
D -- 是 --> B
D -- 否 --> E[Lease 过期 → Key 自动删除]
E --> F[Watch 触发 DELETE 事件 → 全局广播离线]
3.2 消息路由网关:gRPC流式代理与连接池优化的Go实践
核心挑战
高并发场景下,gRPC双向流(Bidi Streaming)易因连接频繁重建导致延迟激增与服务端资源耗尽。需在代理层统一管理长连接生命周期。
连接池设计要点
- 复用底层
*grpc.ClientConn,按目标服务地址(host:port)分桶 - 设置最大空闲连接数(
MaxIdleConnsPerHost=50)与连接存活时间(IdleTimeout=30s) - 自动健康检查:通过轻量
Check()RPC 探活,失败时剔除并重建
流式代理核心逻辑
func (p *Proxy) HandleStream(ctx context.Context, stream pb.RouteService_RouteServer) error {
conn := p.pool.Get(stream.Context(), "backend:9000") // 从池获取连接
backendStream, err := pb.NewRouteServiceClient(conn).Route(ctx)
if err != nil { return err }
// 双向转发:proxy ↔ client ↔ backend
go p.forwardToBackend(stream, backendStream) // 客户端→后端
return p.forwardToClient(backendStream, stream) // 后端→客户端
}
此函数建立单次流代理会话:
p.pool.Get()触发连接复用或新建;forwardToBackend与forwardToClient并发协程实现零拷贝转发,避免缓冲区堆积。ctx传递保障全链路超时与取消传播。
性能对比(QPS/连接数)
| 配置 | QPS | 平均延迟 | 活跃连接数 |
|---|---|---|---|
| 无连接池(直连) | 1,200 | 48ms | 2,100 |
| 连接池(50空闲/30s) | 8,600 | 11ms | 47 |
graph TD
A[客户端gRPC流] --> B[Proxy.HandleStream]
B --> C{连接池Get<br/>host:port}
C -->|命中| D[复用现有Conn]
C -->|未命中| E[新建Conn并注册]
D & E --> F[NewRouteServiceClient]
F --> G[启动双向转发协程]
3.3 会话持久化服务:Redis Hash+Sorted Set结构的Go原子操作封装
会话数据需同时满足字段灵活扩展(如 user_id, last_active, ip)与按时间有序检索(如超时清理、活跃度排序)两大需求。单一 Redis 数据结构无法兼顾,故采用 Hash + Sorted Set 协同建模:
- Hash 存储会话明细(
sess:abc123→{"user_id":"u1","ip":"192.168.1.5"}) - Sorted Set 维护会话生命周期(
sess:active→score=unix_ms_timestamp,member=abc123)
原子写入封装
func (s *SessionStore) UpsertWithExpire(sessID string, fields map[string]string, ttlMs int64) error {
tx := s.client.TxPipeline()
tx.HSet(ctx, "sess:"+sessID, fields)
tx.ZAdd(ctx, "sess:active", &redis.Z{Score: float64(time.Now().UnixMilli()), Member: sessID})
tx.Expire(ctx, "sess:"+sessID, time.Duration(ttlMs)*time.Millisecond)
_, err := tx.Exec(ctx)
return err
}
逻辑分析:三命令打包为事务管道,确保会话元数据、有序索引、TTL 设置严格原子执行;
ttlMs控制 Hash 过期,而 Sorted Set 不设过期——依赖后台定时扫描ZRangeByScore清理 stale member。
关键参数说明
| 参数 | 类型 | 含义 |
|---|---|---|
sessID |
string | 全局唯一会话标识 |
fields |
map | 可变字段键值对(UTF-8) |
ttlMs |
int64 | Hash 过期毫秒数(非 ZSet) |
数据同步机制
graph TD
A[客户端请求] --> B[UpsertWithExpire]
B --> C[Hash写入+ZSet插入+TTL设置]
C --> D[Redis原子事务提交]
D --> E[异步Worker定时ZRemRangeByScore]
第四章:高可用与可观测性工程落地
4.1 基于Prometheus+Grafana的Go微服务指标埋点与告警规则
指标埋点实践
使用 prometheus/client_golang 在 HTTP 处理器中埋点:
var (
httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests.",
},
[]string{"method", "path", "status"},
)
)
func init() {
prometheus.MustRegister(httpRequestsTotal)
}
该代码注册带维度(method/path/status)的计数器,支持多维聚合分析;MustRegister 确保注册失败时 panic,避免静默丢失指标。
告警规则示例
在 alerts.yml 中定义高延迟告警:
| 名称 | 表达式 | 阈值 | 说明 |
|---|---|---|---|
HighHTTPDuration |
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 2 |
2s | P95 延迟超阈值 |
数据流向
graph TD
A[Go App] -->|expose /metrics| B[Prometheus Scraping]
B --> C[TSDB 存储]
C --> D[Grafana 可视化]
C --> E[Alertmanager 触发]
关键配置要点
- Prometheus 需配置
scrape_interval: 15s与目标服务/metrics端点 - Grafana 中需导入 Go Runtime Dashboard(ID: 16358)
- Alertmanager 配置邮件/企微通知路由
4.2 OpenTelemetry链路追踪在gRPC跨服务调用中的Go注入实践
gRPC拦截器注入原理
OpenTelemetry通过UnaryServerInterceptor和StreamServerInterceptor在gRPC服务端注入Span上下文,客户端则使用UnaryClientInterceptor传递traceID与spanID。
客户端拦截器实现
func otelUnaryClientInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
ctx, span := otel.Tracer("grpc-client").Start(ctx, method)
defer span.End()
// 将SpanContext注入gRPC metadata
md, _ := metadata.FromOutgoingContext(ctx)
md = md.Copy()
otel.GetTextMapPropagator().Inject(ctx, propagation.MapCarrier(md))
return invoker(metadata.NewOutgoingContext(ctx, md), method, req, reply, cc, opts...)
}
}
逻辑分析:该拦截器在每次gRPC调用前创建Span,并通过OpenTelemetry传播器将trace上下文写入metadata;propagation.MapCarrier实现W3C TraceContext格式序列化,确保跨进程透传。
服务端接收与续传
- 拦截器从
metadata中提取traceparent头 - 调用
otel.GetTextMapPropagator().Extract()还原SpanContext Tracer.Start()自动关联父Span,构建完整调用链
| 组件 | 作用 | 关键依赖 |
|---|---|---|
otel-go |
提供Tracer与Propagator | go.opentelemetry.io/otel |
grpc-go |
支持拦截器扩展 | google.golang.org/grpc |
graph TD
A[Client Call] --> B[UnaryClientInterceptor]
B --> C[Inject traceparent into metadata]
C --> D[gRPC Transport]
D --> E[UnaryServerInterceptor]
E --> F[Extract & resume Span]
4.3 日志聚合与结构化输出:Zap+Loki在K8s环境下的Go适配方案
结构化日志初始化
使用 Zap 的 Config 显式启用 JSON 编码与调用栈捕获,适配 Loki 的 labels 提取需求:
cfg := zap.Config{
Level: zap.NewAtomicLevelAt(zap.InfoLevel),
Encoding: "json",
EncoderConfig: zap.NewProductionEncoderConfig(),
OutputPaths: []string{"stdout"},
ErrorOutputPaths: []string{"stderr"},
}
logger, _ := cfg.Build()
Encoding: "json"确保字段扁平可被 Loki Promtail 的pipeline_stages解析;EncoderConfig默认禁用堆栈(需显式设EnableCaller: true才注入caller字段,供 Loki 按filename或function聚类)。
日志标签自动注入
K8s Pod 元信息通过 Downward API 注入环境变量,由 Zap 添加为静态字段:
| 环境变量 | 对应字段 | 用途 |
|---|---|---|
POD_NAME |
pod |
关联 Loki 查询标签 |
NAMESPACE |
namespace |
多租户隔离 |
NODE_NAME |
node |
宿主机级故障定位 |
数据同步机制
graph TD
A[Go App] -->|JSON over stdout| B[Promtail]
B -->|HTTP POST /loki/api/v1/push| C[Loki]
C --> D[Prometheus-compatible labels]
Promtail 配置提取 pod、namespace 作为流标签,确保 Loki 存储时天然支持 rate({job="my-go-app"}) 类查询。
4.4 熔断降级与限流控制:Go-kit熔断器与Sentinel Go SDK集成实战
在微服务高并发场景下,单一依赖故障易引发雪崩。Go-kit 的 breaker 提供轻量级熔断能力,而 Sentinel Go SDK 则提供更丰富的流控、熔断、系统自适应保护策略。
集成方式对比
| 维度 | Go-kit Breaker | Sentinel Go SDK |
|---|---|---|
| 熔断策略 | 简单滑动窗口 + 失败率阈值 | 慢调用比例/异常比例/RT阈值 |
| 限流模型 | 不支持原生限流 | QPS/并发线程数/匀速排队 |
| 配置动态化 | 静态初始化 | 支持 Nacos/Apollo 动态规则推送 |
熔断器桥接示例
// 将 Sentinel 的熔断结果映射为 Go-kit breaker.State
func sentinelToBreakerState(rt *sentinel.Rule) breaker.State {
switch rt.Threshold {
case 0.5: return breaker.HalfOpen // 示例映射逻辑
default: return breaker.Closed
}
}
该函数将 Sentinel 规则中的阈值语义转化为 Go-kit 熔断器状态,实现双框架协同决策。
流控熔断协同流程
graph TD
A[请求进入] --> B{Sentinel Check}
B -->|通过| C[执行业务]
B -->|拒绝| D[返回降级响应]
C --> E{是否异常/超时}
E -->|是| F[上报 Sentinel 指标]
F --> G[触发熔断规则评估]
G --> H[更新 Go-kit breaker 状态]
第五章:总结与演进路线图
核心成果回顾
在前四章中,我们完成了基于 Kubernetes 的微服务可观测性平台落地:接入 12 个核心业务服务(含订单、支付、库存模块),日均采集指标超 4.2 亿条,通过 Prometheus + Grafana 实现毫秒级延迟告警(P95
关键技术债清单
| 模块 | 当前状态 | 风险等级 | 修复窗口期 |
|---|---|---|---|
| 日志采集中继(Fluent Bit) | 单点部署,无高可用 | ⚠️ 高 | Q3 2024 |
| 跨集群 Prometheus 联邦 | 查询延迟波动 > 1.2s | 🟡 中 | Q4 2024 |
| Trace 数据冷热分层 | 全量存 ES,存储成本超预算 34% | ⚠️ 高 | Q3 2024 |
下一阶段演进路径
graph LR
A[Q3 2024] --> B[部署多副本 Fluent Bit+Kafka 缓冲]
A --> C[上线 Loki 冷数据归档至 S3]
D[Q4 2024] --> E[接入 eBPF 实时网络拓扑发现]
D --> F[集成 SigNoz 替代部分 Grafana 告警面板]
G[2025 Q1] --> H[构建 AI 异常检测模型(LSTM+Isolation Forest)]
G --> I[完成 Service Mesh 侧 Envoy Tracing 透传]
真实案例:电商大促压测优化
2024 年双 11 前压测中,平台捕获到支付服务在 12,000 TPS 下出现 Redis 连接池耗尽(redis.clients.jedis.exceptions.JedisConnectionException),通过 Flame Graph 定位到 PaymentService#processRefund() 方法中未复用 JedisPool 实例。团队在 48 小时内完成代码重构并灰度发布,最终大促期间支付成功率稳定在 99.997%,较去年提升 0.12 个百分点。
组织协同机制
- 每周三 10:00 召开“可观测性对齐会”,SRE 与开发负责人共同 Review 上周 Top 3 异常根因
- 建立“指标健康度看板”:自动计算各服务 SLI 达标率(如
/api/v1/order的 HTTP 2xx Rate ≥ 99.95%) - 开发者提交 PR 时强制触发
otel-checkCI 流程,验证 OpenTelemetry SDK 版本兼容性及 Span 命名规范
技术选型验证结论
在对比 Jaeger 与 Tempo 的分布式追踪能力时,针对同一笔跨 7 个服务的订单创建请求(TraceID: 0x8a3f2b1e),Tempo 在 200GB 日志量下查询 P99 响应时间为 1.8s,Jaeger 为 4.3s;但 Jaeger 的 Zipkin 兼容性更优,因此最终采用 Tempo 作为主存储,Jaeger 作为调试辅助工具共存部署。
成本优化实效
通过启用 Prometheus Remote Write + VictoriaMetrics 压缩存储,将 90 天指标保留成本降低 61%,单集群月均支出从 ¥12,800 降至 ¥4,950;同时将 Grafana 仪表盘加载速度从平均 3.7s 提升至 1.1s,用户操作响应符合 Google Core Web Vitals 标准。
生产环境灰度策略
新版本可观测性 Agent 采用渐进式发布:首周仅覆盖非核心服务(如客服系统、内部管理后台),第二周扩展至订单读服务(流量占比 15%),第三周才切入支付写链路;每阶段设置 72 小时黄金指标观察窗(错误率、延迟、CPU 使用率),任一指标突破阈值即自动回滚。
社区共建计划
已向 CNCF Sandbox 提交 k8s-otel-operator 项目提案,重点解决 Operator 在混合云环境下(AWS EKS + 阿里云 ACK)的自动证书轮换问题;当前已有 3 家企业(含某头部物流平台)确认参与联合测试,预计 2024 年底发布 v0.4.0 正式版。
