2020-02-08 gRPC從入門到破產(chǎn)(2)SpringCloud與gRPC整合

本文已經(jīng)與(1)整合后發(fā)在了我的個(gè)人網(wǎng)站,歡迎訪問(wèn):http://www.wendev.site/article/25

寫在前面

前一段時(shí)間學(xué)習(xí)SpringCloud時(shí)嘗試了SpringCloud整合Dubbo,感覺非常棒。那比Dubbo更快、還有著跨語(yǔ)言特性的gRPC與SpringCloud又會(huì)碰撞出什么樣的火花呢?今天就來(lái)試一試。

當(dāng)然,一開始寫的也并不復(fù)雜,還是從最簡(jiǎn)單的一個(gè)服務(wù)提供者,一個(gè)服務(wù)消費(fèi)者,發(fā)送一條HelloWorld并顯示端口號(hào)開始。

服務(wù)注冊(cè)與發(fā)現(xiàn)中心選擇了Consul,本來(lái)用的是Nacos,結(jié)果出了莫名其妙的Bug(這個(gè)在下面會(huì)寫),換Consul之后一下子就好了。。。

版本

  • Java:11
  • Consul:1.6.3
  • Spring Cloud:Hoxton.RELEASE
  • Spring Boot:2.2.4.RELEASE
  • gRPC:1.25.0
  • grpc-spring-boot-strater:2.6.2.RELEASE GitHub地址

使用Docker啟動(dòng)Consul

以前一直是使用直接運(yùn)行可執(zhí)行文件啟動(dòng)Consul,而使用docker比直接運(yùn)行jar包更方便管理,所以這次就使用Docker來(lái)運(yùn)行。

這里順便記錄一下Nacos的Docker啟動(dòng)方式:

Nacos

docker pull nacos/nacos-server
docker run --env MODE=standalone --name nacos -d -p 8848:8848 nacos/nacos-server

Consul

只啟動(dòng)一個(gè)實(shí)例的話可以這樣啟動(dòng):

docker pull consul
docker run --name consul -p 8500:8500 -d consul

運(yùn)行完畢就可以通過(guò)localhost:8500訪問(wèn)了。

開始!

整個(gè)項(xiàng)目的目錄結(jié)構(gòu):

