尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

gRPC-Java Hostname 示例深度解析:构建一个自带主机名、健康检查与反射服务的“基础设施探针“服务器

gRPC-Java Hostname 示例深度解析:构建一个自带主机名、健康检查与反射服务的“基础设施探针“服务器 gRPC-Java Hostname 示例深度解析构建一个自带主机名、健康检查与反射服务的基础设施探针服务器【免费下载链接】grpc-javaThe Java gRPC implementation. HTTP/2 based RPC项目地址: https://gitcode.com/GitHub_Trending/gr/grpc-javagRPC-Java 仓库中的 hostname 示例examples/example-hostname/README.md是一个特殊的 Hello World 服务器它不仅响应问候还会在回复中携带自己的主机名并内置 Health 健康检查与 Reflection 反射两个基础设施服务。本文以该示例为核心结合仓库源码讲解它的设计意图、三种构建方式Gradle / Maven / Bazel、运行验证方法、底层实现原理以及单元测试写法帮助读者用它来验证负载均衡、服务发现、健康探测等真实基础设施场景。示例定位为什么需要会报出自己名字的服务器普通 Hello World 服务器只验证客户端能连通服务器。但在负载均衡、DNS 解析、连接池复用等场景中工程师真正想确认的是我的请求到底被路由到了哪一台后端hostname 示例正是为此设计。它的核心逻辑在 HostnameGreeter.java 中public final class HostnameGreeter extends GreeterGrpc.GreeterImplBase { private final String serverName; public HostnameGreeter(String serverName) { if (serverName null) { serverName determineHostname(); } this.serverName serverName; } Override public void sayHello(HelloRequest req, StreamObserverHelloReply responseObserver) { HelloReply reply HelloReply.newBuilder() .setMessage(Hello req.getName() , from serverName) .build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } private static String determineHostname() { try { return InetAddress.getLocalHost().getHostName(); } catch (IOException ex) { logger.log(Level.INFO, Failed to determine hostname. Will generate one, ex); } // Strange. Well, lets make an identifier for ourselves. return generated- new Random().nextInt(); } }关键设计点响应携带标识回复消息格式固定为Hello name, from hostname客户端只需解析message字段即可知道请求被哪台机器处理。主机名探测的兜底逻辑通过InetAddress.getLocalHost().getHostName()获取本机主机名若失败例如容器环境 DNS 异常会退化为生成generated-随机数这样的临时标识。这意味着即使在无法解析主机名的环境下服务器仍能给出一个可区分的身份用于验证负载均衡是否在多个实例间分发请求。复用自己的服务定义它并没有重新定义 proto 服务而是直接复用 examples 根目录下的 helloworld.protoGreeter.SayHello一元 RPC生成的桩类为GreeterGrpc与 hello-world 示例的客户端完全兼容。从源码注释看该服务器被定位为通用哑服务器general purpose dummy server用途是充当测试基础设施的靶机。前置条件先准备一个可用的 grpc-java 构建示例文档明确要求 grpc-java 已经构建完成。强烈建议 checkout 一个 git release 标签发布版会带对应版本的构建产物若必须使用 master HEAD则需先按 COMPILING 完成完整构建包括把 SNAPSHOT 安装到本地仓库并安装代码生成插件 protoc-gen-grpc-java。这一步是所有示例含 hostname通用的前置条件详细流程见 examples README 的 To build the examples 一节。三种构建与运行方式方式一Gradle在grpc-java/examples/example-hostname目录下执行注意示例文档中的目录写作examples-hostname仓库实际目录名为example-hostname请以仓库实际路径为准$ ../gradlew installDist这会生成运行脚本build/install/hostname-server/bin/hostname-server。启动服务器$ ./build/install/hostname-server/bin/hostname-server在另一个终端窗口运行 hello-world 客户端客户端需要先在grpc-java/examples目录用./gradlew installDist构建$ ../build/install/examples/bin/hello-world-client方式二Maven在grpc-java/examples/example-hostname目录下$ mvn verify $ # 从 examples-hostname 目录运行服务器 $ mvn exec:java -Dexec.mainClassio.grpc.examples.hostname.HostnameServer $ # 在另一个终端从 examples 目录运行客户端 $ cd .. $ mvn exec:java -Dexec.mainClassio.grpc.examples.helloworld.HelloWorldClientMaven 构建由 example-hostname/pom.xml 驱动其中值得注意的配置通过grpc-bom统一管理grpc-services、grpc-protobuf、grpc-stub等依赖版本示例当前版本为1.85.0-SNAPSHOTgrpc-netty-shaded作为 runtime 依赖提供传输层实现由protobuf-maven-plugin在编译期调用protoccom.google.protobuf:protoc:${protoc.version}与protoc-gen-grpc-java插件io.grpc:protoc-gen-grpc-java:${grpc.version}自动生成消息类与 gRPC 桩代码编译目标为 Java 8maven.compiler.source/target1.8。方式三Bazel在grpc-java/examples目录下$ bazel build :hello-world-client example-hostname:hostname-server $ # 运行服务器 $ ./bazel-bin/example-hostname/hostname-server $ # 在另一个终端运行客户端 $ ./bazel-bin/hello-world-clientBazel 目标定义见 example-hostname/BUILD.bazelproto_libraryjava_proto_library生成消息类java_grpc_library由仓库根目录的 java_grpc_library.bzl 提供生成 gRPC 桩java_binary目标hostname-server以io.grpc.examples.hostname.HostnameServer为主类并显式挂上io_grpc_grpc_java//services:health与io_grpc_grpc_java//services:reflection两个服务依赖以及io_grpc_grpc_java//netty作为运行时传输。源码级剖析HostnameServer 如何一鱼三吃服务器入口 HostnameServer.java 同时注册了三个服务HealthStatusManager health new HealthStatusManager(); final Server server Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) .addService(new HostnameGreeter(hostname)) .addService(ProtoReflectionServiceV1.newInstance()) .addService(health.getHealthService()) .build() .start();业务服务HostnameGreeter通过构造函数注入主机名。若未显式指定传null则在构造时调用determineHostname()自动探测。反射服务ProtoReflectionServiceV1ProtoReflectionServiceV1.newInstance()来自 services/src/main/java/io/grpc/protobuf/services/ProtoReflectionServiceV1.java实现了 gRPC 反射协议。开启后像grpcurl这类工具无需 proto 文件即可动态发现该服务器暴露了哪些服务与消息类型这大大方便了对 hostname 服务器这类基础设施靶机的在线探查。健康服务HealthStatusManagerHealthStatusManager实现见 services/src/main/java/io/grpc/services/HealthStatusManager.java是 gRPC 标准健康检查协议的服务端管理者。服务器启动后通过下面这行代码把整体状态置为可服务health.setStatus(, ServingStatus.SERVING);这里传入的空字符串代表全局服务的健康状态。源码注释点明了正确的使用范式健康状态本应联动业务依赖——例如若HostnameGreeter依赖某个下游 Channel当该 Channel 处于TRANSIENT_FAILURE时应把状态置为NOT_SERVING由于本示例没有任何外部依赖硬编码SERVING是合适的。这套机制使得负载均衡器或 K8s 探针可以通过标准的grpc.health.v1.Health/Check接口判断后端是否可用。优雅关闭Shutdown Hookmain()中还注册了 JVM shutdown hook按先优雅停机、限时等待、再强制取消的三步走完成关闭server.shutdown(); // 停止接收新 RPC server.awaitTermination(30, TimeUnit.SECONDS); // 等待存量 RPC 最多 30 秒 server.shutdownNow(); // 取消剩余 RPC命令行参数服务器支持两个可选参数顺序为port与hostnameport监听端口默认50051hostname客户端在 greet 回复中看到的名称默认取本机主机名。若第一个参数无法解析为整数程序会打印用法提示并以状态码 1 退出。实际运行例如$ ./build/install/hostname-server/bin/hostname-server 50052 my-node-01即让服务器监听 50052 端口并对外宣称自己是my-node-01——这非常适合在本地模拟多台不同主机名的后端。端到端验证看请求落到了谁身上按照示例文档的流程服务器启动后会打印Listening on port 50051随后在另一终端运行 hello-world 客户端即可完成一次真实的网络往返。客户端默认连接localhost:50051调用SayHello后客户端收到的回复形如Hello world, from 本机主机名其中from后面的内容正是当前处理该请求的服务器实例的标识。在多实例负载均衡场景下多次调用会看到不同主机名从而直接验证请求是否被正确分发。单元测试用 InProcess 传输验证服务逻辑HostnameGreeterTest.java 展示了官方推荐的 gRPC 服务测试范式——不 mock stub而是用 InProcessTransport 在同一进程内完成端到端调用Rule public final GrpcCleanupRule grpcCleanup new GrpcCleanupRule(); private GreeterGrpc.GreeterBlockingStub blockingStub GreeterGrpc.newBlockingStub(grpcCleanup.register( InProcessChannelBuilder.forName(hostname).directExecutor().build())); Test public void sayHello_fixedHostname() throws Exception { grpcCleanup.register( InProcessServerBuilder.forName(hostname) .directExecutor().addService(new HostnameGreeter(me)).build().start()); HelloReply reply blockingStub.sayHello(HelloRequest.newBuilder().setName(you).build()); assertEquals(Hello you, from me, reply.getMessage()); } Test public void sayHello_dynamicHostname() throws Exception { grpcCleanup.register( InProcessServerBuilder.forName(hostname) .directExecutor().addService(new HostnameGreeter(null)).build().start()); HelloReply reply blockingStub.sayHello(HelloRequest.newBuilder().setName(anonymous).build()); assertTrue(reply.getMessage(), reply.getMessage().startsWith(Hello anonymous, from )); }两个测试分别覆盖了两种主机名来源sayHello_fixedHostname注入固定名称me断言回复精确等于Hello you, from mesayHello_dynamicHostname传入null触发自动探测路径只需断言回复以Hello anonymous, from开头即可——因为探测到的主机名随环境变化只验证服务不会崩溃且格式正确。InProcessChannelBuilder/InProcessServerBuilder实现位于 inprocess让客户端与服务端共享同一进程、无需任何 socket/TCP 连接配合GrpcCleanupRule见 testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java自动完成优雅关闭。正如 examples README 中强调的mock stub 很容易掩盖null消息、未调用close()、错误 header、忽略 deadline 与 cancellation 等真实缺陷官方明确不鼓励 mock 最终方法推荐以 InProcess 端到端测试取而代之。典型应用场景小结把以上机制组合起来hostname 示例就是一套开箱即用的基础设施验证工具箱负载均衡验证部署多个实例观察客户端回复中from字段的主机名变化确认请求被均匀分发健康检查联调通过标准 Health 协议grpc.health.v1.Health/Check探测服务可用性与 K8s gRPC probe 或各类 LB 的健康检查器对接反射协议测试用支持反射的工具直接枚举该服务器暴露的服务验证服务注册是否正确服务发现 / 命名解析测试由于回复自带实例标识可以快速判断 DNS 或 resolver 返回的后端地址是否被正确连接。它复用了Greeter服务定义、以三种构建系统Gradle、Maven、Bazel均可一键构建运行是 gRPC-Java 生态中成本最低、收益最直接的基础设施测试服务器。【免费下载链接】grpc-javaThe Java gRPC implementation. HTTP/2 based RPC项目地址: https://gitcode.com/GitHub_Trending/gr/grpc-java创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表