gRPC 本地服务搭建
- RPC
- RPC 原理
- 主流 RPC 框架
- gRPC
- 概述
- 特点
- 服务端创建
- 定义服务
- 生成 gRPC 代码
- 服务端实现
- 客户端实现
- 踩坑记录
- 源码
RPC
RPC 原理
RPC 框架的目标就是让远程服务调用更加简单、透明,RPC 框架负责屏蔽底层的传输方式(TCP 或者 UDP)、序列化方式(XML/Json/二进制)和通信细节。服务调用者可以像调用本地接口一样调用远程的服务提供者,而不需要关心底层通信细节和调用过程。
RPC 框架的调用原理图:
主流 RPC 框架
- 支持多语言的 RPC 框架,如 Google 的 grpc、Apache 的 Thrift
- 只支持特定语言的 RPC 框架,例如新浪微博的 Motan
- 支持服务治理等服务化特性的分布式服务框架,其底层内核仍然是 RPC 框架,例如阿里的 Doubble
随着微服务的发展,基于语言中立性原则构建微服务,逐渐成为一种主流模式,例如对于后端并发处理要求高的微服务,比较适合采用 Go 语言构建,而对于前端的 Web 界面,更适合 Java 和 JavaScript。
因此,基于多语言的 RPC 框架来构建微服务,是一种比较好的技术选择。例如 Netflix,API 服务编编排层和后端的微服务之间就采用 grpc 进行通信。
gRPC
概述
gRPC 是由 Google 开发并开源的一种语言中立的 RPC 框架,当前支持 C、Java 和 Go 语言。
gRPC的调用示例如下所示:
特点
- 语言中立,支持多种语言;
- 基于 IDL 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
- 通信协议基于标准的 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
- 序列化支持 PB (Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB,保障了 RPC 调用的高性能。
服务端创建
定义服务
一个 RPC 服务通过参数和返回值类型来指定可以远程调用的方法。在这里我们使用 protocal buffers 接口定义语言来定义服务方法。客户端和服务端均使用服务定义生成的接口代码。
创建 helloworld.proto 文件:
在这里我们定义一个服务。Greeter 服务有一个方法 SayHello ,可以让服务端从远程客户端接收一个包含用户名的 HelloRequest 消息后,在一个 HelloReply 里发送回一个Greeter。
syntax = "proto3";
option java_package = "io.grpc.examples";
package helloworld;
// The greeter service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
生成 gRPC 代码
添加依赖:
<properties>
<grpc.version>1.0.3</grpc.version>
</properties>
<!--grpc所依赖的包-->
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</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>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
</dependency>
</dependencies>
<build>
<!--解析proto的工具-->
<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.1.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
添加完依赖后使用 IDEA Maven 的 compile :
生成代码,在如下图目录中:
以下类包含所有我们需要创建这个例子的所有代码:
HelloRequest.java,HelloResponse.java 和其它文件所包含的所有 protocol buffer 用来填充、序列化和提取
HelloRequest
和HelloReply
类型的代码。GreeterGrpc.java, 包含 (还有其他有用的代码):
Greeter
服务端需要实现的接口public static interface Greeter { public void sayHello(Helloworld.HelloRequest request, StreamObserver<Helloworld.HelloReply> responseObserver); }
客户端用来与
Greeter
服务端进行对话的存根
类。就像你所看到的,异步存根也实现了Greeter
接口。public static class GreeterStub extends AbstractStub<GreeterStub> implements Greeter { ... }
服务端实现
public class HelloWorldServer {
private int port = 50051;
private Server server;
/**
* 启动服务
* @throws IOException
*/
private void start() throws IOException {
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
.start();
System.out.println("service start...");
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
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();
}
}
// block 一直到退出程序
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();
}
// 实现 定义一个实现服务接口的类
private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
public void sayHello(HelloRequest req,
StreamObserver<HelloReply> responseObserver) {
//获取参数
System.out.println("收到的信息:" + req.getName());
//这里可以放置具体业务处理代码 start
//这里可以放置具体业务处理代码 end
//构造返回
HelloReply reply = HelloReply.newBuilder()
.setMessage(("Hello: " + req.getName())).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}
客户端实现
public class HelloWorldClient {
private final ManagedChannel channel; //一个gRPC信道
private final GreeterGrpc.GreeterBlockingStub blockingStub;//阻塞/同步 存根
//初始化信道和存根
public HelloWorldClient(String host,int port){
this(ManagedChannelBuilder.forAddress(host, port)
// Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
// needing certificates.
.usePlaintext(true));
}
/** Construct client for accessing RouteGuide server using the existing channel. */
private HelloWorldClient(ManagedChannelBuilder<?> channelBuilder) {
channel = channelBuilder.build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
//客户端方法
public void greet(String name){
HelloRequest request = HelloRequest
.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
System.out.println("RPC调用失败:"+e.getMessage());
return;
}
System.out.println("服务器返回信息:"+response.getMessage());
}
public static void main(String[] args) throws InterruptedException {
HelloWorldClient client = new HelloWorldClient("127.0.0.1",50051);
try {
for(int i=0;i<5;i++){
client.greet("world:"+i);
}
}finally {
client.shutdown();
}
}
}
先运行服务端再与运行客户端,结果:
客户端结果:
服务器返回信息:Hello: world:0
服务器返回信息:Hello: world:1
服务器返回信息:Hello: world:2
服务器返回信息:Hello: world:3
服务器返回信息:Hello: world:4
服务端结果:
service start...
收到的信息:world:0
收到的信息:world:1
收到的信息:world:2
收到的信息:world:3
收到的信息:world:4
自此,一个简单的本地 RPC 调用就完成了。
踩坑记录
proto 文件必须放在 main 目录下,否则 compile 的时候不会生成 target 里面的代码。
源码
源码
GitHub 学习仓库
相关文章