gRPC 名称解析(Name Resolution)全指南:URI 命名语法与 Resolver 插件机制
gRPC 客户端在创建 Channel 时,需要把一个"目标名称"(target)解析为真实的服务器地址,这一过程由名称解析系统(Name Resolution)完成。gRPC 以 DNS 为默认名称系统,同时通过一套通用的 URI 语法与可插拔的 Resolver 插件机制,支持 Unix Domain Socket、VSOCK、固定 IP 等多种命名与解析方式。本文基于当前仓库 doc/naming.md,结合 C-core 源码(src/core/resolver/)深度梳理名称语法、默认端口与解析器注册分发机制,帮助你正确构造 Channel target,理解解析结果如何影响负载均衡与服务配置。
概述:gRPC 的目标名称如何被解析
与其他 RPC 框架把"服务名"直接交给路由表不同,gRPC 把"名称解析"设计为 Channel 生命周期中的独立环节。当应用调用诸如 grpc_create_channel("dns:///example.com:443", ...) 之类接口时:
- 客户端库解析目标字符串,识别其 URI scheme;
- 依据 scheme 从 Resolver 注册表选出对应的 resolver 插件;
- 把完整名称交给该插件,插件联系权威源(如 DNS 服务器)完成解析;
- 解析结果返回客户端库,用于建连、负载均衡与服务配置下发。
不同语言的 gRPC 客户端库都提供插件机制,允许为不同名称系统接入各自的解析器,因此"名称解析"与具体语言实现解耦,而 C-core 的注册表实现是最直接的参考。
名称语法:基于 RFC 3986 的 URI
用于 gRPC Channel 构造的名称是"自包含的全限定名",采用 RFC 3986 定义的 URI 语法:
- URI scheme 决定使用哪个 resolver 插件;
- 若未写 scheme 前缀或 scheme 未知,则默认使用
dnsscheme; - URI path 表示待解析的名称。
大多数 gRPC 实现支持的 scheme 见下表:
| Scheme | 语法 | 说明 |
|---|---|---|
dns(默认) |
dns:[//authority/]host[:port] |
通过 DNS 解析 host |
unix |
unix:path、unix:///absolute_path |
Unix Domain Socket(仅 Unix 系统) |
unix-abstract |
unix-abstract:abstract_path |
抽象命名空间中的 Unix Socket(仅 Unix 系统) |
vsock |
vsock:cid:port |
VSOCK(仅 Linux 系统) |
以下 scheme 由 gRPC C-core 实现支持,其他语言可能不支持:
| Scheme | 语法 | 说明 |
|---|---|---|
ipv4 |
ipv4:address[:port][,address[:port],...] |
IPv4 地址(逗号分隔可多个) |
ipv6 |
ipv6:address[:port][,address[:port],...] |
IPv6 地址(逗号分隔可多个) |
未来还可能加入 etcd 等更多 scheme。
各 scheme 的细节与默认端口
dns:默认名称系统
dns:[//authority/]host[:port]:
host是要通过 DNS 解析的主机;port是返回给每个地址的端口;若不指定默认 443,但部分实现对于不安全(insecure)Channel 默认使用 80;authority用于指定 DNS 服务器,仅部分实现支持。在 C-core 中,默认 DNS resolver 不支持该字段;基于 c-ares 的 resolver 支持以"IP:port"形式指定(例如dns://8.8.8.8:53/example.com)。
在 C-core 源码里,地址解析对端口的缺省处理确实落在 443 上,参见 src/core/lib/address_utils/parse_address.cc 中 return htons(443); 的默认分支。
unix:Unix Domain Socket
unix:path:path为套接字位置,可以是相对路径或绝对路径;unix:///absolute_path:第二种形式要求绝对路径——URI 中三个斜杠的最后一个斜杠属于路径本身,即 URI 的 path 部分为/absolute_path。
例如目标 unix:///tmp/grpc.sock 实际指向文件系统路径 /tmp/grpc.sock。
unix-abstract:抽象命名空间套接字
unix-abstract:abstract_path:
abstract_path表示抽象命名空间中的一个名字;- 该名字与文件系统路径名毫无关联;
- 套接字不施加任何权限限制,任何进程/用户都可访问;
- 底层实现要求抽象套接字首字符为
'\0',实现会自动在abstract_path前补该空字节,因此不要把空字节写进abstract_path。
vsock:虚拟机通信(仅 Linux)
vsock:cid:port:
cid是 32 位上下文标识符(Context Identifier),标识源或目的地(某台虚拟机或宿主机);port是 32 位端口号,用于区分同一台机器上运行的多个服务。
典型的用法如 vsock:3:50051 表示与 CID 为 3 的虚拟机/宿主机上的 50051 端口通信,适用于宿主机与 guest 之间不经过 IP 栈的场景。
ipv4 / ipv6:固定地址解析(仅 C-core)
ipv4:address[:port][,address[:port],...]与ipv6:...支持逗号分隔的多个address[:port];- 不指定端口时默认 443;
- IPv6 若要与端口连用,地址必须用字面方括号包裹,如
ipv6:[2607:f8b0:400e:c00::ef]:443或ipv6:[::]:1234。
注意 ipv4/ipv6 scheme 的解析函数同样是缺省 443(见上文 parse_address 的证据)。
从源码看 scheme 分发:Resolver 注册表机制
当目标字符串里没有 scheme 或 scheme 未知时,为什么回落到 DNS?答案在 C-core 的注册表实现 src/core/resolver/resolver_registry.cc:
Reset()把default_prefix设为"dns:///"(见 resolver_registry.cc);FindResolverFactory()先尝试把原目标解析为 URI 并查找对应工厂;找不到时,就用default_prefix + target拼接出dns:///...再试一次(见 resolver_registry.cc)。这正是"example.com:443等价于dns:///example.com:443"的底层来源。
各 scheme 对应的解析器通过工厂(ResolverFactory)注册进 ResolverRegistry,工厂必须提供小写 scheme、URI 合法性校验与实例创建方法。参见 src/core/resolver/resolver_factory.h。
内建 Resolver 的注册点
C-core 中 ipv4/ipv6/unix/unix-abstract/vsock 五类地址式解析器统一实现在 src/core/resolver/sockaddr/sockaddr_resolver.cc,注册逻辑 RegisterSockaddrResolver() 显示:
IPv4ResolverFactory(scheme 为ipv4)与IPv6ResolverFactory(scheme 为ipv6)无条件注册;UnixResolverFactory与UnixAbstractResolverFactory仅在宏GRPC_HAVE_UNIX_SOCKET定义时注册;VSockResolverFactory仅在宏GRPC_HAVE_VSOCK定义时注册。
见 sockaddr_resolver.cc。也就是说,unix、unix-abstract、vsock 是否可用取决于构建期平台宏,这也与文档中"仅 Unix/Linux 系统"的说明一一对应。
同时,dns scheme 由 src/core/resolver/dns/dns_resolver.h 中的 ClientChannelDNSResolverFactory 提供,其内部再根据构建选项与运行环境在 c-ares 与 native 两种实现间选择(见下文)。
ParseUri() 解析时还会拒绝带 authority 的 URI(authority-based URIs not supported...),并逐个按逗号切分地址构造 EndpointAddressesList(见 sockaddr_resolver.cc)。这与文档中 ipv4/ipv6 支持逗号分隔多地址的描述吻合。
Resolver 插件的职责:返回什么给客户端
客户端库依据 scheme 选中 resolver 插件后,把全限定名称字符串交给它。Resolvers 需要能够联系 authority 并把解析结果返回客户端库,返回内容包含:
- 解析出的地址列表:每个地址都是(IP, port)二元组,且可附带一组任意的键值对属性(attributes),用于把解析器获知的信息传达给负载均衡策略;
- 一份服务配置(service config):例如重试参数、超时、负载均衡策略名称等,解析结果中可选携带。
此外,插件 API 允许 resolver 持续监听一个端点(endpoint),一旦解析结果变化即返回更新后的解析结果。这意味着解析不是"一次性的静态查询",而可能是一次持续到 Channel 关闭的异步观察过程——这也是客户端库实现服务发现变更后自动切换地址的基础。
从 C-core 的 SockaddrResolver 可以看到这类"静态"解析器的骨架:构造时把解析好的地址列表保存下来,StartLocked() 只调用一次 result_handler_->ReportResult() 上报结果(见 sockaddr_resolver.cc);而对 DNS 这类可变的端点,解析器实现则会周期性刷新并把更新推给 Channel。
平台差异与运行期环境变量
DNS 解析器后端:ares 还是 native?
在 C-core 中,dns scheme 的实际解析后端可通过环境变量 GRPC_DNS_RESOLVER 选择(完整说明见 doc/environment_variables.md):
ares:基于 c-ares 库的解析器,多数平台默认(iOS、Android、Node 除外);只有 c-ares 支持在 URI 中指定 DNS 服务器 authority(IP:port);native:基于getaddrinfo()的解析器,为执行名称解析会创建新线程。
NetBIOS 注意事项:如果网络依赖 NetBIOS 或 DNS 与 NetBIOS 混合解析(例如某些 Windows 网络),应使用 native 解析器,或确保所有 NetBIOS 名也在 DNS 中配置——因为 ares 只做纯 DNS 名称解析。若 gRPC 构建时未启用 c-ares,则该环境变量被忽略。
端口默认值差异
dns、ipv4、ipv6缺省端口均为 443(部分实现 insecure 通道用 80);- 实践中若以明文方式快速联调,常显式书写端口,例如
dns:///localhost:50051。
实战:构造 Channel 目标字符串
综合上文,各类目标的典型写法可归纳如下(在任意语言 gRPC API 的 Channel target 参数中直接使用):
# DNS:主机名(等价写法)
example.com:443
dns:///example.com
dns:///example.com:443
# DNS:指定 DNS 服务器(c-ares 后端,C-core)
dns://8.8.8.8:53/example.com
# Unix Domain Socket:相对/绝对路径
unix:/tmp/grpc.sock
unix:///tmp/grpc.sock
# 抽象命名空间 Unix Socket(首字符空字节由实现自动补上)
unix-abstract:my_abstract_sock_name
# VSOCK(仅 Linux)
vsock:3:50051
# 固定地址(仅 C-core,可逗号分隔多地址,端口缺省 443)
ipv4:127.0.0.1:50051
ipv4:192.0.2.1:443,192.0.2.2:443
ipv6:[::1]:50051
ipv6:[2607:f8b0:400e:c00::ef]:443
这些字符串在仓库测试中也被直接用作 target 证据,例如 C-core 的 Channel 相关单元测试以 ipv4:127.0.0.1:1234 形式构造解析成功的地址结果(见 test/core/client_channel/client_channel_test.cc)。
选择建议:
- 服务端拥有标准域名、需要多地址轮换/故障转移时,使用
dns:///host[:port],让其走完整的 resolver + 负载均衡链路; - 单机进程间通信追求低延迟与免 TCP 时,用
unix:/unix:///; - 容器/宿主机强隔离或绕过网络栈的虚拟化场景,用
vsock:cid:port; - 调试或测试阶段希望完全绕开 DNS、锁定具体端点时,用
ipv4:/ipv6:。
小结
gRPC 名称解析把"channel 目标"统一建模为 RFC 3986 URI:scheme 决定解析器、path 决定要解析的名字、缺省端口 443、缺省 scheme 回落 DNS。C-core 通过 ResolverRegistry + 各 ResolverFactory(DNS、Sockaddr 系列)把这一抽象落地为可插拔、可编译期裁剪的插件体系,解析结果(地址列表 + 属性 + service config)既可支持负载均衡策略,也能持续监听动态刷新。理解这套语法与机制,是正确配置 gRPC 客户端、排查连接失败与实现自定义名称系统的前提。
延伸阅读
- 名称解析官方文档 doc/naming.md
- 负载均衡 doc/load-balancing.md
- 服务配置 doc/service_config.md
- 环境变量说明(含 GRPC_DNS_RESOLVER)doc/environment_variables.md
- Resolver 注册表实现 src/core/resolver/resolver_registry.cc
- 地址式解析器(ipv4/ipv6/unix/unix-abstract/vsock)src/core/resolver/sockaddr/sockaddr_resolver.cc
- DNS 解析器工厂 src/core/resolver/dns/dns_resolver.h
- URI 地址解析底层实现 src/core/lib/address_utils/parse_address.cc
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00