拥抱云原生,Java与Python基于gRPC通信

在这里插入图片描述

😊你好,我是小航,一个正在变秃、变强的文艺倾年。
🔔本文讲解实战gRPC通信,欢迎大家多多关注!
🔔每天进步一点点,一起卷起来叭!

需求描述:

前端-java后台-python算法分析-java处理分析结果返回

大概就是这么一个情况,公司产品需要一个上万人排班(而且可能是多个上万人进行排班)的功能,但系统是基于java做的后台,公司的算法那工程师使用的是python进行算法实现,故需要进行跨系统支持,毕竟算法运算非常非常吃资源

gRPC简介:

gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

gRPC是一个现代的开源高性能远程过程调用(RPC)框架,可以在任何环境中运行。它可以有效地连接数据中心内和跨数据中心的服务,支持负载均衡、跟踪、健康检查和身份验证。它也适用于分布式计算,将设备、移动应用程序和浏览器连接到后端服务
在这里插入图片描述
好处可概括为:1.简单 2.快 3.跨语言跨平台 4.双向+安全

gRPC 是Google公司开发的一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。

  • gRpc官网地址:https://www.grpc.io
  • gRpc中文文档地址:http://doc.oschina.net/grpc

具体内容查看文档即可

gRPC机制:

在这里插入图片描述
当客户端发送请求的网络消息到达服务器时,服务器上的网络服务将其传递给服务器存根(server-stub)。服务器存根与客户端存根一一对应,是远程方法在服务端的体现,用来将网络请求传递来的数据转换为本地过程调用。服务器存根一般处于阻塞状态,等待消息输入。

当服务器存根收到网络消息后,服务器将方法参数从网络消息中提取出来,然后以常规方式调用服务器上对应的实现过程。从实现过程角度看,就好像是由客户端直接调用一样,参数和返回地址都位于调用堆栈中,一切都很正常。实现过程执行完相应的操作,随后用得到的结果设置到堆栈中的返回值,并根据返回地址执行方法结束操作。以 read 为例,实现过程读取本地文件数据后,将其填充到 read 函数返回值所指向的缓冲区。

read 过程调用完后,实现过程将控制权转移给服务器存根,它将结果(缓冲区的数据)打包为网络消息,最后通过网络响应将结果返回给客户端。网络响应发送结束后,服务器存根会再次进入阻塞状态,等待下一个输入的请求。

客户端接收到网络消息后,客户操作系统会将该消息转发给对应的客户端存根,随后解除对客户进程的阻塞。客户端存根从阻塞状态恢复过来,将接收到的网络消息转换为调用结果,并将结果复制到客户端调用堆栈的返回结果中。当调用者在远程方法调用 read 执行完毕后重新获得控制权时,它唯一知道的是 read 返回值已经包含了所需的数据,但并不知道该 read 操作到底是在本地操作系统读取的文件数据,还是通过远程过程调用远端服务读取文件数据。

总结下RPC执行步骤:

  1. 调用客户端句柄,执行传递参数。

  2. 调用本地系统内核发送网络消息。

  3. 消息传递到远程主机,就是被调用的服务端。

  4. 服务端句柄得到消息并解析消息。

  5. 服务端执行被调用方法,并将执行完毕的结果返回给服务器句柄。

  6. 服务器句柄返回结果,并调用远程系统内核。

  7. 消息经过网络传递给客户端。

  8. 客户端接受数据。

在这里插入图片描述

RPC与HTTP2:

通俗解释:HTTP VS RPC (普通话 VS 方言)

HTTP 与 RPC 的关系就好比普通话与方言的关系。要进行跨企业服务调用时,往往都是通过 HTTP API,也就是普通话,虽然效率不高,但是通用,没有太多沟通的学习成本。但是在企业内部还是 RPC 更加高效,同一个企业公用一套方言进行高效率的交流,要比通用的 HTTP 协议来交流更加节省资源。整个中国有非常多的方言,正如有很多的企业内部服务各有自己的一套交互协议一样。虽然国家一直在提倡使用普通话交流,但是这么多年过去了,你回一趟家乡探个亲什么的就会发现身边的人还是流行说方言。


gRPC协议架构:

在这里插入图片描述
gRPC是一种用于实现RPC API的技术。由于gRPC是开源框架,通信双方都基于该框架进行二次开发,从而使得通信双方聚焦在业务,无需关注由gRPC软件框架实现的底层通信。


gRPC引入了三个新概念:Channel、RPC和Message。三者之间的关系很简单:每个Channel可能有许多RPC,而每个RPC可能有许多Message。

