Skip to content

contrib/registry/nacos 存在数据竞争问题 #4649

@lingcoder

Description

@lingcoder

问题描述

在 PR #4608 中引入的 SetDefaultMetadata 方法存在数据竞争问题。当 RegisterSetDefaultMetadata 并发调用时,会触发 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 方案

  1. 语义不匹配SetDefaultMetadataSetClusterNameSetGroupName 这些方法的设计意图是初始化时配置,而非运行时动态修改。使用 Mutex 会给调用者传递错误的信号——暗示这些方法可以在运行时安全地并发调用。

  2. Set 方法在 Register 后调用无实际效果:这些配置只影响后续的 Register 调用。对于已经注册的服务,修改这些值不会产生任何效果(不会更新已注册服务的元数据)。这进一步证明它们是初始化配置,而非运行时配置。

  3. 不必要的运行时开销:如果这些值只在初始化时设置一次,引入 Mutex 会带来不必要的锁开销。

  4. 与 Go 惯用模式不一致:Go 社区对于初始化配置普遍使用 Option 模式(如 grpc.DialDialOption),而非 Set 方法。

建议方案

采用 Option 模式重构,保持与 etcdpolaris 等其他注册中心实现的一致性:

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
}

关于接口兼容性

SetClusterNameSetGroupNameSetDefaultMetadata 这些方法不是 gsvc.Registry 接口的一部分,仅是 nacos 实现的额外便利方法。改用 Option 模式不会破坏接口契约,与 consul 等其他注册中心的实现方式保持一致。

关于 SetClusterName 和 SetGroupName

这两个方法自 nacos 注册中心首次引入时就存在(PR #2995,2023年10月,v2.6.x),已有较长历史。为避免影响现有用户,建议:

  • 保留这两个方法,但标记为 deprecated
  • 新增对应的 Option 函数
  • 在文档中引导用户使用 Option 模式

相关 PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    awesomeIt's awesome! We keep watching.bugIt is confirmed a bug, but don't worry, we'll handle it.contribabout gf contrib

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions