一个小巧的单体应用会随着公司业务的扩张而慢慢成长,逐渐演化成一个庞大且复杂的系统怪物,系统任何一处的问题都将影响整个怪物的表现,很少有单独的开发者能理清系统怪物所有的肌理脉络,导致bug的定位和新功能的扩展都变得越来越困难,对系统的任一改动都要求整个怪物一起回归测试并重新部署,效率必然不高。所以公司发展到了一定阶段,总会需要从架构上寻找解决系统怪物之道,而微服务就是目前最流行的架构方案之一,它将系统怪物拆分成多个独立自治的服务小怪兽,让我们有能力分而治之。
[插画:无数小怪兽组成一座大怪兽形状的山,小怪兽正一个个从山上滚下来]
一旦系统怪物被拆分成了多个服务小怪兽,小怪兽们如何沟通协作就成了我们最关心的问题。服务小怪兽间的通信就好像发电报一样,涉及到数据序列化、反序列化、连接管理、收发线程、超时处理等多个问题,RPC框架的出现解决了这些问题,就好像通过电报员发电报一样,使用RPC框架让小怪兽们不必关心通信的底层细节。
[插画:小怪兽在请电报员发电报]
如果通信的小怪兽们语言不通,那么我们需要对电报员(亦即RPC框架)的人选提出更高的要求,无论小怪兽们用的是什么语言,协助通信的两位电报员都必须把它们的话翻译成电报员彼此能理解的同一种语言,亦即IDL(Interface Description Language),是的,电报员在这种情况下还必须承担翻译的角色,而gRPC就是一位如此优秀的电报员。
[插画:小怪兽说"今晚的月色真美",电报员写”I Love you“]
实现Node客户端小怪兽发送"今晚的月色真美",Java服务端小怪兽收到电报内容,并回复"I love you too"。
通过Spring Boot创建Java项目,pom.xml中加入如下依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty-shaded</artifactId> <version>1.21.0</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> <version>1.21.0</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-stub</artifactId> <version>1.21.0</version> </dependency> </dependencies> <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.5.0.Final</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.5.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.21.0:exe:${os.detected.classifier}</pluginArtifact> <!--指定生成文件目录--> <outputDirectory>src/main/java</outputDirectory> <!--重新生成文件时不清除 原有src/main/java下的内容--> <clearOutputDirectory>false</clearOutputDirectory> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
定义IDL文件
syntax = "proto3"; option java_multiple_files = true; option java_package = "net.changjinglu.proto"; option java_outer_classname = "TelegraphProto"; package telegraph; // The greeting service definition. service TelegraphService { // Sends a greeting rpc SayLove (LoveRequest) returns (LoveReply) {} } // The request message containing the user's name. message LoveRequest { string message = 1; } // The response message containing the greetings message LoveReply { string message = 1; }
编译生成IDL定义的Java服务接口
mvn clean install
实现IDL定义的Java服务接口
public class TelegraphGreeterImpl extends TelegraphServiceGrpc.TelegraphServiceImplBase { @Override public void sayLove(LoveRequest request, StreamObserver<LoveReply> responseObserver) { System.out.println("收到Node小怪兽的消息:"+request.getMessage()); responseObserver.onNext(LoveReply.newBuilder().setMessage("I Love U Too").build()); //结束 responseObserver.onCompleted(); } }
编写并启动Java服务端
public class GrpcServer { /** GRPC 服务端 */ private Server server; public static void main(String[] args) throws IOException, InterruptedException { GrpcServer grpcService = new GrpcServer(); grpcService.start(); System.out.println("GRPC 服务端启动成功"); //GRPC 服务端需要手动阻塞线程 grpcService.waitTermination(); } private void start() throws IOException { //绑定接口、启动服务 this.server = ServerBuilder.forPort(8899) .addService(new TelegraphGreeterImpl()) .build() .start(); System.out.println("server start!"); //这里是为了防止jvm关闭了,但是tcp还没有关闭的情况 Runtime.getRuntime().addShutdownHook(new Thread(()->{ System.out.println("关闭jvm"); GrpcServer.this.stop(); })); } private void stop() { if (this.server != null) { this.server.shutdown(); } } private void waitTermination() throws InterruptedException { if (this.server != null) { server.awaitTermination(); } } }
编写并启动Nodejs客户端,客户端使用相同的IDL
var PROTO_FILE_PATH = '/Users/wenyuan/Nodejs/grpc/proto/telegraph.proto'; var grpc = require('grpc'); var grpcService = grpc.load(PROTO_FILE_PATH).telegraph; function main() { var stub = new grpcService.TelegraphService('localhost:8899',grpc.credentials.createInsecure()); stub.sayLove({message:'今晚的月色真美'},function (error, result) { console.log('收到Java小怪兽的消息: ' + result.message); }); } main();