gRPC从入门到破产(上)纯Java语言:gRPC单独使用/与Spring Cloud整合

用惯了Spring全家桶,偶尔也尝试一下小清新的gRPC吧
为啥叫“从入门到破产”呢?因为中文输入法打“gRPC”出来的第一个就是“个人破产”,咱也总不能干啥都“从入门到入土”,“从入门到放弃”吧,偶尔也来点别的,哈哈。

一些废话

做完个人网站之后就一直处于非常颓废的状态,而最近总算找到要学的新东西了——gRPC

为什么要学gRPC?还不是因为闲得都要发霉了,急需补充新知识啊,而且我一直觉得gRPC比Dubbo“小清新”多了,非常想找个机会一探究竟。

其实是最近要做一个比较大的项目,涉及到Python和Java混编的问题,为了解决这个问题,所以需要一个可以跨语言的RPC框架。本来想用性能更高的Thrift,但是这东西的参考资料实在太少了,所以选择了资料更多的gRPC。而且gRPC是Google开发的,大厂出品值得信赖啊。虽说Thrift是Facebook出品,也不差啦。

Refrence

HelloWorld

pom.xml

建一个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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>site.wendev</groupId>
    <artifactId>grpc-helloworld</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/io.grpc/grpc-all -->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-all</artifactId>
            <version>1.27.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <!-- https://mvnrepository.com/artifact/kr.motd.maven/os-maven-plugin -->
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.11.3:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.27.0:exe:${os.detected.classifier}</pluginArtifact>
                    <protocExecutable>/Users/jiangwen/tools/protoc-3.11.3/bin/protoc</protocExecutable>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

不需要Spring Initializr了,莫名感觉非常爽

proto文件的编写与代码生成

gRPC使用HTTP2作为通信协议,使用Google开源的protobuf作为序列化和反序列化协议,所以需要编写.proto文件,定义服务和其他相关信息:

hello_world.proto

syntax = "proto3";

option java_package = "site.wendev.grpc";
option java_outer_classname = "HelloWorldProto";

package helloworld;

service HelloWorld {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

简单来说,这段代码定义了一个发送一条信息的服务,并且定义了请求信息——包含name参数和响应信息——包含message参数。

然后使用Maven插件生成代码:

这两个插件都需要使用。

生成的文件如下,这两个文件里包含了我们需要的一些东西:

接着把它们放到项目里,就可以使用了:

服务端与客户端

这个因为太复杂,就照搬官方github仓库里的了。因为我的类名与官方的不一样,所以稍微改了改:

服务端(HelloWorldServer.java):

package site.wendev.grpc;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.io.IOException;
import java.util.logging.Logger;

public class HelloWorldServer {
    public static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
    private Server server;

    private void start() throws IOException {
        int port = 50051;
        server = ServerBuilder.forPort(port)
                .addService(new GreeterImpl())
                .build()
                .start();
        logger.info("Server started, listening on " + port);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.err.println("*** shutting down gRPC server since JVM is shutting down");
            HelloWorldServer.this.stop();
            System.err.println("*** server shut down");
        }));
    }

    private void stop() {
        if (server != null) {
            server.shutdown();
        }
    }

    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        final HelloWorldServer server = new HelloWorldServer();
        server.start();
        server.blockUntilShutdown();
    }

    static class GreeterImpl extends HelloWorldGrpc.HelloWorldImplBase {

        @Override
        public void sayHello(HelloWorldProto.HelloRequest req, StreamObserver<HelloWorldProto.HelloReply> responseObserver) {
            HelloWorldProto.HelloReply reply = HelloWorldProto.HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
        }
    }
}

客户端(HelloWorldClient.java):

package site.wendev.grpc;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;

import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HelloWorldClient {
    private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());

    private final ManagedChannel channel;
    private final HelloWorldGrpc.HelloWorldBlockingStub blockingStub;

    public HelloWorldClient(String host, int port) {
        this(ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext()
                .build());
    }

    HelloWorldClient(ManagedChannel channel) {
        this.channel = channel;
        blockingStub = HelloWorldGrpc.newBlockingStub(channel);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    public void greet(String name) {
        logger.info("client request ==============" + name + " ...");
        HelloWorldProto.HelloRequest request = HelloWorldProto.HelloRequest.newBuilder().setName(name).build();
        HelloWorldProto.HelloReply response;
        try {
            response = blockingStub.sayHello(request);
        } catch (StatusRuntimeException e) {
            logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
            return;
        }
        logger.info("response: ===============" + response.getMessage());
    }

    public static void main(String[] args) throws Exception {
        HelloWorldClient client = new HelloWorldClient("localhost", 50051);
        try {
            String user = "world===============";
            if (args.length > 0) {
                user = args[0];
            }
            client.greet(user);
        } finally {
            client.shutdown();
        }
    }
}

运行

与Spring Cloud一样,需要先启动服务端(服务提供者),再启动客户端(服务消费者)。

启动服务端:

启动客户端:

客户端在启动时会向服务端发送请求(这个请求在greet方法里定义了),而客户端得到响应后,就立刻关闭了。

踩坑记录

这次的两个坑都是缺依赖,没有其他的什么坑。

坑1:使用Maven插件生成代码失败

在生成客户端的代码时,报了这么一个错误:

