欢迎访问我的GitHub
https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;
《java版gRPC实战》全系列链接
- 用proto生成代码
- 服务发布和调用
- 服务端流
- 客户端流
- 双向流
- 客户端动态获取服务端地址
- 基于eureka的注册发现
关于eureka
前面咱们在开发客户端应用时,所需的服务端地址都是按如下步骤设置的:
- 在application.yml中配置,如下图:
- 在用到gRPC的bean中,使用注解GrpcClient即可将Stub类注入到成员变量中:
- 上述操作方式的优点是简单易用好配置,缺点也很明显:服务端的IP地址或者端口一旦有变化,就必须修改application.yml并重启客户端应用;
- 聪明的您一定想到了应对之道:注册中心!没错,有了注册中心,咱们的客户端只要能从注册中心取得最新的服务端地址,就不再需要手动配置了,以下是常规的eureka作用说明:
本篇概览
- 如果您有Spring Cloud的开发经验,对resttemplate和feign等应该很熟悉,但是Spring Cloud环境下的gRPC调用却没有那么常用,本篇的目标是通过实战与大家一起掌握Spring Cloud环境下的gRPC调用,分为以下章节:
- eureka应用开发
- gRPC服务端开发
- gRPC客户端开发
- 验证
- 一点疑惑
源码下载
- 本篇实战中的完整源码可在GitHub下载到,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):
名称 | 链接 | 备注 |
---|---|---|
项目主页 | https://github.com/zq2599/blog_demos | 该项目在GitHub上的主页 |
git仓库地址(https) | https://github.com/zq2599/blog_demos.git | 该项目源码的仓库地址,https协议 |
git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
- 这个git项目中有多个文件夹,《java版gRPC实战》系列的源码在grpc-tutorials文件夹下,如下图红框所示:
- grpc-tutorials文件夹下有多个目录,本篇文章对应的eureka代码在cloud-eureka目录,服务端代码在cloud-server-side目录,客户端代码在cloud-client-side目录,如下图:
eureka应用开发
- 在父工程grpc-turtorials下面新建名为cloud-eureka的模块,其build.gradle内容如下:
// 使用springboot插件plugins {id \'org.springframework.boot\'}dependencies {implementation \'org.springframework.boot:spring-boot-starter\'// 依赖eurekaimplementation \'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server\'// 状态暴露需要的依赖implementation \'org.springframework.boot:spring-boot-starter-actuator\'// 依赖自动生成源码的工程implementation project(\':grpc-lib\')}
- 配置文件bootstrap.yml,设置自己的web端口号和应用名,另外eureka.client.serviceUrl.defaultZone的配置请改成自己的IP:
server:port: 8085spring:application:name: cloud-eurekaeureka:instance:hostname: localhostprefer-ip-address: truestatus-page-url-path: /actuator/infohealth-check-url-path: /actuator/healthlease-expiration-duration-in-seconds: 30lease-renewal-interval-in-seconds: 30client:registerWithEureka: falsefetchRegistry: falseserviceUrl:defaultZone: http://192.168.50.5:8085/eureka/server:enable-self-preservation: falseendpoints:shutdown:enabled: true
- 这个模块只有一个类CloudEurekaApplication.java:
package com.bolingcavalry.grpctutorials;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@EnableEurekaServer@SpringBootApplicationpublic class CloudEurekaApplication {public static void main(String[] args) {SpringApplication.run(CloudEurekaApplication.class, args);}}
- 以上就是一个简单通用的eureka服务了;
gRPC服务端开发
-
依赖eureka的gRPC服务端,其重点在于:第一,配置使用eureka,第二,不要指定端口;
-
在父工程grpc-turtorials下面新建名为cloud-server-side的模块,其build.gradle内容如下,注意要引入gRPC服务端相关的starter:
// 使用springboot插件plugins {id \'org.springframework.boot\'}dependencies {implementation \'org.projectlombok:lombok\'implementation \'org.springframework.boot:spring-boot-starter-web\'implementation \'org.springframework.boot:spring-boot-starter\'// 作为gRPC服务提供方,需要用到此库implementation \'net.devh:grpc-server-spring-boot-starter\'// 作为eureka的clientimplementation \'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client\'// 状态暴露需要的依赖implementation \'org.springframework.boot:spring-boot-starter-actuator\'// 依赖自动生成源码的工程implementation project(\':grpc-lib\')// annotationProcessor不会传递,使用了lombok生成代码的模块,需要自己声明annotationProcessorannotationProcessor \'org.projectlombok:lombok\'}
- 配置文件application.yml,设置自己的应用名,另外值得注意的是server.port和grpc.server.port这两个配置的值都是0,这样两个端口就会被自动分配未被占用的值:
spring:application:name: cloud-server-sideserver:port: 0grpc:server:port: 0eureka:instance:prefer-ip-address: trueinstanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}client:register-with-eureka: truefetch-registry: trueservice-url:defaultZone: http://192.168.50.5:8085/eureka/
- 启动类CloudServerSideApplication.java:
package com.bolingcavalry.grpctutorials;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@EnableEurekaClient@EnableDiscoveryClient@SpringBootApplicationpublic class CloudServerSideApplication {public static void main(String[] args) {SpringApplication.run(CloudServerSideApplication.class, args);}}
- 提供gRPC服务的类GrpcServerService,和local-server模块中的一样:
package com.bolingcavalry.grpctutorials;import com.bolingcavalry.grpctutorials.lib.HelloReply;import com.bolingcavalry.grpctutorials.lib.SimpleGrpc;import net.devh.boot.grpc.server.service.GrpcService;import java.util.Date;@GrpcServicepublic class GrpcServerService extends SimpleGrpc.SimpleImplBase {@Overridepublic void sayHello(com.bolingcavalry.grpctutorials.lib.HelloRequest request,io.grpc.stub.StreamObserver<HelloReply> responseObserver) {HelloReply reply = HelloReply.newBuilder().setMessage("1. Hello " + request.getName() + ", " + new Date()).build();responseObserver.onNext(reply);responseObserver.onCompleted();}}
- 以上就是服务端代码了,可见除了将gRPC端口设置为0,以及常规使用eureka的配置,其他部分和local-server模块是一样的;
gRPC客户端开发
- 依赖eureka的gRPC客户端,其重点在于:第一,配置使用eureka,第二,配置中的gRPC配置项的名字要等于gRPC服务端在eureka注册的名字,如下图红框所示:
- 在父工程grpc-turtorials下面新建名为cloud-client-side的模块,其build.gradle内容如下,注意要引入gRPC客户端相关的starter:
// 使用springboot插件plugins {id \'org.springframework.boot\'}dependencies {implementation \'org.projectlombok:lombok\'implementation \'org.springframework.boot:spring-boot-starter-web\'implementation \'org.springframework.boot:spring-boot-starter\'// 作为gRPC服务使用方,需要用到此库implementation \'net.devh:grpc-client-spring-boot-starter\'// 作为eureka的clientimplementation \'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client\'// 状态暴露需要的依赖implementation \'org.springframework.boot:spring-boot-starter-actuator\'// 依赖自动生成源码的工程implementation project(\':grpc-lib\')// annotationProcessor不会传递,使用了lombok生成代码的模块,需要自己声明annotationProcessorannotationProcessor \'org.projectlombok:lombok\'}
- 配置文件application.yml,设置自己的web端口号,另外值得注意的是gRPC配置项cloud-server-side的名字要等于gRPC服务端在eureka注册的名字,并且不需要address配置项:
server:port: 8086spring:application:name: cloud-client-sideeureka:instance:prefer-ip-address: truestatus-page-url-path: /actuator/infohealth-check-url-path: /actuator/healthinstanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}client:register-with-eureka: truefetch-registry: trueservice-url:defaultZone: http://192.168.50.5:8085/eureka/grpc:client:# gRPC配置的名字,GrpcClient注解会用到cloud-server-side:enableKeepAlive: truekeepAliveWithoutCalls: truenegotiationType: plaintext
- 启动类CloudClientSideApplication.java,使用了eureka相关的注解:
package com.bolingcavalry.grpctutorials;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@EnableEurekaClient@EnableDiscoveryClient@SpringBootApplicationpublic class CloudClientSideApplication {public static void main(String[] args) {SpringApplication.run(CloudClientSideApplication.class, args);}}
- 封装gRPC调用的服务类GrpcServerService,和local-server模块中的一样,GrpcClient注解对应配置中的gRPC配置项:
package com.bolingcavalry.grpctutorials;import com.bolingcavalry.grpctutorials.lib.HelloReply;import com.bolingcavalry.grpctutorials.lib.HelloRequest;import com.bolingcavalry.grpctutorials.lib.SimpleGrpc;import io.grpc.StatusRuntimeException;import net.devh.boot.grpc.client.inject.GrpcClient;import org.springframework.stereotype.Service;@Servicepublic class GrpcClientService {@GrpcClient("cloud-server-side")private SimpleGrpc.SimpleBlockingStub simpleStub;public String sendMessage(final String name) {try {final HelloReply response = this.simpleStub.sayHello(HelloRequest.newBuilder().setName(name).build());return response.getMessage();} catch (final StatusRuntimeException e) {return "FAILED with " + e.getStatus().getCode().name();}}}
- 再做一个web接口类,这样我们就能通过web调用验证gRPC服务了:
package com.bolingcavalry.grpctutorials;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class GrpcClientController {@Autowiredprivate GrpcClientService grpcClientService;@RequestMapping("/")public String printMessage(@RequestParam(defaultValue = "will") String name) {return grpcClientService.sendMessage(name);}}
- 客户端开发完毕,接下来可以验证了;
验证
- 启动cloud-eureka:
- 启动cloud-server-side,可见gRPC服务端口自动分配了65141,不过我们无需关心这个值,因为客户端可以从eureka获取到:
- 接下来启动cloud-client-side,启动成功后eureka上可见两个服务的注册信息:
- 浏览器访问cloud-client-side提供的web接口,响应如下,可见cloud-client-side成功调用了cloud-server-side的gRPC服务:
一点疑惑
-
如果您对eureka有所了解,可能会产生一点疑惑:cloud-client-side从eureka取得的cloud-server-side信息,应该是http服务的地址和端口,不应该有gRPC的端口号,因为eureka的注册发现服务并不包含gRPC有关的!
-
篇幅所限,这里不适合将上述问题展开分析,咱们来关注最核心的地方,相信聪明的您看上一眼就会豁然开朗;
-
DiscoveryClientNameResolver来自grpc-client-spring-boot-autoconfigure.jar,用来保存从eureka取得的服务端信息,该类的注释已经说得很清楚了,从metadata的gRPC.port配置项中取得gRPC端口号:
- 在DiscoveryClientNameResolver的代码中打上断点,查看成员变量instanceList,可见metadata中确实有gRPC端口的信息:
- 至于cloud-server-side如何将端口号提交到eureka,以及cloud-client-side为何会使用DiscoveryClientNameResolver来处理eureka的服务列表信息,就不在本文中讨论了,您要是有兴趣深入研究eureka,可以参考《程序员欣宸文章汇总(Spring篇)》中的Eureka源码分析专题,如下图:
- 至此,基于eureka的gRPC服务注册发现的开发和验证就完成了,希望本文可以给您带来一些参考,让您的服务在注册中心的加持下更加灵活和可靠;
你不孤单,欣宸原创一路相伴
- Java系列
- Spring系列
- Docker系列
- kubernetes系列
- 数据库+中间件系列
- DevOps系列
欢迎关注公众号:程序员欣宸
微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界…https://github.com/zq2599/blog_demos