介绍
gRpc是一种可以跨语言的Rpc框架,它可以跨语言的原因在于其接口定义文件的平台无关性。
这一点与Dubbo形成鲜明对比,Dubbbo的接口文件直接就是Java的接口类,进而在云原生时代限制了Dubbo的跨语言发展。
使用gRpc开发的主要步骤有:
- 定义proto接口文件,文件中约定了接口函数和入参出参的数据结构;
- 利用proto生成相应语言的接口代码,例如go,Java,python语言的代码等;
- 基于上述生产的代码进行编码,之后的服务端与客户端只需要约定好域名和端口即可完成调用。
例子
下面以Java语言为例,给出示例代码。
编写接口定义文件 .proto
syntax = "proto3";
package hello;
option java_multiple_files = true;
option java_package = "cn.liyongwei.api.hello";
service HelloService {
rpc sayHello(HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message =1;
}
生成Java接口类
- 新建一个
maven
项目; - 在
src/main
下新建一个proto
文件夹,然后把上述的接口定义保存为hello.proto
文件; - 在
pom
文件引入grpc
相关依赖,包括生成Java
接口类的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>cn.liyongwei.grpc</groupId> <artifactId>demo-api</artifactId> <version>2.0</version> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <protobuf.version>3.21.7</protobuf.version> <grpc.version>1.53.0</grpc.version><!-- CURRENT_GRPC_VERSION --> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-bom</artifactId> <version>${grpc.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty-shaded</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-stub</artifactId> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java-util</artifactId> <version>${protobuf.version}</version> </dependency> </dependencies> <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.4.1.Final</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.5.0</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.4.0:exe:${os.detected.classifier}</pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
- 在maven的插件中找到
proto
,右键点击compile
,选择第二项进行编译。编译成功后,在target/generated-class
中可以找到接口相关的类; - 点击
maven/deploy
把这个接口项目发布到自己电脑的maven仓库中,就可以在其他项目中使用了。
服务端类
- 新建一个
maven
项目,在pom
文件中引入如下依赖;<dependency> <groupId>cn.liyongwei.grpc</groupId> <artifactId>demo-api</artifactId> <version>2.0</version> </dependency>
- 实现接口类逻辑
package cn.liyongwei.grpc.service; import cn.liyongwei.api.hello.HelloReply; import cn.liyongwei.api.hello.HelloRequest; import cn.liyongwei.api.hello.HelloServiceGrpc; import io.grpc.stub.StreamObserver; import java.net.InetAddress; import java.net.UnknownHostException; @GrpcService(name = "helloService") public class HelloService extends HelloServiceGrpc.HelloServiceImplBase { @Override public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) { String hostname = null; try { hostname = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { e.printStackTrace(); } HelloReply reply = HelloReply.newBuilder().setMessage("hello, " + request.getName() + "! from " + hostname).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } }
- 定义注解,主要作用就是实现自动载入服务
package cn.liyongwei.grpc.service; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface GrpcService { String name() default ""; }
- 启动类
package cn.liyongwei.grpc.service; import io.grpc.BindableService; import io.grpc.Grpc; import io.grpc.InsecureServerCredentials; import io.grpc.Server; import io.grpc.ServerServiceDefinition; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; public class Application { private static final Logger logger = Logger.getLogger(Application.class.getName()); private Server server; public static void main(String[] args) throws InterruptedException, IOException { final Application application = new Application(); application.start(); application.blockUntilShutdown(); } private void start() throws IOException { List<ServerServiceDefinition> gRpcServices = scanServices(); int port = 50051; server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) .addServices(gRpcServices) .build() .start(); logger.info("Server started, listening on " + port); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { // Use stderr here since the logger may have been reset by its JVM shutdown hook. System.err.println("*** shutting down gRPC server since JVM is shutting down"); try { Application.this.stop(); } catch (InterruptedException e) { e.printStackTrace(); } System.err.println("*** server shut down"); } }); } private void stop() throws InterruptedException { if (server != null) { server.shutdown().awaitTermination(30, TimeUnit.SECONDS); } } private void blockUntilShutdown() throws InterruptedException { if (server != null) { server.awaitTermination(); } } private List<ServerServiceDefinition> scanServices() { List<ServerServiceDefinition> gRpcServices = new ArrayList<>(); String path = this.getClass().getPackage().getName(); path = path.replace(".", "/"); ClassLoader classLoader = this.getClass().getClassLoader(); URL resources = classLoader.getResource(path); File currentDirectory = new File(resources.getFile()); if(currentDirectory.isDirectory()) { File[] files = currentDirectory.listFiles(); for(File f : files) { String fileName = f.getAbsolutePath(); String className = fileName.substring(fileName.indexOf("cn"), fileName.indexOf(".class")); className = className.replace("\\", "."); try { Class<?> clazz = classLoader.loadClass(className); if(clazz.isAnnotationPresent(GrpcService.class)) { BindableService instance = (BindableService) clazz.getDeclaredConstructor().newInstance(); gRpcServices.add(instance.bindService()); } } catch (ClassNotFoundException e) { } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } } return gRpcServices; } }
- 运行
main
方法,开始监听本地50051端口
客户端类
- 创建一个SpringBoot项目,引入
web
和上述的接口类<dependency> <groupId>cn.liyongwei.grpc</groupId> <artifactId>demo-api</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.8-SNAPSHOT</version> </dependency>
- 配置类
package cn.liyongwei.grpc.client.config; import cn.liyongwei.api.hello.HelloServiceGrpc; import io.grpc.Grpc; import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * gRPC Client的配置——启动、建立channel、获取stub、关闭等 * 需要注册为Spring Bean * * @author 江文 * @date 2020/4/12 3:27 下午 */ @Component public class GrpcClientConfiguration { private ManagedChannel channel; public GrpcClientConfiguration() { start(); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { // Use stderr here since the logger may have been reset by its JVM shutdown hook. System.err.println("*** shutting down gRPC server since JVM is shutting down"); try { GrpcClientConfiguration.this.stop(); } catch (InterruptedException e) { e.printStackTrace(); } System.err.println("*** server shut down"); } }); } public void start() { String target = "localhost:50051"; // 开启channel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) .build(); } public void stop() throws InterruptedException { channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); } @Bean(name = "helloService") public HelloServiceGrpc.HelloServiceBlockingStub getHelloService() { // 通过channel获取到服务端的stub return HelloServiceGrpc.newBlockingStub(channel); } }
- Controller类
package cn.liyongwei.grpc.client.controller; import cn.liyongwei.api.hello.HelloReply; import cn.liyongwei.api.hello.HelloRequest; import cn.liyongwei.api.hello.HelloServiceGrpc; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController public class GRpcController { @Resource(name = "helloService") private HelloServiceGrpc.HelloServiceBlockingStub helloService; @GetMapping("/hello/{name}") public String sayHello(@PathVariable String name) { HelloReply helloReply = helloService.sayHello(HelloRequest.newBuilder().setName(name).build()); return helloReply.getMessage(); } }
- 启动项目,通过浏览器调用一下试试吧!