Skip to content

[Bug] redo_service 重连时使用首次缓存的 Instance 覆盖服务端 metadata,无 API 可更新缓存 #327

Description

@lengmodkx

问题描述

我使用 NacosNamingService.register_instance() 注册了一个临时实例,metadata 里带了 Dubbo 3 协议需要的字段(如 interfacemethodsversion 等)。

现象:首次注册成功后,如果我通过 Nacos 控制台手动修正了 metadata,或者重新调用 register_instance() 更新了 metadata——只要 gRPC 连接发生断连重连,redo_service 就会用第一次注册时缓存的旧 Instance 数据重新注册,覆盖掉我修正后的 metadata。

阅读源码后确认了原因:

源码分析

register_ephemeral_instance() 位于 v2/nacos/naming/remote/naming_grpc_client_proxy.py:126

async def register_ephemeral_instance(self, service_name, group_name, instance):
    # ① 先把 Instance(含 metadata)缓存到 redo
    await self.redo_service.cache_instance_for_redo(service_name, group_name, instance)
    # ② 发送注册请求
    request = InstanceRequest(...)
    response = await self.request_naming_server(request, InstanceResponse)
    # ③ 标记为已注册
    await self.redo_service.instance_registered(service_name, group_name)

cache_instance_for_redo() 位于 v2/nacos/naming/redo/naming_grpc_redo_service.py,把 Instance 原样存入 _redo_data_map

当连接断开时,AbstractRedoService.on_disconnect() 只把所有缓存条目标记为"未注册",但 Instance 数据本身不更新。重连后,process_instance_register_redo_type() 直接用这个旧的 Instance 重新注册:

async def process_instance_register_redo_type(self, instance_redo_data):
    data = instance_redo_data.get()  # ← 缓存的旧 Instance,metadata 是旧的
    await self.proxy.register_instance(service_name, group_name, data)

关键矛盾nacos_naming_service.py 提供了 update_instance() 方法(它内部也调 register_instance(),也会 cache_instance_for_redo() 覆盖缓存),但如果用户是通过以下方式修改 metadata,缓存就不会更新:

  1. 通过 Nacos 控制台手动改 metadata —— SDK 不知道,缓存不变
  2. 外部 API / 另一个 SDK 修改 —— SDK 不知道,缓存不变
  3. deregister_instance()register_instance() —— deregister 把缓存标记为"预期已注销",但 register 时缓存会被新 Instance 覆盖(这个路径理论上可以,但存在时序窗口)

复现步骤

  1. Python 进程调用 register_instance(),metadata 为 {"version": "1.0"}
  2. 在 Nacos 控制台把该实例的 metadata 改成 {"version": "2.0"}
  3. 断开 gRPC 连接(kill Nacos 再重启,或等网络空闲超时)
  4. Python SDK 重连后,Nacos 控制台的 metadata 又变回了 {"version": "1.0"}

期望行为

至少提供一种方式让用户可以更新 redo 缓存中的 Instance metadata,例如:

  • update_instance() 应该被文档明确推荐
  • 或者提供一个 refresh_redo_cache() 公开方法
  • 或者在 redo 重注册前,先从 Nacos 服务端拉取当前 Instance 做对比

当前 Workaround

只能杀掉 Python 进程重开,让 redo 缓存从零开始,用正确的新 metadata 注册。

影响

所有需要运行时调整 metadata(如 Dubbo 协议参数、灰度标签、权重等)的场景都会受影响——redo 会悄无声息地给你覆盖回去。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions