歡迎訪問我的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