歡迎訪問我的GitHub
https://github.com/zq2599/blog_demos
內容:所有原創文章分類匯總及配套源碼,涉及Java、Docker、Kubernetes、DevOPS等;
《java版gRPC實戰》全系列鏈接
關於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'
// 依賴eureka
implementation '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: 8085
spring:
application:
name: cloud-eureka
eureka:
instance:
hostname: localhost
prefer-ip-address: true
status-page-url-path: /actuator/info
health-check-url-path: /actuator/health
lease-expiration-duration-in-seconds: 30
lease-renewal-interval-in-seconds: 30
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://192.168.50.5:8085/eureka/
server:
enable-self-preservation: false
endpoints:
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
@SpringBootApplication
public 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的client
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
// 狀態暴露需要的依賴
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// 依賴自動生成源碼的工程
implementation project(':grpc-lib')
// annotationProcessor不會傳遞,使用了lombok生成代碼的模塊,需要自己聲明annotationProcessor
annotationProcessor 'org.projectlombok:lombok'
}
- 配置文件application.yml,設置自己的應用名,另外值得注意的是server.port和grpc.server.port這兩個配置的值都是0,這樣兩個端口就會被自動分配未被占用的值:
spring:
application:
name: cloud-server-side
server:
port: 0
grpc:
server:
port: 0
eureka:
instance:
prefer-ip-address: true
instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
client:
register-with-eureka: true
fetch-registry: true
service-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
@SpringBootApplication
public 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;
@GrpcService
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {
@Override
public 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的client
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
// 狀態暴露需要的依賴
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// 依賴自動生成源碼的工程
implementation project(':grpc-lib')
// annotationProcessor不會傳遞,使用了lombok生成代碼的模塊,需要自己聲明annotationProcessor
annotationProcessor 'org.projectlombok:lombok'
}
- 配置文件application.yml,設置自己的web端口號,另外值得注意的是gRPC配置項cloud-server-side的名字要等於gRPC服務端在eureka注冊的名字,並且不需要address配置項:
server:
port: 8086
spring:
application:
name: cloud-client-side
eureka:
instance:
prefer-ip-address: true
status-page-url-path: /actuator/info
health-check-url-path: /actuator/health
instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://192.168.50.5:8085/eureka/
grpc:
client:
# gRPC配置的名字,GrpcClient注解會用到
cloud-server-side:
enableKeepAlive: true
keepAliveWithoutCalls: true
negotiationType: 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
@SpringBootApplication
public 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;
@Service
public 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;
@RestController
public class GrpcClientController {
@Autowired
private 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世界...
https://github.com/zq2599/blog_demos