多语言gRPC开发入门与避坑指南

news2024/11/18 1:28:45

目录

gRPC相关介绍

什么是gPRC

gPRC的优点

gPRC的缺点

gPRC定位

协议缓冲区(Protocol Buffers)

四种调用方式

gRPC开发三大步骤

第一步:定义和编写proto服务文件

第二步:proto文件转化为gRPC代码

第三步:调用gRPC代码

详细开发细节

Server端-Java

 Client端-Java

Client端-Golang

步骤一:准备插件

步骤二:准备proto服务描述文件

步骤三:基于proto文件生成gRPC代码

步骤四:编写客户端代码调用gRPC

 Client端-Python

步骤一:安装grpc和相关工具:

步骤二:忽略

步骤三:基于proto文件生成gRPC代码

步骤四:编写客户端代码调用gRPC

避坑指南

Java

Golang

Python

总结


gRPC相关介绍

什么是gPRC

基于http2+protocol buffer技术,简单说就是编码压缩+缓存,追求高效率。

gPRC的优点

1、高性能,毕竟是rpc技术,这个优点是我们选择gRPC的主要出发点。
2、报文可在stream上双向流传输,这个是http协议做不到的,用到双向流的场景首选gRPC。

gPRC的缺点

1、开发步骤很复杂! 这个是被gRPC劝退的主要原因,开发过程一不小心就踩坑。
2、目前支持的不充分。简单说就是太新了,像浏览器啥的支持的不好,导致开发调试没那么方便。

gPRC定位

gPRC是CNCF里唯一的传输协议项目,它的定位就是云原生时代分布式、微服务集群的核心底层通信服务。基于这个定位,我们就有必须学习它的理由。

协议缓冲区(Protocol Buffers)

这个是gRPC协议的核心模块,.proto文件、protoc工具、protobuf插件等跟它有关的名词充斥着整个gRPC开发过程。
Protocol Buffers是google推出的一种序列化的协议,它的定位就像json、xml、对象二进制流等一样,是用于对象传输过程中编码解码的。
它比json还要精简,主要体现在编码时丢弃了一些不必要的内容,解码的时候抛弃了全扫描的方式。

四种调用方式

1、同步函数式,一来一回
rpc SayHello(HelloRequest) returns (HelloResponse);
2、 客户端流式,多来一回
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
3、服务端流式,一来多回
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
4、双向流,多来多回
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);

gRPC开发三大步骤

第一步:定义和编写proto服务文件

像定义API一样,直接编写一个.proto后缀的服务描述文件,如下:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.jingtao.library";
option java_outer_classname = "BookServiceProto";
option objc_class_prefix = "HLW";

package mybook;
// The greeting service definition.
service BookService {
  // Sends a greeting
  rpc check (RequestData) returns (Book) {}
}

// The request message containing the user's name.
message RequestData {
  string name = 1;
}

message Book {
  string name = 1;
  string auther = 2;
  int32 price = 3;
}

syntax定义协议版本
option部分是跟着语言耦合的部分
package定义了服务的包名
service定义了服务的函数
message定义了服务函数需要打的入参、出参等对象

第二步:proto文件转化为gRPC代码

该步骤是把上面的.proto文件通过工具或插件生成可以被程序直接调用的gRPC代码,因为要被程序调用,所以这一步是与语言紧耦合的,不同语言转换的方式各不相同。

第三步:调用gRPC代码

到了第三步就相对轻松了,第二步生成的gRPC文件已经以接口的方式将服务封装好了,我们要干的只是import它,然后调用它。

详细开发细节

Server端-Java

java语言.proto文件转化为gRPC有三种方式

方式一:独立转换
在操作系统中安装转换可视化或者命令工具,例如protoc(下载地址https://github.com/protocolbuffers/protobuf/releases),在操作系统层做好转换后将转换后的代码copy到代码项目中。
方式二:开发工具插件转化
以IDEA为例,转换的插件有GenProtoBuf、Protocol Buffer,而且都是免费的。
方式三:依赖转换
直接在项目中引入相关依赖插件,利用它们生成代码。例如java中的protobuf-java-util

我采用的是方式三,pom文件如下:

<?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>org.example</groupId>
    <artifactId>BookFactory</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <protoc.version>3.12.0</protoc.version>
        <grpc.version>1.56.1</grpc.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.12.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java-util -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>3.12.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.grpc/grpc-all -->
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-all</artifactId>
            <version>1.56.1</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}
                    </protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
                    </pluginArtifact>
                    <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
                    <outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

双击Plugin中protobuf的compile生成服务bean,通过custom生成可直接被上层调用的gRPC。

服务端引用并实现grpc代码

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-all</artifactId>
            <version>1.56.1</version>
        </dependency>

服务端代码如下:

package com.jingtao.library;

import io.grpc.Grpc;
import io.grpc.InsecureServerCredentials;
import io.grpc.Server;
import io.grpc.stub.StreamObserver;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

public class BookServer {

    private static final Logger logger = Logger.getLogger(BookServer.class.getName());

    private Server server;

    int port = 8088;

    private void start() throws IOException {
        server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
                .addService(new BookImpl())
                .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 {
                    BookServer.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);
        }
    }

    static class BookImpl extends BookServiceGrpc.BookServiceImplBase {

        @Override
        public void check(RequestData req, StreamObserver<Book> responseObserver) {
            Book reply = Book.newBuilder().setName(req.getName()).setAuther("Shakespeare").setPrice(35).build();
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
        }
    }

    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 BookServer server = new BookServer();
        server.start();
        server.blockUntilShutdown();
    }

}

 Client端-Java

Java客户端前面部分跟服务端一样,只有最后一步不同:

package com.jingtao.library;

import io.grpc.*;

import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class BookClientor {

    private static final Logger logger = Logger.getLogger(BookClientor.class.getName());

    private final BookServiceGrpc.BookServiceBlockingStub blockingStub;

    public BookClientor(Channel channel) {
        blockingStub = BookServiceGrpc.newBlockingStub(channel);
    }

    public void say(String name) {
        logger.info("Will try to greet " + name + " ...");
        RequestData request = RequestData.newBuilder().setName(name).build();
        Book response;
        try{
            response = blockingStub.check(request);
        }catch (StatusRuntimeException e) {
            logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
            return;
        }
        logger.info("Invoke say result is : " + response.getName()
                + " " + response.getAuther()
                + " " + response.getPrice());
    }

    public static void main(String[] args) throws Exception{
        String user = "les miserables";
        // Access a service running on the local machine on port 50051
        String target = "localhost:8088";
        ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create())
                .build();
        try {
            BookClientor client = new BookClientor(channel);
            client.say(user);
        } finally {
            // ManagedChannels use resources like threads and TCP connections. To prevent leaking these
            // resources the channel should be shut down when it will no longer be used. If it may be used
            // again leave it running.
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }
}

Client端-Golang

步骤一:准备插件

给protoc准备go相关插件和工具
git clone https://github.com/golang/protobuf.git $GOPATH/src/github.com/golang/protobuf
cd $GOPATH/src/
go install github.com/golang/protobuf/protoc-gen-go@latest
这一步的目标是准备好protoc-gen-go,搞定后一定要确保包含protoc-gen-go的bin在path中,可以被protoc用到。

步骤二:准备proto服务描述文件

注意需要显式的指定所生成gRPC代码的包名
option go_package="./;book";

步骤三:基于proto文件生成gRPC代码

准备好proto文件后,执行以下命令生成go的gRPG文件
protoc -I proto/ --go_out=plugins=grpc:proto proto/book_route.proto

步骤四:编写客户端代码调用gRPC

package book