Could not transfer artifact io.grpc:protoc-gen-grpc-java:exe:${os.detected.classifier}:1.27.0 from/to alimaven (http://maven.aliyun.com/nexus/content/groups/public/): TransferFailedException

多番排查无果后,发现是缺了一个依赖(在build节点下):

        <extensions>
            <extension>
                <!-- https://mvnrepository.com/artifact/kr.motd.maven/os-maven-plugin -->
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>

加上这个依赖,就可以成功生成了。

坑2:Error:(23, 18) java: 找不到符号 符号: 类 Generated 位置: 程序包javax.annotation.Generated

这个错误发生在自动生成的代码中。

查了一下,这个其实也是缺少依赖导致的,加上它就好了:

<!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>

gRPC与SpringCloud整合

写在前面

前一段时间学习SpringCloud时尝试了SpringCloud整合Dubbo,感觉非常棒。那比Dubbo更快、还有着跨语言特性的gRPC与SpringCloud又会碰撞出什么样的火花呢?试试就知道。

当然,一开始写这个小例子也并不复杂,还是从最简单的一个服务提供者,一个服务消费者,发送一条HelloWorld并显示端口号开始。

服务注册与发现中心选择了Consul,本来用的是Nacos,结果出了莫名其妙的Bug(这个在下面会写),换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启动Consul

以前一直是使用直接运行可执行文件启动Consul,而使用docker比直接运行jar包更方便管理,所以这次就使用Docker来运行。

这里顺便记录一下Nacos的Docker启动方式:

Nacos

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

Consul

只启动一个实例的话可以这样启动:

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

运行完毕就可以通过localhost:8500访问了。

开始!

整个项目的目录结构:

父级依赖管理工程

<?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>

这个grpc-spring-boot-starter貌似是国人写的,赞!

公共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文件夹下建立一个proto文件夹,写个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;
}

然后像上一篇讲的那样把代码生成出来,放到相应目录下,再执行mvn clean install把这个模块安装在本地,供其他服务调用。

服务提供者

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,这样写是“端口号随机”的意思,可以保证每次启动端口都不同,就不用手动修改了。server.port写死了是因为需要把这个值注入,返回给服务消费者,如果不需要返回端口号也可以写0。

然后编写服务提供者:

/**
 * 服务提供者:返回服务消费者发送的信息和端口号
 * 使用<code>@GrpcService</code>注解声明这个服务提供者
 *
 * @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();
    }
}

题外话:大家可以从注释中看到创建时间是昨天早上,所以我踩坑踩了整整一天。。。写此文的目的除了总结,就是希望大家不要继续踩我踩过的坑。

逻辑很简单,主要就是生成一条响应信息,然后返回响应。代码并不比使用Dubbo复杂多少。

这里遇到个大坑,差点没把我坑死。。。

最新的gRPC版本是1.27.0,然而grpc-spring-boot-starter只支持到1.25,还不兼容1.27。。。晕死,为了换个版本还得重新下一个3.10版本的protoc(1.27是用的3.11.3版本的),然后重新生成代码。。。说不定我应该去grpc-spring-boot-starter的仓库里提个issue?

服务消费者

首先是依赖,与服务提供者非常相似,就是grpc-spring-boot-starter换成了服务消费者的:

<?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>

配置,也没什么区别。因为没有CA证书,用OpenSSL生成又一直报各种奇奇怪怪的错误,就设定为不加密的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

业务逻辑代码,首先是Service:

/**
 * 服务消费者:远程调用服务提供者,并且接收消息返回给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();
    }
}

这个@GrpcClient加在stub上,或者如同网上绝大多数资料那样加在channel上都可以,不过官方推荐加在stub上,就按照推荐的来。

代码同样也不复杂。

然后是Controller层,调用Service里的rpc方法,返回调用结果:

/**
 * Controller层,只有一个方法。
 *
 * @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;
    }
}

运行

先说一下,这里又一个巨坑!比上一个要大得多!排查这个花了我整整一天,最后发现居然不是我的错(虽说选了不合适的注册中心也算是我的错吧)。。。

本来是用的Nacos做的服务注册与发现中心,结果请求总是出错,报network closed for unknown reason的异常。后来换了Consul,就改了依赖和加上了Consul服务注册与发现的配置代码,业务代码改都没改一下子就好了。。。

看来这是Nacos的bug,具体原因不明。不过用Consul不会出问题,似乎用Eureka也不会。

先启动服务提供者,修改server.port多启动几份,然后启动服务消费者。在Consul里可以看到,启动成功了:

不过这个健康检查失败也不知道是哪里出了问题,但是服务间调用正常,可能因为我没在yml文件里配置?

可以看到Tags里出现了gRPC的端口号,gRPC客户端(服务消费者)就是通过这个Tag找到gRPC服务端(服务提供者)的端口的。

请求127.0.0.1:8720/HelloWorld可以发现消息成功返回了:

多刷新几次,可以看到Consul提供的负载均衡效果:

通过这个例子,我们发现gRPC和Spring Cloud在grpc-spring-boot-strater的帮助下可以配合得非常好。虽然坑有些多,但毕竟gRPC不是Dubbo那种无缝集成的,出现一些坑也是可以理解的。

评论

发表评论