java版gRPC實戰之七:基於eureka的註冊發現

程式設計師欣宸 發表於 2021-09-19
Java

歡迎訪問我的GitHub

https://github.com/zq2599/blog_demos

內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等;

《java版gRPC實戰》全系列連結

  1. 用proto生成程式碼
  2. 服務釋出和呼叫
  3. 服務端流
  4. 客戶端流
  5. 雙向流
  6. 客戶端動態獲取服務端地址
  7. 基於eureka的註冊發現

關於eureka

前面我們們在開發客戶端應用時,所需的服務端地址都是按如下步驟設定的:

  • 在application.yml中配置,如下圖:

在這裡插入圖片描述

  • 在用到gRPC的bean中,使用註解GrpcClient即可將Stub類注入到成員變數中:

在這裡插入圖片描述

  • 上述操作方式的優點是簡單易用好配置,缺點也很明顯:服務端的IP地址或者埠一旦有變化,就必須修改application.yml並重啟客戶端應用;
  • 聰明的您一定想到了應對之道:註冊中心!沒錯,有了註冊中心,我們們的客戶端只要能從註冊中心取得最新的服務端地址,就不再需要手動配置了,以下是常規的eureka作用說明:

在這裡插入圖片描述

本篇概覽

  • 如果您有Spring Cloud的開發經驗,對resttemplate和feign等應該很熟悉,但是Spring Cloud環境下的gRPC呼叫卻沒有那麼常用,本篇的目標是通過實戰與大家一起掌握Spring Cloud環境下的gRPC呼叫,分為以下章節:
  1. eureka應用開發
  2. gRPC服務端開發
  3. gRPC客戶端開發
  4. 驗證
  5. 一點疑惑

原始碼下載

名稱 連結 備註
專案主頁 https://github.com/zq2599/blog_demos 該專案在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該專案原始碼的倉庫地址,https協議
git倉庫地址(ssh) [email protected]: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.portgrpc.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服務註冊發現的開發和驗證就完成了,希望本文可以給您帶來一些參考,讓您的服務在註冊中心的加持下更加靈活和可靠;

你不孤單,欣宸原創一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 資料庫+中介軟體系列
  6. DevOps系列

歡迎關注公眾號:程式設計師欣宸

微信搜尋「程式設計師欣宸」,我是欣宸,期待與您一同暢遊Java世界...
https://github.com/zq2599/blog_demos