问题描述
当服务同时满足以下两个条件时,会触发此 Bug:
- 设置了非空的
Metadata,例如 kratos.Metadata(map[string]string{"weight": "10"})
- 注册了多个 endpoint,例如同时注册
grpc:// 和 http://
register.go 中的 Endpoints() 函数在循环构建 Eureka 实例时,将 service.Metadata 的引用(而非副本)赋给了每个 Eureka Endpoint 结构体的 MetaData 字段。导致每次循环都在同一个 map 上写入 metadata["Endpoints"],后一次覆盖前一次,最终所有注册到 Eureka 的实例的"Endpoints" 元数据均为最后一个 endpoint 的值
消费方(如 system-perm)通过服务发现解析时,gRPC resolver 找不到grpc:// scheme 的地址,打印如下警告并拒绝更新地址列表:
[resolver] Zero endpoint found,refused to write, instances:
[service-name-id service-name-id]
后续 gRPC 调用因无可用地址而等待超时,最终报错:
rpc error: code = DeadlineExceeded desc = context deadline exceeded
复现步骤
- 启动服务端,同时注册 gRPC 和 HTTP 两个 endpoint,并设置非空
Metadata:
kratos.Metadata(map[string]string{"weight": "10"}),
kratos.Endpoint(
grpcURL, // grpc://192.168.1.100:9000
httpURL, // http://192.168.1.100:8000
)
-
启动消费方,通过 discovery:///service-name
方式调用上述服务(gRPC)。
-
发起任意 gRPC 调用。
实际结果: rpc error: code = DeadlineExceeded desc = context deadline exceeded
根本原因
register.go 中 Endpoints() 函数:
// https://github.com/go-kratos/kratos/blob/main/contrib/registry/eureka/register.go#L113-L116
for _, ep := range service.Endpoints {
metadata := make(map[string]string)
if len(service.Metadata) > 0 {
metadata = service.Metadata // ← 仅复制引用,非 deep copy
}
// ...
metadata["Endpoints"] = ep // 第1轮: "grpc://..."
// 第2轮: "http://..." →覆盖了第1轮!
res = append(res, Endpoint{
MetaData: metadata, // 所有 Endpoint 共享同一个底层 map
})
}
当 service.Metadata 非空时,所有循环迭代均指向同一个map,"Endpoints" 键的最终值始终为 slice 中最后一个 endpoint(即 http://),gRPC 消费方因此无法发现 grpc:// 地址。
期望行为
每个生成的 Eureka Endpoint 结构体应持有独立的 metadata map,并包含正确的 "Endpoints" 值。
修复方案
metadata := maps.Clone(service.Metadata) // deep copy,非引用复制
if metadata == nil {
metadata = make(map[string]string)
}
环境信息
github.com/go-kratos/kratos/contrib/registry/eureka/v2 v2.0.0-20260404020628-f149714c1d54
github.com/go-kratos/kratos/v2 v2.9.2
- Go
1.24
- Eureka Server:Spring Boot Eureka(标准 REST API,
namespace: "eureka")
问题描述
当服务同时满足以下两个条件时,会触发此 Bug:
Metadata,例如kratos.Metadata(map[string]string{"weight": "10"})grpc://和http://register.go中的Endpoints()函数在循环构建 Eureka 实例时,将service.Metadata的引用(而非副本)赋给了每个 EurekaEndpoint结构体的MetaData字段。导致每次循环都在同一个 map 上写入metadata["Endpoints"],后一次覆盖前一次,最终所有注册到 Eureka 的实例的"Endpoints"元数据均为最后一个 endpoint 的值消费方(如
system-perm)通过服务发现解析时,gRPC resolver 找不到grpc://scheme 的地址,打印如下警告并拒绝更新地址列表:后续 gRPC 调用因无可用地址而等待超时,最终报错:
复现步骤
Metadata:
启动消费方,通过
discovery:///service-name方式调用上述服务(gRPC)。
发起任意 gRPC 调用。
实际结果:
rpc error: code = DeadlineExceeded desc = context deadline exceeded根本原因
register.go中Endpoints()函数:当
service.Metadata非空时,所有循环迭代均指向同一个map,"Endpoints"键的最终值始终为 slice 中最后一个 endpoint(即http://),gRPC 消费方因此无法发现grpc://地址。期望行为
每个生成的 Eureka
Endpoint结构体应持有独立的 metadata map,并包含正确的"Endpoints"值。修复方案
环境信息
github.com/go-kratos/kratos/contrib/registry/eureka/v2 v2.0.0-20260404020628-f149714c1d54github.com/go-kratos/kratos/v2 v2.9.21.24namespace: "eureka")