import (
	bookgrpc "book-client-go/proto"
	"fmt"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func MyClient() {
	conn, err := grpc.Dial(":8088", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		// handle error
		panic(err)
	}
	defer conn.Close()

	client := bookgrpc.NewBookServiceClient(conn)

	req := bookgrpc.RequestData{
		Name: "world",
	}

	reply, err := client.Check(context.Background(), &req)
	if err != nil {
		fmt.Println("client.bookservice error:", err)
		return
	}

	fmt.Printf("get msg from server:[%v] \n", reply)

}

 Client端-Python

步骤一:安装grpc和相关工具:

pip install grpcio
pip install grpcio-tools  
pip install protobuf

步骤二:忽略

python不需要单独设置option_package,因为最终生成的gRPC代码在python中放在哪个目录下就引入哪个目录好了。

步骤三:基于proto文件生成gRPC代码

然后执行以下python命令将proto文件转换为gRPC的python代码
python -m grpc_tools.protoc -I ./ --python_out=./ --grpc_python_out=. ./book_route.proto

步骤四:编写客户端代码调用gRPC

import grpc

from book import book_route_pb2,book_route_pb2_grpc

def run():
    conn = grpc.insecure_channel('127.0.0.1:8088')
    client = book_route_pb2_grpc.BookServiceStub(channel=conn)
    reqeust = book_route_pb2.RequestData(name='jingtao')
    respnse = client.check(reqeust)
    print("received name:", respnse.name)
    print("received auther:", respnse.auther)
    print("received:", respnse.price)

if __name__ == '__main__':
    run()

避坑指南

Java

1、java.lang.NoSuchMethodError: java.nio.ByteBuffer.clear()Ljava/nio/ByteBuffer;

代码全部都编译过并切启动成功的前提下,客户端和服务端进行grpc交互时遇到这个报错

核心是jdk版本与grpc依赖包版本不一致导致的
我用的jdk1.8+protoc3.12.0+grpc1.57.0就会出这个错误,换成jdk1.8+protoc3.12.0+grpc1.56.1后问题解决。

 2、UnusedPrivateParameter unused:

通过插件基于proto文件生产的gprc代码中出现了UnusedPrivateParameter编译错误,核心原因是maven导入的依赖版本与实际所需要的依赖版本不一致导致的。

错误配置:

正确配置:

Golang

1、protoc-gen-go: unable to determine Go import path for "book_route.proto"

protoc -I proto/ --go_out=plugins=grpc:proto proto/book_route.proto的时候报错:

原因是我从java项目中直接copy了proto过来,但里面缺少go需要的配置,修改proto后问题解决。
所以gRPC开发过程中proto文件不能直接复制,需要摘选出service和message部分,这是一个比较讨厌的地方。 

Python

1、Cannot unpack file C:\Users\MGTV\AppData\Local\Temp\pip-unpack-iee13adu\simple

执行pip安装grpc时报错,原因是因为pip的源有问题。

开始pip安装网络太慢所以用到代理:
pip install grpcio-i http://pypi.mirrors.ustc.edu.cn/pypi/simple/ --trusted-host pypi.mirrors.ustc.edu.cn
把http://mirrors.aliyun.com/pypi/simple/替换成https://pypi.mirrors.ustc.edu.cn/simple/也解决不了,后来直接用了最原始的命令,没有走代理,问题解决

2、生成的_pd2.py缺少request和response的负载对象导致开发过程中编译不过

这个问题是grpc_tools在1.44版本以后由预定义变成了运行中自动生成对象了,不影响正常运行,这样虽然使代码更简洁了,但是可读性确实变差了。 

总结

本文用java实现了gRPC服务端,然后分别用java、go、python来调用,只给了“一来一回”同步函数式调用,其实gRPC支持4中调用方式,真实情况比复杂的多。但比起对接gRPC来说,开发环境的准备更复杂,坑也很多,希望通过本篇博文能把gRPC开发所有步骤和需要注意的地方都解释清楚。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/837198.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3—SCSS的安装、配置与使用

SCSS 安装 使用npm安装scss&#xff1a; npm install sass sass-loader --save-dev 配置 配置到全局 &#x1f31f;附赠代码&#x1f31f; css: {preprocessorOptions: {scss: {additionalData:import "./src/Function/Easy_I_Function/Echarts/ToSeeEcharts/utill.…

caj文件怎么转换成pdf?了解一下这种方法

caj文件怎么转换成pdf&#xff1f;如果你曾经遇到过需要将CAJ文件转换成PDF格式的情况&#xff0c;那么你一定知道这是一件麻烦的事情。幸运的是&#xff0c;现在有许多软件和工具可以帮助你完成这项任务。下面就给大家介绍一款使用工具。 【迅捷PDF转换器】是一款功能强大的工…

camunda-modeler(5.9.0)介绍及下载

官网地址: https://camunda.com/ 中文站点:http://camunda-cn.shaochenfeng.com Camunda Modeler是一个用于创建、编辑和验证BPMN、CMMN和DMN模型的工具。它提供了一个可视化的界面&#xff0c;使用户可以以图形方式设计和调整工作流程、决策表和案例管理模型。 具体来说&…

8266 ESP-07模块的使用 以及详细介绍

esp8266系列 陶瓷天线 版本 详细介绍说明 最近使用8266的ESP-01S做了个数据无线收发装置&#xff0c;发现板载天线信号太弱&#xff0c;装上外壳后信号更弱&#xff0c;因此考虑能否使用带有外接天线的模块代替ESP-01S。经过在安可信官网搜索发现&#xff0c;ESP07、ESP07S、ES…

2023上半年手机及数码行业分析报告(京东销售数据分析)

2023年上半年&#xff0c;手机市场迎来复苏&#xff0c;同环比来看&#xff0c;销量销额纷纷上涨。 而数码市场中&#xff0c;各个热门品类表现不一。微单相机及智能手表同比去年呈现增长态势&#xff0c;而笔记本电脑市场则出现下滑。 基于此现状&#xff0c;鲸参谋发布了20…

Python实现(条形码,二维码)生成识别

Python实现&#xff08;二维码&#xff0c;条形码&#xff09;生成识别 生成条形码生成二维码识别条形码二维码 生成条形码 安装barcode模块: $ pip install python-barcode barcode文档 import barcode from barcode.writer import ImageWriter # 更多了解&#xff1a;https…

TX验证码

好久没分享文章了&#xff0c;今天来水水文章。 水的意思就是&#xff1a;文章不会分析参数的生成流程。 最开始菜鸡弟弟以为腾讯防水墙就只有一个滑块来着&#xff0c;也感谢不不哥哥提供的网址。 来看下tx旗下的验证类型。 当然除了这些之外&#xff0c;还有一种六宫格的&a…

心率变异性HRV指标说明:SDNN,SDANN,RMSSD,SDNNIndex,SDSD,NN50,PNN50;TP,ULF,VLF,LF,HF

自主神经活动与多种疾病有关系&#xff0c;特别是与某些心血管疾病的死亡率&#xff0c;尤其与猝死率有关。同时&#xff0c;也公认心率变异性&#xff08;HRV&#xff09;分析是判断自主神经活动的常用的定量指标。HRV降低是预测心脏病人死亡的独立危险因子&#xff0c;有十分…

java gc分析

使用工具转换&#xff1a;https://ctbots.com/#/ 通用GC分析 jstat -gc -t pid堆内存分析 jstat -gccapacity -t pid年轻代GC分析 jstat -gcnew -t pid年轻代内存分析 jstat -gcnewcapacity -t pid老年代GC分析 jstat -gcold -t pid老年代内存分析 jstat -gcoldcapacity…

IO进程线程第七天(8.4)信号量,条件变量

在第一题的基础上加上一个需求&#xff1a;要求打印&#xff0c;倒置线程&#xff0c;顺序执行。出现的现象为先打印1234567&#xff0c;后打印7654321&#xff08;不使用flag&#xff09; 创建两个线程&#xff0c;其中一个线程读取文件中的数据&#xff0c;另外一个线程将读…

新手Vite打包工具的使用并解决yarn create vite报错

一、手动创建 1.创建vite-Demo文件夹 2.初始化 yarn init -y 3.安装vite yarn add -D vite 4.打包准备 说明&#xff1a;不需要在src下面创建&#xff0c;在vite-Demo文件夹创建 4.1index.js文件 document.body.insertAdjacentHTML("beforeend","<h1>…

MySQL函数(二十五)

二八佳人体似酥&#xff0c;腰悬利剑斩愚夫&#xff0c;虽然不见人头落,暗里教君骨髓枯。 上一章简单介绍了 MySQL存储过程(二十四),如果没有看过,请观看上一章 前面学习了很多函数&#xff0c;使用这些函数可以对数据进行的各种处理操作&#xff0c;极大地提高用户对数据库的…

Linux(三):Linux服务器下日常实操命令 (常年更新)

基础命令 cd命令&#xff1a;切换目录 cd &#xff1a;切换当前目录百至其它目录&#xff0c;比如进入/etc目录&#xff0c;则执行 cd /etccd / &#xff1a;在Linux 系统中斜杠“/”表示的是根目录。cd / ,即进入根目录.cd ~&#xff1a;进入用户在该系统的home目录&#…

小程序商品如何设置限购

限购是一种常用的小程序商品销售策略&#xff0c;可以帮助商家提高销售额、控制库存和增加用户的购买欲望。那么&#xff0c;小程序产品怎么设置限购呢&#xff1f;下面将为您详细介绍。 1. 设置限购数量 可以设置最低购买数量来鼓励用户批量购买或满足特定的销售需求。例如&…

为什么大多数团队推行自动化测试最后却不了了之?

随着软件行业的快速发展&#xff0c;接口测试用例在软件开发中扮演着越来越重要的角色。自动化测试作为软件测试的一个重要分支&#xff0c;一般可以提高测试效率和质量&#xff0c;节约测试成本和时间&#xff0c;但是在实际推行过程中&#xff0c;大多数团队最终却难以持续实…

在word的文本框内使用Endnote引用文献,如何保证引文编号按照上下文排序

问题 如下图所示&#xff0c;我在word中插入了一个文本框&#xff08;为了插图&#xff09;&#xff0c;然后文本框内有引用&#xff0c;结果endnote自动将文本框内的引用优先排序&#xff0c;变成文献[1]了&#xff0c;而事实上应该是[31]。请问如何能让文本框内的排序也自动…

[CKA]考试之持久化存储卷PersistentVolume

由于最新的CKA考试改版&#xff0c;不允许存储书签&#xff0c;本博客致力怎么一步步从官网把答案找到&#xff0c;如何修改把题做对&#xff0c;下面开始我们的 CKA之旅 题目为&#xff1a; Task 创建一个pv&#xff0c;名字为app-config&#xff0c;大小为2Gi&#xff0c;…

数据结构初阶--二叉树的顺序结构之堆

目录 一.堆的概念及结构 1.1.堆的概念 1.2.堆的存储结构 二.堆的功能实现 2.1.堆的定义 2.2.堆的初始化 2.3.堆的销毁 2.4.堆的打印 2.5.堆的插入 向上调整算法 堆的插入 2.6.堆的删除 向下调整算法 堆的删除 2.7.堆的取堆顶元素 2.8.堆的判空 2.9.堆的求堆的…

PyMol选择配体周围的氨基酸残基

PyMOL选择配体周围的氨基酸残基 1. 问题 经常使用PyMOL做蛋白质和小分子的可视化&#xff0c;可以直接生成用于文章发表的高质量图片&#xff0c;图片生成可参考pymol作图 等教程。但是&#xff0c;用了这么久一直没发现可以直接用于选择配体周围一定距离氨基酸残基的功能。 …

Java阶段五Day17

Java阶段五Day17 文章目录 Java阶段五Day17师傅后台功能师傅审核列表相关功能启动进程和启动方式 后台审核详情查询查询审核详情流程远程调用图片服务 缓存逻辑缓存逻辑流程查询引入缓存流程完成缓存逻辑面试题整理 附录redis分布式——架构演变 师傅后台功能 师傅审核列表 相…