父級(jí)依賴管理工程

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>site.wendev.grpc</groupId>
    <artifactId>grpc-spring-cloud-final</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
    </parent>

    <modules>
        <module>wendev-api</module>
        <module>wendev-provider</module>
        <module>wendev-consumer</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>11</java.version>
        <spring.cloud.version>Hoxton.RELEASE</spring.cloud.version>
        <nacos.version>0.9.0.RELEASE</nacos.version>
        <lombok.version>1.18.10</lombok.version>
        <java.annotation.api.version>1.3.2</java.annotation.api.version>
        <grpc.version>1.25.0</grpc.version>
        <grpc.spring.boot.starter.verison>2.6.2.RELEASE</grpc.spring.boot.starter.verison>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- 公共 API 模塊 -->
            <dependency>
                <groupId>site.wendev.grpc</groupId>
                <artifactId>wendev-api</artifactId>
                <version>${project.version}</version>
            </dependency>

            <!-- Spring Cloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Nacos -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
                <version>${nacos.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                <version>${nacos.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.springframework.cloud</groupId>
                        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>

            <!-- gRPC -->
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-all</artifactId>
                <version>${grpc.version}</version>
            </dependency>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-netty-shaded</artifactId>
                <version>${grpc.version}</version>
            </dependency>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-protobuf</artifactId>
                <version>${grpc.version}</version>
            </dependency>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-stub</artifactId>
                <version>${grpc.version}</version>
            </dependency>
            <dependency>
                <groupId>javax.annotation</groupId>
                <artifactId>javax.annotation-api</artifactId>
                <version>${java.annotation.api.version}</version>
                <scope>provided</scope>
            </dependency>

            <!-- gRPC Spring Boot Starter -->
            <dependency>
                <groupId>net.devh</groupId>
                <artifactId>grpc-server-spring-boot-starter</artifactId>
                <version>${grpc.spring.boot.starter.verison}</version>
            </dependency>
            <dependency>
                <groupId>net.devh</groupId>
                <artifactId>grpc-client-spring-boot-starter</artifactId>
                <version>${grpc.spring.boot.starter.verison}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

這個(gè)grpc-spring-boot-starter貌似是國(guó)人寫的,贊!

公共API模塊

Maven依賴,主要是gRPC的依賴:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <artifactId>grpc-spring-cloud-final</artifactId>
        <groupId>site.wendev.grpc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>wendev-api</artifactId>
    <name>wendev-api</name>
    <packaging>jar</packaging>

    <properties>
        <os.plugin.version>1.6.2</os.plugin.version>
        <protoc.version>3.10.0</protoc.version>
        <protobuf.plugin.version>0.6.1</protobuf.plugin.version>
    </properties>

    <dependencies>
        <!-- gRPC -->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-all</artifactId>
        </dependency>

        <!-- Javax Annotation Api -->
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <!-- gRPC 代碼生成插件需要此 extension -->
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>${os.plugin.version}</version>
            </extension>
        </extensions>
        <plugins>
            <!-- protobuf java 代碼生成器 -->
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>${protobuf.plugin.version}</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}
                    </protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.25.0:exe:${os.detected.classifier}</pluginArtifact>
                    <protocExecutable>/Users/jiangwen/tools/protoc-3.10.0/bin/protoc</protocExecutable>
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

首在main文件夾下建立一個(gè)proto文件夾,寫個(gè)proto文件:

hello_world.proto

syntax = "proto3";

option java_package = "site.wendev.grpc.api";
option java_multiple_files = true;
option java_outer_classname = "HelloProto";

service HelloWorld {
    rpc Hello (HelloRequest) returns (HelloResponse) {
    }
}

message HelloRequest {
    string message = 1;
}

message HelloResponse {
    string response = 1;
}

然后像上一篇講的那樣把代碼生成出來(lái),放到相應(yīng)目錄下,再執(zhí)行mvn clean install把這個(gè)模塊安裝在本地,供其他服務(wù)調(diào)用。

服務(wù)提供者

Maven依賴:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>grpc-spring-cloud-final</artifactId>
        <groupId>site.wendev.grpc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>wendev-provider</artifactId>
    <version>${project.version}</version>
    <name>wendev-provider</name>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <!-- 公共 API 模塊 -->
        <dependency>
            <groupId>site.wendev.grpc</groupId>
            <artifactId>wendev-api</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- Consul -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <!-- gRPC Spring Boot Starter -->
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-server-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

配置:

bootstrap.yml

server:
  port: 8763
grpc:
  server:
    port: 0
spring:
  application:
    name: wendev-provider
  cloud:
    consul:
      host: 127.0.0.1
      port: 8500
      discovery:
        hostname: 127.0.0.1

gRPC的端口是0,這樣寫是“端口號(hào)隨機(jī)”的意思,可以保證每次啟動(dòng)端口都不同,就不用手動(dòng)修改了。server.port寫死了是因?yàn)樾枰堰@個(gè)值注入,返回給服務(wù)消費(fèi)者,如果不需要返回端口號(hào)也可以寫0。

然后編寫服務(wù)提供者:

/**
 * 服務(wù)提供者:返回服務(wù)消費(fèi)者發(fā)送的信息和端口號(hào)
 * 使用<code>@GrpcService</code>注解聲明這個(gè)服務(wù)提供者
 *
 * @author 江文
 * @date 2020/2/7 5:12 上午
 */
@GrpcService
public class HelloWorldService extends HelloWorldGrpc.HelloWorldImplBase {
    @Value("${server.port}")
    private String port;

    @Override
    public void hello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
        String message = String.format("Welcome to WenDev, your message is %s, from port %s."
                + "From: Spring Cloud + gRPC.",
                request.getMessage(), port);
        HelloResponse response = HelloResponse.newBuilder().setResponse(message).build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

題外話:大家可以從注釋中看到創(chuàng)建時(shí)間是昨天早上,所以我踩坑踩了整整一天。。。寫此文的目的除了總結(jié),就是希望大家不要繼續(xù)踩我踩過(guò)的坑。

邏輯很簡(jiǎn)單,主要就是生成一條響應(yīng)信息,然后返回響應(yīng)。代碼并不比使用Dubbo復(fù)雜多少。

這里遇到個(gè)大坑,差點(diǎn)沒(méi)把我坑死。。。

最新的gRPC版本是1.27.0,然而grpc-spring-boot-starter只支持到1.25,還不兼容1.27。。。暈死,為了換個(gè)版本還得重新下一個(gè)3.10版本的protoc(1.27是用的3.11.3版本的),然后重新生成代碼。。。說(shuō)不定我應(yīng)該去grpc-spring-boot-starter的倉(cāng)庫(kù)里提個(gè)issue?

服務(wù)消費(fèi)者

首先是依賴,與服務(wù)提供者非常相似,就是grpc-spring-boot-starter換成了服務(wù)消費(fèi)者的:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>grpc-spring-cloud-final</artifactId>
        <groupId>site.wendev.grpc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>wendev-consumer</artifactId>
    <version>${project.version}</version>
    <name>wendev-consumer</name>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <!-- 公共 API 模塊 -->
        <dependency>
            <groupId>site.wendev.grpc</groupId>
            <artifactId>wendev-api</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- Consul -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <!-- gRPC Spring Boot Starter -->
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

配置,也沒(méi)什么區(qū)別。因?yàn)闆](méi)有CA證書,用OpenSSL生成又一直報(bào)各種奇奇怪怪的錯(cuò)誤,就設(shè)定為不加密的plaintext了:

