-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
问题描述
在 PR #4608 中引入的 SetDefaultMetadata 方法存在数据竞争问题。当 Register 和 SetDefaultMetadata 并发调用时,会触发 Go 的 race detector。
CI 失败链接
https://github.com/gogf/gf/actions/runs/21207538002/job/61007573612
复现方式
运行 contrib/registry/nacos/v2 的测试并开启 race 检测:
go test -race ./...问题根因
Registry 结构体中的 defaultMetadata 字段没有并发保护:
// nacos.go
func (reg *Registry) SetDefaultMetadata(metadata map[string]string) *Registry {
reg.defaultMetadata = metadata // 写操作,无锁保护
return reg
}
// nacos_register.go
func (reg *Registry) Register(ctx context.Context, service gsvc.Service) (...) {
for k, v := range reg.defaultMetadata { // 读操作,无锁保护
metadata[k] = v
}
}为什么不建议使用 Mutex 方案
-
语义不匹配:
SetDefaultMetadata、SetClusterName、SetGroupName这些方法的设计意图是初始化时配置,而非运行时动态修改。使用 Mutex 会给调用者传递错误的信号——暗示这些方法可以在运行时安全地并发调用。 -
Set 方法在 Register 后调用无实际效果:这些配置只影响后续的
Register调用。对于已经注册的服务,修改这些值不会产生任何效果(不会更新已注册服务的元数据)。这进一步证明它们是初始化配置,而非运行时配置。 -
不必要的运行时开销:如果这些值只在初始化时设置一次,引入 Mutex 会带来不必要的锁开销。
-
与 Go 惯用模式不一致:Go 社区对于初始化配置普遍使用 Option 模式(如
grpc.Dial的DialOption),而非 Set 方法。
建议方案
采用 Option 模式重构,保持与 etcd、polaris 等其他注册中心实现的一致性:
type Option func(*Registry)
func WithDefaultMetadata(metadata map[string]string) Option {
return func(r *Registry) {
r.defaultMetadata = metadata
}
}
func New(client naming_client.INamingClient, opts ...Option) *Registry {
reg := &Registry{client: client}
for _, opt := range opts {
opt(reg)
}
return reg
}关于接口兼容性
SetClusterName、SetGroupName、SetDefaultMetadata 这些方法不是 gsvc.Registry 接口的一部分,仅是 nacos 实现的额外便利方法。改用 Option 模式不会破坏接口契约,与 consul 等其他注册中心的实现方式保持一致。
关于 SetClusterName 和 SetGroupName
这两个方法自 nacos 注册中心首次引入时就存在(PR #2995,2023年10月,v2.6.x),已有较长历史。为避免影响现有用户,建议:
- 保留这两个方法,但标记为 deprecated
- 新增对应的 Option 函数
- 在文档中引导用户使用 Option 模式
相关 PR
- feat(contrib/registry/nacos): add SetDefaultEndpoint and SetDefaultMetadata methods #4608 - 引入
SetDefaultMetadata的 PR