资讯动态

gRPC 名称解析测试利器:Fake Resolver 的实现原理与实战用法

发布时间:2026/9/10 13:48:32 来源:尧图企业网站定制
gRPC 名称解析测试利器Fake Resolver 的实现原理与实战用法【免费下载链接】grpcC based gRPC (C, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc导读本文围绕 gRPCC 核心实现中用于测试的Fake Name Resolution假名称解析机制展开深入剖析src/core/resolver/fake目录下 Fake Resolver 与FakeResolverResponseGenerator的设计目标、源码实现与测试应用。通过本文读者可以掌握如何在测试代码中绕过真实的 DNS 解析直接注入解析结果、模拟错误条件与重新解析re-resolution等边界场景从而编写出确定性、不依赖网络状态的 gRPC 单元测试与端到端测试。一、为什么需要假名称解析在 gRPC 的客户端通道client channel中名称解析name resolution负责把用户传入的 target如dns:///example.com:443转换为一组可用的端点地址与服务配置。真实场景下这一过程依赖 DNS、xDS 等外部基础设施会引入网络延迟与不确定性给测试带来两个痛点结果不可控测试无法预知 DNS 返回的地址集合难以断言解析结果是否正确到达客户端通道边界条件难以复现解析失败、返回空地址列表、解析结果在通道启动前后到达等时序问题很难用真实解析器稳定触发。Fake Resolver 正是为解决这些问题而生的它实现了与真实解析器完全相同的Resolver接口但允许测试代码通过一个响应生成器Response Generator在任意时机注入任意解析结果。正如 AGENTS.md 所述它的总体目的是提供一个可指定名称解析查询结果的具体Resolver实现从而便于测试错误条件和其它边界情况。必须强调的是它是测试专用实现绝不应在生产环境中使用。二、gRPC 名称解析的抽象骨架Resolver 与 ResolverFactory在深入 Fake Resolver 之前先理解它实现的两个抽象接口。Resolver定义在 resolver.hResult解析结果的结构体包含addressesabsl::StatusOrEndpointAddressesList端点地址列表或错误service_configabsl::StatusOrRefCountedPtrServiceConfig服务配置或错误resolution_note人类可读的说明文字例如未找到 DNS 记录可透传到 RPC 失败状态信息args附带透传给 LB 策略的 ChannelArgsresult_health_callbackLB 策略处理完结果后的回调携带该结果是否被接受的状态。ResultHandler解析器向通道回报结果的代理对象核心方法是ReportResult(Result)。生命周期方法StartLocked()启动解析、RequestReresolutionLocked()请求重新解析、ShutdownLocked()关闭。所有带Locked后缀的方法都必须在构造时传入的work_serializer上串行执行。ResolverFactory定义在 resolver_factory.h每个解析器实现对应一个 URI scheme如dns、fake。Fake Resolver 的工厂类通过scheme()返回fake因此在测试中可以构造fake:...形式的 target。三、Fake Resolver 的两个核心组件整个 Fake Resolver 机制由 fake_resolver.h 与 fake_resolver.cc 中的两个类协作完成3.1FakeResolver测试专用的 Resolver 实现FakeResolver直接继承Resolver其状态极其简单class FakeResolver final : public Resolver { private: // 通过 channel args 注入的响应生成器 RefCountedPtrFakeResolverResponseGenerator response_generator_; // 下一个要返回的解析结果若在 resolver 启动前就设置了结果 std::optionalResult next_result_; bool started_ false; // StartLocked() 之后置真 bool shutdown_ false; // ShutdownLocked() 之后置真 };它的三个核心生命周期方法见 fake_resolver.ccStartLocked()标记已启动并立即调用MaybeSendResultLocked()尝试把暂存的结果推送出去RequestReresolutionLocked()调用response_generator_-ReresolutionRequested()让测试代码通过WaitForReresolutionRequest()感知通道要求重新解析这一事件ShutdownLocked()解除与 response generator 的绑定关系。MaybeSendResultLocked()是结果投递的咽喉只有当 resolver 已启动且未关闭、且存在待发送结果时才通过result_handler_-ReportResult(...)把结果上报给客户端通道。3.2FakeResolverResponseGenerator测试侧的遥控器这是整个机制的灵魂。它本身是一个RefCounted对象不直接依赖任何网络通过 channel argument 注入到 Fake Resolver 中为测试代码提供注入并触发自定义解析的能力。它对外暴露以下方法见 fake_resolver.h方法行为SetResponseAndNotify(Result, Notification*)触发一次新的解析并携带指定结果若 resolver 尚不可用则延迟设置直到其出现resolver 可用前最多调用一次可选通知在响应设置完成后被触发SetResponseAsync(Result)SetResponseAndNotify的异步简化版无需等待SetResponseSynchronously(Result)同步版内部创建Notification并阻塞等待其完成适合在测试主线程中直接调用WaitForReresolutionRequest(Duration)等待一次重新解析请求超时返回 false若上次调用之后已有重新解析请求则立即返回 trueWaitForResolverSet(Duration)等待 resolver 完成绑定resolver 与 generator 的关联可能异步发生仅用于测试响应生成器内部用两把互斥锁分别保护resolver 绑定与结果暂存mu_与重新解析请求状态reresolution_mu_并通过CondVar实现等待/唤醒保证跨线程调用安全。3.3 两者的绑定channel argument 注入Fake Resolver 通过名为GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR即grpc.fake_resolver.response_generator的 channel argument 与响应生成器建立联系见 fake_resolver.h。FakeResolver构造时用args.args.GetObjectRefFakeResolverResponseGenerator()取出该对象并调用SetFakeResolver()完成双向绑定。为了让该对象能被塞进 C 风格的grpc_channel_args代码定义了grpc_arg_pointer_vtableResponseGeneratorChannelArgCopy/Destroy/Cmp见 fake_resolver.cc通过引用计数管理其生命周期。四、三种时序场景响应的注入时机处理Fake Resolver 最有价值的设计在于对结果到达时机的宽容处理。无论结果在 resolver 生命周期的哪个阶段到达都能正确投递场景一结果先于 resolver 创建ReturnResultBeforeResolverCreated测试先调用SetResponseAsync(result)此时resolver_为空结果被暂存到生成器的result_字段见 fake_resolver.cc。随后创建 Fake Resolver构造函数触发SetFakeResolver()发现已有暂存结果便立刻通过 WorkSerializer 投递。场景二结果先于 resolver 启动ReturnResultBeforeResolverStartedresolver 已创建但尚未StartLocked()。此时next_result_被暂存MaybeSendResultLocked()因started_ false直接返回当测试调用StartLocked()后暂存结果被立即上报。场景三resolver 启动后注入结果ReturnResult此时生成器直接通过 WorkSerializer 投递无需暂存。这一机制的关键实现是SendResultToResolver()见 fake_resolver.cc它把投递动作调度到 resolver 的work_serializer_上执行在串行化环境中写入next_result_并调用MaybeSendResultLocked()从而保证与StartLocked()、ShutdownLocked()等操作之间不存在数据竞争——这与Resolver接口所有 Locked 方法必须在 work_serializer 上执行的约束保持一致。五、两个容易被忽略的实现细节细节一从 channel args 中移除生成器参数FakeResolver构造函数在保存 channel args 时显式调用了args.args.Remove(GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR)。注释给出了原因见 fake_resolver.cc共享同一批子通道subchannel的多个通道可能持有不同的响应生成器若不移除该参数子通道池会因为该 channel arg 值不同而无法复用相同地址的子通道导致测试中出现意外的重复建连。细节二结果的 channel args 与 resolver 的 channel args 合并在MaybeSendResultLocked()中投递前会执行next_result_-args next_result_-args.UnionWith(channel_args_)且以注入结果中的同名参数为准。这保证了测试注入的地址列表能够携带测试方自定义的 channel args同时不丢失 resolver 构造时传入的参数。六、测试用例如何驱动 Fake Resolver仓库自带的单元测试 fake_resolver_test.cc 完整演示了 Fake Resolver 的使用范式其构建方式BuildFakeResolver如下ResolverFactory* factory CoreConfiguration::Get().resolver_registry().LookupResolverFactory(fake); ResolverArgs args; args.args ChannelArgs().SetObject(std::move(response_generator)); args.work_serializer std::move(work_serializer); args.result_handler std::move(result_handler); return factory-CreateResolver(std::move(args));即通过LookupResolverFactory(fake)从注册表中获取工厂把响应生成器放入ChannelArgs再结合WorkSerializer与自定义的ResultHandler创建解析器。该测试文件覆盖了四个典型场景WaitForResolverSet验证生成器能感知 resolver 的绑定ReturnResultBeforeResolverCreated结果先于 resolver 创建启动后收到ReturnResultBeforeResolverStarted结果先于 resolver 启动启动后收到WaitForReresolutionRequest调用RequestReresolutionLocked()后生成器能捕获重新解析请求。测试的ResultHandler会逐地址比对实际结果与期望结果并通过absl::Notification同步等待结果到达超时5 * grpc_test_slowdown_factor()秒这正是确定性测试的典型写法。该测试目标在 test/core/resolver/BUILD 中定义为fake_resolver_test。七、仓库中的真实应用场景Fake Resolver 不仅用于自身单元测试还被整个 gRPC 代码库的多处关键路径复用说明其测试注入能力已成为基础设施从端点创建通道channel_create.cc 的CreateChannelFromEndpoint内部构造FakeResolverResponseGenerator构造一个仅含单个端点地址的Resolver::Result然后以fake:created-from-endpoint为目标创建客户端通道——把基于已有 TCP 连接建通道这种特殊场景复用到了 Fake Resolver 之上grpclb 负载均衡grpclb.cc 引入 Fake Resolver用于在 grpclb 相关测试中注入解析结果xDS 逻辑 DNS 集群xds_channel_args.h 定义了grpc.TEST_ONLY.xds_logical_dns_cluster_fake_resolver_response_generator这一测试专用参数xds_dependency_manager.cc 在构建 xDS 逻辑 DNS 集群时也会读取并注入FakeResolverResponseGenerator端到端与传输层测试no_server_test.cc 使用SetResponseSynchronously注入结果来模拟无服务器场景too_many_pings_test.cc 用它构造地址解析结果以测试 HTTP/2 层的 ping 限流行为注册机制grpc_plugin_registry.cc 声明并调用RegisterFakeResolver(builder)将fakescheme 注册进CoreConfiguration的 resolver 注册表工厂实现见 fake_resolver.cc。八、使用建议与边界基于源码结构可以总结出以下使用要点只用于测试这是 AGENTS.md 与代码注释反复强调的红线。Fake Resolver 返回的是测试注入的静态数据不具备任何真实解析能力利用时序弹性SetResponseAsync允许在 resolver 创建、启动之前注入结果可用于测试解析结果迟到通道启动竞态等边界同步等待原语SetResponseSynchronously、WaitForResolverSet、WaitForReresolutionRequest三个方法让测试线程能够阻塞等待关键事件从而写出确定性的断言注意子通道复用由于构造时生成器参数会被从 channel args 中移除多个测试通道只要解析出的地址一致就可以共享子通道池这有助于控制测试资源开销配合 WorkSerializer所有投递与回调都在work_serializer上串行执行测试中涉及 resolver 状态变更的操作也应通过同一WorkSerializer调度避免并发问题。结语Fake Resolver 是 gRPC 测试体系中的一个精巧设计它以最小的实现代价一个 Fake 解析器 一个响应生成器 一个 channel argument为上层测试提供了任意时机注入任意解析结果、感知重新解析请求的完整能力。理解它的实现不仅能帮助你编写更可靠的 gRPC 单元测试与端到端测试也有助于理解 gRPC 客户端通道中 Resolver、ChannelArgs、WorkSerializer 之间的协作关系。想要进一步研究可以从 fake_resolver.h、fake_resolver.cc 与 fake_resolver_test.cc 三份文件入手。【免费下载链接】grpcC based gRPC (C, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价