<p>C# 无内置服务发现,需依赖第三方或环境集成;Aspire 通过 ServiceDiscovery + Docker DNS 实现自动解析;Consul 注册须确保 ID 唯一、Address 可达、健康检查返回 200;客户端需用 AddHttpClient().AddServiceDiscovery() 或 IConsulClient 封装调用。</p>

C# 本身没有 ServiceDiscovery 类,也没有内置服务发现机制——所有“自动发现服务”的能力都来自第三方组件或运行环境的集成支持。 直接调用 ServiceDiscovery.Register() 或 ServiceDiscovery.GetServices("orders") 会编译失败或抛出 NullReferenceException / MethodNotFoundException。你得先明确自己处在哪个技术栈里:是纯 ASP.NET Core、Aspire、Steeltoe、Consul.NET,还是 Kubernetes 原生环境。
Aspire 里为什么 https://orders 能直接访问?
Aspire 不是实现了服务发现协议,而是通过 Microsoft.Extensions.ServiceDiscovery + Docker Compose DNS 自动注入实现的地址解析。它只在 Aspire 托管的资源之间生效。
- 必须用
builder.AddProject<MyApi>("orders")或builder.AddContainer("orders", "myorders:1.0")显式声明服务名,否则不会注册到内部服务目录 - HTTP 客户端必须注册为带服务发现能力的实例:
builder.Services.AddHttpClient<OrderClient>().AddServiceDiscovery();裸new HttpClient()会走系统 DNS,找不到orders - 本地开发(
dotnet run)时,https://orders解析为http://localhost:5042这类动态端口;Docker Compose 模式下则解析为容器内 DNS 名http://orders:8080,硬编码localhost必然失败 - gRPC 客户端同理,需用
AddGrpcClient<OrderService.OrderServiceClient>().AddServiceDiscovery()
Consul 注册后查不到服务?先检查这三处
Consul UI 看不到服务,或 Health.Service("orders") 返回空列表,90% 是注册参数填错,不是网络不通。
-
ID必须全局唯一:多个实例共用同一ID会导致后注册的覆盖前一个;推荐格式:$"orders-{Environment.ProcessId}-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}" -
Address不能写127.0.0.1或localhost:其他服务要能访问到它,就得填 Pod IP、宿主机 IP 或可解析的 DNS 名;K8s 中建议用Service名(如orders.default.svc.cluster.local) - 健康检查路径必须返回 HTTP 200 且不被中间件拦截:ASP.NET Core 中若用
[HttpGet("/health")]但没加[AllowAnonymous],会被认证中间件返回 401,Consul 就判定服务不健康;建议单独开一个裸路由,如/healthz,直接return Ok()
用 IHttpClientFactory 封装 Consul 服务发现调用
别在业务代码里手写 HttpClient 去调 http://consul:8500/v1/health/service/orders —— 这会让服务发现逻辑和业务强耦合,还容易引发连接池耗尽、DNS 缓存不刷新等问题。
- 注册命名客户端:
services.AddHttpClient("consul-service-client"),并配置超时与重试策略 - 服务发现逻辑应封装在独立服务中(如
IOrderServiceResolver),用IConsulClient(Singleton)查Health.Service("orders", tag: "api"),只取ServiceEntry.Service.Address和ServiceEntry.Service.Port拼 URL - 服务列表必须缓存:用
MemoryCache存 30 秒,Key 为"orders-api",避免高频轮询压垮 Consul - Watch 机制必须单例启动且显式调用
.Start():Consul 的 blocking query 不是“监听”,而是长轮询,漏掉Start()就等于没启用实时刷新
本地开发 Consul 连不上?ACL 和地址配置最常踩坑
错误信息如 Unexpected response code: 403 或 HttpRequestException: Connection refused,基本锁定在初始化阶段。
- Consul Agent 未开
-dev模式时,默认启用 ACL,写操作(如服务注册)会被拒绝;开发阶段加consul agent -dev -client=0.0.0.0最省事 -
ConsulClient初始化必须显式指定Address:本地开发用http://127.0.0.1:8500(不用localhost),Docker Compose 用http://consul:8500,K8s 用http://consul-server.default.svc.cluster.local:8500 -
ConsulClient应注册为 Singleton:它内部管理HttpClient,线程安全,重复 new 会浪费连接资源 - 别用已停更的
Consul.NET包;官方ConsulNuGet 包(作者 alexstockton)才支持 .NET 6+ 生命周期管理
真正难的不是写注册代码,而是让服务“活着被看见”:注册时地址填对、健康检查真实可达、查询时过滤条件匹配、缓存与刷新节奏合理。很多故障不是发现不了,而是发现后立刻失效——比如健康检查间隔设成 60 秒,而服务实际 5 秒就挂了,这 55 秒的流量黑洞,比完全发现不了更危险。


















