Asp-Net-Core学习笔记:gRPC快速入门

1前言

此前,我在做跨语言调用时,用的是 Facebook 的 Thrift,挺轻量的,还不错。

Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个远程过程调用(RPC)框架来使用,是由Facebook为“大规模跨语言服务开发”而开发的。它通过一个代码生成引擎联合了一个软件栈,来创建不同程度的、无缝的跨平台高效服务,可以使用C#、C++(基于POSIX兼容系统)Cappuccino、Cocoa、Delphi、Erlang、Go、Haskell、Java、Node.js、OCaml、Perl、PHP、Python、Ruby和Smalltalk编程语言开发。 2007由Facebook开源,2008年5月进入Apache孵化器, 2010年10月成为Apache的顶级项目。

最近的项目中也有类似的需求,这次打算使用 Google 的 gRPC,原因是 gRPC 知名度也很高,之前一直想用但没有场景,加上 AspNetCore 的文档里有介绍,看起来官方也是推荐使用这种方式进行 RPC 调用。.

所以就果断开始了 gRPC 实践,最终做出来的效果还是可以的(这是后话)。

本文主要介绍 C# 与 Python 之间,使用 gRPC 进行跨语言调用。

2概念科普

什么是RPC?

远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。 比如 Java RMI(远程方法调用(Remote Method Invocation)。能够让在某个java虚拟机上的对象像调用本地对象一样调用另一个java虚拟机中的对象上的方法)。

Asp-Net-Core学习笔记:gRPC快速入门

从上图可以看出, RPC 本身是 client-server模型,也是一种 request-response 协议。 有些实现扩展了远程调用的模型,实现了双向的服务调用,但是不管怎样,调用过程还是由一个客户端发起,服务器端提供响应,基本模型没有变化。 服务的调用过程为:

  1. client调用client stub,这是一次本地过程调用
  2. client stub将参数打包成一个消息,然后发送这个消息。打包过程也叫做 marshalling
  3. client所在的系统将消息发送给server
  4. server的的系统将收到的包传给server stub
  5. server stub解包得到参数。 解包也被称作 unmarshalling
  6. 最后server stub调用服务过程. 返回结果按照相反的步骤传给client

关于 gRPC

gRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架。

前文有说过,这个是 Google 开发的,以下是 AspNetCore 的文档中,对 gRPC 的介绍。

gRPC 的主要优点是:

  • 现代高性能轻量级 RPC 框架。
  • 协定优先 API 开发,默认使用协议缓冲区,允许与语言无关的实现。
  • 可用于多种语言的工具,以生成强类型服务器和客户端。
  • 支持客户端、服务器和双向流式处理调用。
  • 使用 Protobuf 二进制序列化减少对网络的使用。

这些优点使 gRPC 适用于:

  • 效率至关重要的轻量级微服务。
  • 需要多种语言用于开发的 Polyglot 系统。
  • 需要处理流式处理请求或响应的点对点实时服务。

gRPC 使用 .proto 文件来定义接口和数据类型,同时提供了通过 .proto 文件生成不同语言调用代码的工具。

3项目介绍

本文的项目是使用 C# 调用 Python 写的服务,这个服务是一个大语言模型。

在本项目中,C# 项目作为客户端,Python 项目作为服务端。

4编写 .proto 文件

第一步,要先编写用于定义接口和数据结构的 .proto 文件。

将下面代码保存到 chat.proto 文件里,接下来在要接入 gRPC 的每个项目里,都要复制一份这个文件。

syntax = "proto3";

import "google/protobuf/wrappers.proto";

option csharp_namespace = "AIHub.Blazor";

package aihub;

service ChatHub {
  rpc Chat (ChatRequest) returns (ChatReply);
  rpc StreamingChat (ChatRequest) returns (stream ChatReply);
}

message ChatRequest {
  string prompt = 1;
  repeated google.protobuf.StringValue history = 2;
  int32 max_length = 3;
  float top_p = 4;
  float temperature = 5;
}

message ChatReply {
  string response = 1;
}

这里定义了两个 Chat 方法,其中一个是一元调用,一个是流式输出。

gRPC 服务可以有不同类型的方法,服务发送和接收消息的方式取决于所定义的方法的类型

  • 一元 - 调用完就返回,同步方法
  • 服务器流式处理 - 服务端流式返回数据
  • 客户端流式处理 - 客户端流式写入数据
  • 双向流式处理

都挺好理解的,在 proto 定义里面也很易懂,stream 放在哪里,哪里就是流式。

stream 放在请求参数,那客户端输入就是流式的,放在返回值前面,那服务端的返回就是流式。

都放就代表双向流式。

接着又定义了用到的数据结构,一个输入参数 ChatRequest,一个返回参数 ChatReply

string prompt = 1; 这里赋值的意思是这个参数的位置,不是定义变量的赋值