在这里插入图片描述
那么gRPC如何关联HTTP/2呢?

在这里插入图片描述
HTTP/2中的流在一个连接上允许多个并发会话,而gRPC的通过支持多个并发连接上的多个流扩展了这个概念。

Channel: 表示和终端的一个虚拟链接

Channel 背后实际上可能有多个HTTP/2 连接。从上面关系图来看,一个RPC和一个HTTP/2连接相关联,rpc实际上是纯HTTP/2流。Message与rpc关联,并以HTTP/2数据帧的形式发送。

在这里插入图片描述

消息是在数据帧之上分层的。一个数据帧可能有许多gRPC消息,或者如果一个gRPC消息非常大,它可能跨越多个数据帧。

服务分类:

一元RPC

客户端发送一个单独的请求到服务器,然后服务器在返回一个单独响应给客户端
在这里插入图片描述
protobuf定义方式:

rpc SayHello(HelloRequest) returns (HelloResponse);

服务器流式RPC

客户端向服务器发送请求,并获得一个流来读取一系列消息。客户端从返回的流中读取,直到没有更多的消息。gRPC保证单个RPC调用中的消息顺序
在这里插入图片描述
客户端请求一次,但是服务器通过流返回多个返回消息。

定义方式:

rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);

在返回的消息的前面使用 stream 关键字

客户端流式RPC

其中客户端写入一系列消息并将它们发送到服务器,再次使用提供的流。一旦客户端完成了消息的写入,它就会等待服务器读取消息并返回响应。
在这里插入图片描述
定义方式:

rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);

双向流RPC

双方使用读写流发送一系列消息。两个流独立运作,因此客户端和服务器可以读和写在他们喜欢的任何顺序:例如,服务器可以等待收到所有客户端消息之前写的反应,也可以交替阅读一条消息然后写一个消息,或其他一些读写的结合。每个流中的消息顺序被保留。
在这里插入图片描述
定义方式:

rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);

通信实战:

根据需求,我们这里以Python(服务端)与Java(客户端)为例:

安装插件:

Protobuf - IntelliJ IDEs Plugin | Marketplace (jetbrains.com)

img
设计 protocol buffer:

Protocol Buffers是什么?
Protocol Buffers官网:https://developers.google.com/protocol-buffers

定义一个msg.proto文件:

// @1 使用proto3语法
syntax = "proto3";
// @2 生成多个类(一个类方便管理)
option java_multiple_files = false;
// @3 生成java类所在包
option java_package = "com.example.spbclient.proto";
// @4 生成外层类类名
option java_outer_classname = "MsgProto";

// @6 .proto包名(逻辑包名)
package msg;

// @7 定义服务,用于描述要生成的API接口,类似于Java的业务逻辑接口类
service MsgService {
    // imgIdentify 方法名 ImgRequest 传入参数  ImgResponse 返回响应
    //注意:这里是returns 不是return
    rpc GetMsg (MsgRequest) returns (MsgResponse) {}
}
//定义请求数据结构
// string 数据类型
// calName 参数名称
// 1 序号、索引值(表示第一个参数,防止传参顺序错乱),一旦开始就不能改变
// int32 int
// int64 long
// 不可以使用 19000-19999 保留字
// TODO 大小写问题
message MsgRequest {
    string name = 1;
}

message MsgResponse {
    string msg = 1;
}

proto语法:

.proto TypeNotesC++ TypeJava TypePython Type[2]Go TypeRuby TypeC# TypePHP Type
doubledoubledoublefloatfloat64Floatdoublefloat
floatfloatfloatfloatfloat32Floatfloatfloat
int32使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代int32intintint32Fixnum 或者 Bignum(根据需要)intinteger
uint32使用变长编码uint32intint/longuint32Fixnum 或者 Bignum(根据需要)uintinteger
uint64使用变长编码uint64longint/longuint64Bignumulonginteger/string
sint32使用变长编码,这些编码在负值时比int32高效的多int32intintint32Fixnum 或者 Bignum(根据需要)intinteger
sint64使用变长编码,有符号的整型值。编码时比通常的int64高效。int64longint/longint64Bignumlonginteger/string
fixed32总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。uint32intintuint32Fixnum 或者 Bignum(根据需要)uintinteger
fixed64总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。uint64longint/longuint64Bignumulonginteger/string
sfixed32总是4个字节int32intintint32Fixnum 或者 Bignum(根据需要)intinteger
sfixed64总是8个字节int64longint/longint64Bignumlonginteger/string
boolboolbooleanboolboolTrueClass/FalseClassboolboolean
string一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。stringStringstr/unicodestringString (UTF-8)stringstring
bytes可能包含任意顺序的字节数据。stringByteStringstr[]byteString (ASCII-8BIT)ByteStringstring

