欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

gRPC 本地服务搭建

程序员文章站 2022-03-20 20:27:21
"RPC" "RPC 原理" "主流 RPC 框架" "gRPC" "概述" "特点" "服务端创建" "定义服务" "生成 gRPC 代码" "服务端实现" "客户端实现" "踩坑记录" "源码" RPC RPC 原理 RPC 框架的目标就是 让远程服务调用更加简单、透明,RPC 框架负责屏蔽底层 ......

rpc

rpc 原理

rpc 框架的目标就是让远程服务调用更加简单、透明,rpc 框架负责屏蔽底层的传输方式(tcp 或者 udp)、序列化方式(xml/json/二进制)和通信细节。服务调用者可以像调用本地接口一样调用远程的服务提供者,而不需要关心底层通信细节和调用过程。

rpc 框架的调用原理图:

gRPC 本地服务搭建

主流 rpc 框架

  1. 支持多语言的 rpc 框架,如 google 的 grpc、apache 的 thrift
  2. 只支持特定语言的 rpc 框架,例如新浪微博的 motan
  3. 支持服务治理等服务化特性的分布式服务框架,其底层内核仍然是 rpc 框架,例如阿里的 doubble

随着微服务的发展,基于语言中立性原则构建微服务,逐渐成为一种主流模式,例如对于后端并发处理要求高的微服务,比较适合采用 go 语言构建,而对于前端的 web 界面,更适合 java 和 javascript。

因此,基于多语言的 rpc 框架来构建微服务,是一种比较好的技术选择。例如 netflix,api 服务编编排层和后端的微服务之间就采用 grpc 进行通信。

grpc

概述

grpc 是由 google 开发并开源的一种语言中立的 rpc 框架,当前支持 c、java 和 go 语言。

grpc的调用示例如下所示:

gRPC 本地服务搭建

特点

  1. 语言中立,支持多种语言;
  2. 基于 idl 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 stub;
  3. 通信协议基于标准的 http/2 设计,支持双向流、消息头压缩、单 tcp 的多路复用、服务端推送等特性,这些特性使得 grpc 在移动端设备上更加省电和节省网络流量;
  4. 序列化支持 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 :

gRPC 本地服务搭建

生成代码,在如下图目录中:

gRPC 本地服务搭建

以下类包含所有我们需要创建这个例子的所有代码:

  • hellorequest.java,helloresponse.java 和其它文件所包含的所有 protocol buffer 用来填充、序列化和提取 hellorequesthelloreply 类型的代码。

  • greetergrpc.java, 包含 (还有其他有用的代码):

    greeter 服务端需要实现的接口

    public static interface greeter {
      public void sayhello(helloworld.hellorequest request,
               streamobserver&lt;helloworld.helloreply> responseobserver);
    }

    客户端用来与 greeter 服务端进行对话的 存根 类。就像你所看到的,异步存根也实现了 greeter 接口。

    public static class greeterstub extends abstractstub&lt;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 学习仓库