bootstrap.yml

server:
  port: 8720
spring:
  application:
    name: wendev-consumer
  cloud:
    consul:
      host: 127.0.0.1
      port: 8500
      discovery:
        hostname: 127.0.0.1
grpc:
  client:
    GLOBAL:
      security:
        enable-keep-alive: true
      keep-alive-without-calls: true
      negotiation-type: plaintext

業(yè)務(wù)邏輯代碼,首先是Service:

/**
 * 服務(wù)消費(fèi)者:遠(yuǎn)程調(diào)用服務(wù)提供者,并且接收消息返回給Controller。
 *
 * @author 江文
 * @date 2020/2/7 5:33 上午
 */
@Service
public class HelloWorldService {
    @GrpcClient("wendev-provider")
    private HelloWorldGrpc.HelloWorldBlockingStub stub;

    public String rpc(String message) {
        HelloRequest request = HelloRequest.newBuilder().setMessage(message).build();
        return stub.hello(request).getResponse();
    }
}

這個(gè)@GrpcClient加在stub上,或者如同網(wǎng)上絕大多數(shù)資料那樣加在channel上都可以,不過(guò)官方推薦加在stub上,就按照推薦的來(lái)。

代碼同樣也不復(fù)雜。

然后是Controller層,調(diào)用Service里的rpc方法,返回調(diào)用結(jié)果:

/**
 * Controller層,只有一個(gè)方法。
 *
 * @author 江文
 * @date 2020/2/7 5:36 上午
 */
@RestController
public class HelloWorldController {
    final HelloWorldService service;

    @GetMapping("/{message}")
    public String helloWorld(@PathVariable String message) {
        return service.rpc(message);
    }

    HelloWorldController(HelloWorldService service) {
        this.service = service;
    }
}

運(yùn)行

先說(shuō)一下,這里又一個(gè)巨坑!比上一個(gè)要大得多!排查這個(gè)花了我整整一天,最后發(fā)現(xiàn)居然不是我的錯(cuò)(雖說(shuō)選了不合適的注冊(cè)中心也算是我的錯(cuò)吧)。。。

本來(lái)是用的Nacos做的服務(wù)注冊(cè)與發(fā)現(xiàn)中心,結(jié)果請(qǐng)求總是出錯(cuò),報(bào)network closed for unknown reason的異常。后來(lái)?yè)Q了Consul,就改了依賴和加上了Consul服務(wù)注冊(cè)與發(fā)現(xiàn)的配置代碼,業(yè)務(wù)代碼改都沒(méi)改一下子就好了。。。

看來(lái)這是Nacos的bug,具體原因不明。不過(guò)用Consul不會(huì)出問(wèn)題,似乎用Eureka也不會(huì)。

先啟動(dòng)服務(wù)提供者,修改server.port多啟動(dòng)幾份,然后啟動(dòng)服務(wù)消費(fèi)者。在Consul里可以看到,啟動(dòng)成功了:

不過(guò)這個(gè)健康檢查失敗也不知道是哪里出了問(wèn)題,但是服務(wù)間調(diào)用正常,可能因?yàn)槲覜](méi)在yml文件里配置?

可以看到Tags里出現(xiàn)了gRPC的端口號(hào),gRPC客戶端(服務(wù)消費(fèi)者)就是通過(guò)這個(gè)Tag找到gRPC服務(wù)端(服務(wù)提供者)的端口的。

請(qǐng)求127.0.0.1:8720/HelloWorld可以發(fā)現(xiàn)消息成功返回了:

多刷新幾次,可以看到Consul提供的負(fù)載均衡效果:

通過(guò)這個(gè)例子,我們發(fā)現(xiàn)gRPC和Spring Cloud在grpc-spring-boot-strater的幫助下可以配合得非常好。雖然坑有些多,但畢竟gRPC不是Dubbo那種無(wú)縫集成的,出現(xiàn)一些坑也是可以理解的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容