Skywalking原理篇(二):Agent 与 OAP 的通信原理

GRPC 基础知识

基本介绍

gRPC 是一个高性能、开源和通用的 RPC 框架,面向服务端和移动端,基于 HTTP/2 协议标准而设计,默认使用 ProtoBuf(Protocol Buffers) 序列化协议进行开发,当前支持C、Java、Go等多种语言
gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库。与很多RPC系统一样,服务端负责实现定义好的接口并处理客户端的请求,客户端根据接口描述直接本地调用需要的服务而不用去关心具体底层通信细节和调用过程。客户端和服务端可以分别使用gRPC支持的不同语言进行实现

基本通信流程

在这里插入图片描述

  1. gRPC 通信的第一步是定义 IDL(Interface Definition Language)proto 文件
  2. 第二步是编译 proto 文件,得到存根 Stub 文件。Stub 中集成了服务调用、数据序列化等底层功能,客户端使用它与服务端进行交互。
  3. 第三步是服务端实现第一步定义的接口并启动,这些接口的定义也在存根 Stub 文件里面
  4. 最后一步是客户端借助 Stub 文件调用服务端的函数,虽然客户端调用的函数是有服务端实现的,但是调用起来就像是本地函数一样

多语言 Hello World 案例

案例获取地址

准备工作

IDEA 安装 Protobuf SupportProtobuf Editor 插件用于识别 proto 文件

Protobuf Support 插件官方已弃用,不支持IDEA19+的版本,由于我的电脑IDEA版本较低,所以用的这个
Protobuf Editor 可以支持最新版本的IDEA,但不支持部分老版本的

准备好 python 环境并安装 grpc 相关插件
pip3 install grpcio
pip3 install grpcio-tools

项目搭建

项目结构如下
grpc-sample
├── src
│   └── main
│        ├── java
│        │   └── io
│        │       └── grpc
│        │           └── sample
│        │               └── helloworld
│        ├── proto
│        ├── python
│        └── resources
└── pom.xml        
创建一个 Maven 工程,配置 pom 文件,导入 gRPC 的依赖和编译插件
<properties>
  <grpc-version>1.20.0</grpc-version>
</properties>

<dependencies>
  <dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-core</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>
</dependencies>

<build>
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.5.0.Final</version>
    </extension>
  </extensions>

  <plugins>
    <!-- protobuf插件 -->
    <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.9.1:exe:${os.detected.classifier}</pluginArtifact>
        <!-- proto文件的所在路径 -->
        <protoSourceRoot>src/main/proto</protoSourceRoot>
      </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>
      <version>3.6.1</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>
  </plugins>
</build>
在 main/proto 目录下创建 helloworld.proto 文件,定义服务和序列化数据结构
// 指定使用proto3的语法,如果不指定的话,编译器会使用proto2去编译
syntax = "proto3";

// option选项: 影响 特定环境下 的处理方式

// 不同的类会分散为多个java文件(默认false代表所有内容集中在同一个java文件)
option java_multiple_files = true;
// 指定生成的类应该放在什么Java包名下(默认采用包名)
option java_package = "io.grpc.sample.helloworld";
// 指定生成的java文件的类名(默认根据文件名采用驼峰式生成)
option java_outer_classname = "HelloWorldProto";

// 声明包名, 防止项目间命名冲突
package helloworld;

// 定义服务接口
service Greeter {
   
  // 定义一个简单rpc方法 sayHello(除此之外还有3种流式rpc方法)
  // 接收一个 HelloRequest 消息体, 返回一个 HelloReply 消息体
  rpc SayHello (HelloRequest) returns (HelloReply) {
   }
}

// 定义请求消息体
message HelloRequest {
   
  // 消息字段
  string name = 1;
}

// 定义响应消息体
message HelloReply {
   
  string message = 1;
}
使用 protobuf-maven-plugin 插件编译 proto 文件生成 Java 文件

前面在 pom 文件中加入了 protobuf 插件后,可以在 maven projects 视图界面看到以下几个编译指令

Plugins
└── protobuf
    ├── protobuf:compile # 编译消息对象, 生成java文件
    ├── protobuf:compile-cpp # 编译消息对象, 生成c文件
    ├── protobuf:compile-custom # 依赖消息对象, 生成接口服务文件
    ├── protobuf:compile-javanano # 编译消息对象, 生成javanano文件
    └── protobuf:compile-python # 编译消息对象, 生成python文件

我们在使用时,只需要执行以下两个指令

  • protobuf:compile
    默认在 target/generated-sources/protobuf/java 目录下生成消息文件
  • protobuf:compile-custom
    默认在 target/generated-sources/protobuf/grpc-java 目录下生成接口服务文件

执行完成后会生成如下文件

target/generated-sources/
├── annotations
└── protobuf
    ├── grpc-java
    │   └── io/grpc/sample/helloworld
    │       └── GreeterGrpc.java
    └── java
        └── io/grpc/sample/helloworld
            ├── HelloReply.java
            ├── HelloReplyOrBuilder.java
            ├── HelloRequest.java
            ├── HelloRequestOrBuilder.java
            └── HelloWorldProto.java

使用 grpcio-tools 工具编译 proto 文件生成 Python 文件
# 进入proto目录
cd proto
# 编译proto文件并将对应的python文件输出到python目录
python -m grpc_tools.protoc --python_out=../python --grpc_python_out=../python -I. helloworld.proto

执行完成后会在 python 目录下生成如下文件

python
├── helloworld_pb2.py # 消息文件
└── helloworld_pb2_grpc.py # 接口服务文件
创建 Java 服务端
  • 首通过监听端口创建 server 实例
  • 然后重载 GreeterGrpc.GreeterImplBase 中定义的 sayHello 方法来提供服务的具体实现,并将该服务注册到 server 实例中
  • 最后再启动 server 服务
package io.grpc.sample.helloworld;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

/**
 * HelloWorld 服务端
 * Server that manages startup/shutdown of a {@code Greeter} server.
 */
public class HelloWorldServer {
   
    private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());

    private Server server;

    private void start() throws IOException {
   
        int port = 50051;
        // 创建server对象, 监听50051端口
        server = ServerBuilder.forPort(port)
                // 注册服务
                .addService(new GreeterImpl())
                .build()
                .start();
        logger.info("Server started, listening on " + port);
        // 添加JVM停止时的回调函数
        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 {
   
                    HelloWorldServer.this.stop();
                } catch (InterruptedException e) {
   
                    e.printStackTrace(System.err);
                }
                System.err.println("*** server shut down");
            }
        });
    }

    private void stop() throws InterruptedException {
   
        if (server != null) {
   
            // 等待所有提交的任务执行结束关闭服务
            server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
        }
    }

    /**
     * Await termination on the main thread since the grpc library uses daemon threads.
     */
    private void blockUntilShutdown() throws InterruptedException {
   
        if (server != null) {
   
            server.awaitTermination();
        }
    }

    /**
     * Main launches the server from the command line.
     */
    public static void main(String[] args) throws IOException, InterruptedException {
   
        final HelloWorldServer server = new HelloWorldServer();
        // 启动服务
        server.start();
        // 阻塞当前主线程
        server.blockUntilShutdown();
    }

    // 扩展grpc自动生成的服务接口抽象, 实现业务功能
    static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
   

        @Override
        public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
   
            // 构建响应消息, 从请求消息中获取姓名并在前面拼接上"Hello "
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
            // 在流关闭或抛出异常前可以调用多次
            responseObserver.onNext
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值