Python端:

使用 pip安装:

pip install grpcio
pip install grpcio-tools googleapis-common-protos

gRPC由两个部分构成,grpciogRPC 工具, 后者是编译 protocol buffer 以及提供生成代码的插件。

生成接口代码:(记得进入msg.proto所在文件夹下)

python -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. msg.proto

新建msg_server.py

// RPC 必备的包,以及刚刚生成的俩文件
import grpc
import msg_pb2
import msg_pb2_grpc

// 并发
from concurrent import futures
import time

_ONE_DAY_IN_SECONDS = 60 * 60 * 24

// service 实现GetMsg方法
class MsgServicer(msg_pb2_grpc.MsgServiceServicer):

    def GetMsg(self, request, context):
        print("Received name: %s" % request.name)
        return msg_pb2.MsgResponse(msg='Hello, %s!' % request.name)


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    msg_pb2_grpc.add_MsgServiceServicer_to_server(MsgServicer(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    try:
        while True:
            time.sleep(_ONE_DAY_IN_SECONDS)
    except KeyboardInterrupt:
        server.stop(0)


if __name__ == '__main__':
    serve()

启动服务端:

python msg_server.py

Java端:

将msg.proto文件放到与Java同级的proto文件夹下:

在这里插入图片描述

导入依赖:

<!-- https://mvnrepository.com/artifact/net.devh/grpc-client-spring-boot-starter -->
<dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
            <version>2.13.1.RELEASE</version>
</dependency>

增加代码生成插件:

<build>
        <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.0.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.0.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

application.yml配置grpc

grpc:
  client:
    grpc-server:
      address: 'static://127.0.0.1:50051'
      negotiationType: plaintext
spring:
  application:
    name: spb-client
server:
  port: 8080

生成对应代码:
在这里插入图片描述
在这里插入图片描述

编写Controller层

package com.example.spbclient.controller;

import com.example.spbclient.proto.MsgProto;
import com.example.spbclient.proto.MsgServiceGrpc;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author xh
 * @Date 2022/10/14
 */
@RestController
public class ApiController {
    @GrpcClient("grpc-server")
    private MsgServiceGrpc.MsgServiceBlockingStub msgStub;

    @GetMapping("/test")
    public String testImgCal() {
        MsgProto.MsgRequest msgRequest = MsgProto.MsgRequest.newBuilder()
                .setName("图片信息")
                .build();
        MsgProto.MsgResponse msgResponse = msgStub.getMsg(msgRequest);
        return msgResponse.getMsg();
    }

}

客户端测试:

Java端:
在这里插入图片描述
Python端:
在这里插入图片描述

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
云原生 OpenStack 是一种将云计算和开源项目 OpenStack 结合起来的理念和实践。它旨在将 OpenStack 能力与云原生技术相结合,以提供更加灵活、可扩展和高效的云计算解决方案。 云原生是一种设计和构建应用程序的方法,旨在充分利用云计算的优势,如弹性扩展、容器化和自动化管理。云原生应用程序通常被设计为微服务架构,拆分为多个小型、独立且可独立部署的组件。这些组件使用容器技术进行封装和管理,以提供更高的可移植性和弹性。 云原生 OpenStack 结合了 OpenStack 的功能和云原生的设计原则和技术。它提供了一种以容器为基础的轻量级虚拟化解决方案,使应用程序更易于部署、维护和扩展。通过使用容器技术,云原生 OpenStack 可以快速部署和启动新的服务实例,并根据负载情况自动进行水平扩展。同时,它还可以根据需要对网络和存储资源进行动态配置和管理。 云原生 OpenStack 还使用 DevOps 开发和运维模式,通过自动化工具和流程来加速应用程序的开发、测试和部署。这种自动化可以提高整个开发和运维过程的效率,并减少人为错误的发生。此外,云原生 OpenStack 还支持持续集成和持续交付,使开发人员能够快速交付新功能和修复程序漏洞。 总之,云原生 OpenStack结合了云计算和开源项目 OpenStack 的优势,提供了高度灵活和可扩展的云计算解决方案。它适用于各种规模和类型的应用程序,可以提供高性能、高可用性和灵活的资源管理能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值