Asp-Net-Core学习笔记:gRPC快速入门
前言
此前,我在做跨语言调用时,用的是 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 进行跨语言调用。
概念科普
什么是RPC?
远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。 比如 Java RMI(远程方法调用(Remote
Method Invocation)。能够让在某个java虚拟机上的对象像调用本地对象一样调用另一个java虚拟机中的对象上的方法)。

从上图可以看出, RPC 本身是 client-server模型,也是一种 request-response 协议。
有些实现扩展了远程调用的模型,实现了双向的服务调用,但是不管怎样,调用过程还是由一个客户端发起,服务器端提供响应,基本模型没有变化。
服务的调用过程为:
- client调用client stub,这是一次本地过程调用
- client stub将参数打包成一个消息,然后发送这个消息。打包过程也叫做 marshalling
- client所在的系统将消息发送给server
- server的的系统将收到的包传给server stub
- server stub解包得到参数。 解包也被称作 unmarshalling
- 最后server stub调用服务过程. 返回结果按照相反的步骤传给client
关于 gRPC
gRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架。
前文有说过,这个是 Google 开发的,以下是 AspNetCore 的文档中,对 gRPC 的介绍。
gRPC 的主要优点是:
- 现代高性能轻量级 RPC 框架。
- 协定优先 API 开发,默认使用协议缓冲区,允许与语言无关的实现。
- 可用于多种语言的工具,以生成强类型服务器和客户端。
- 支持客户端、服务器和双向流式处理调用。
- 使用 Protobuf 二进制序列化减少对网络的使用。
这些优点使 gRPC 适用于:
- 效率至关重要的轻量级微服务。
- 需要多种语言用于开发的 Polyglot 系统。
- 需要处理流式处理请求或响应的点对点实时服务。
gRPC 使用 .proto 文件来定义接口和数据类型,同时提供了通过 .proto 文件生成不同语言调用代码的工具。
项目介绍
本文的项目是使用 C# 调用 Python 写的服务,这个服务是一个大语言模型。
在本项目中,C# 项目作为客户端,Python 项目作为服务端。
编写 .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; 这里赋值的意思是这个参数的位置,不是定义变量的赋值,第一次用差点整蒙了。
服务端 Python 项目
先在 Python 项目里,把 gRPC 服务器搭建起来。
把上面的 chat.proto 文件,复制到 Python 项目的目录里面。
Python 项目需要安装依赖
pip install grpcio
pip install grpcio-tools
执行命令生成代码
python -m grpc_tools.protoc -I . --python_out=. --pyi_out=. --grpc_python_out=. ./chat.proto
上面的命令,可以根据 proto 文件,生成以下代码
- chat_pb2_grpc.py
- chat_pb2.py
- chat_pb2.pyi
编写服务器代码
from concurrent import futures
import logging
import grpc
import chat_pb2
import chat_pb2_grpc
class Greeter(chat_pb2_grpc.ChatHubServicer):
def Chat(self, request: chat_pb2.ChatRequest, context):
response, history = model.chat(
tokenizer,
request.prompt,
history=[],
max_length=request.max_length,
top_p=request.top_p,
temperature=request.temperature)
return chat_pb2.ChatReply(response=response)
def StreamingChat(self, request: chat_pb2.ChatRequest, context):
past_key_values, history = None, []
current_length = 0
for response, history, past_key_values in model.stream_chat(
tokenizer, request.prompt, history=history,
past_key_values=past_key_values,
return_past_key_values=True):
print(response[current_length:], end="", flush=True)
yield chat_pb2.ChatReply(response=response)
current_length = len(response)
def serve():
port = '50051'
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
chat_pb2_grpc.add_ChatHubServicer_to_server(Greeter(), server)
server.add_insecure_port('[::]:' + port)
server.start()
print("Server started, listening on " + port)
server.wait_for_termination()
if __name__ == '__main__':
logging.basicConfig()
serve()
前面在 proto 里定义的俩方法:Chat, StreamingChat 要自己实现,这里我直接调用 Transformer ,一元方法就直接返回,流式输出使用 yield 生成结果。
扩展:Python gRPC客户端
本文把 Python 项目作为 gRPC 的服务端供 AspNetCore 项目调用
反过来也是没问题的
Python 作为客户端的代码如下
import logging
import grpc
import chat_pb2
import chat_pb2_grpc
def run():
print("Will try to greet world ...")
with grpc.insecure_channel('localhost:50051') as channel:
stub = chat_pb2_grpc.ChatHubStub(channel)
response = stub.Chat(chat_pb2.ChatRequest(prompt='你好'))
print("Greeter client received: " + response.message)
if __name__ == '__main__':
logging.basicConfig()
run()
客户端 AspNetCore 项目
AspNetCore 与 gRPC 的集成非常容易
先把上面的 proto 文件添加到项目中
在本项目中我放到 Protos/chat.proto 中
先安装 nuget 包
dotnet add Grpc.AspNetCore
然后编辑 .csproj 添加以下配置
<ItemGroup>
<Protobuf Include="Protos\chat.proto" GrpcServices="Both" />
</ItemGroup>
其中 GrpcServices="Both" 表示同时生成服务端和客户端代码
完成之后,Build 项目,然后就会在 obj\Debug\net6.0\Protos 目录下生成对应的代码
不过这些代码不需要直接引用,编译器会自动处理。
客户端
编辑 Program.cs 文件
注册一个 gRPC 客户端
builder.Services.AddGrpcClient<ChatHub.ChatHubClient>(options => {
options.Address = new Uri(builder.Configuration.GetValue<string>("gRPCServer"));
});
如果要注册多个相同的客户端,可以加个名字区分
builder.Services.AddGrpcClient<ChatHub.ChatHubClient>("client1", options => {
options.Address = new Uri(builder.Configuration.GetValue<string>("gRPCServer"));
});
builder.Services.AddGrpcClient<ChatHub.ChatHubClient>("client2", options => {
options.Address = new Uri(builder.Configuration.GetValue<string>("gRPCServer"));
});
使用的时候直接注入就完事了。
如果是命名客户端,就先注入 GrpcClientFactory 对象
var clinet1 = grpcClientFactory.CreateClient<ChatHub.ChatHubClient>("client1");
var clinet2 = grpcClientFactory.CreateClient<ChatHub.ChatHubClient>("client2");
发起一个请求(一元模式)
var request = new ChatRequest { Prompt = "哈喽" };
ChatReply reply = ChatClient.Chat(request);
读取流式返回数据
using (var call = ChatClient.StreamingChat(request)) {
await foreach (var resp in call.ResponseStream.ReadAllAsync()) {
Console.Write(resp.Response);
}
}
简简单单,搞定~
服务端
同样很简单
注册服务、配置中间件
builder.Services.AddGrpc();
app.MapGrpcService<ChatService>();
ChatService 代码
public class ChatService : ChatHub.ChatHubBase {
private readonly ILogger<ChatService> _logger;
public ChatService(ILogger<ChatService> logger) {
_logger = logger;
}
public override Task<ChatReply> Chat(ChatRequest request, ServerCallContext context) {
return new Task<ChatReply>(() => new ChatReply { Response = $"你好,{request.Prompt}" });
}
public override async Task StreamingChat(ChatRequest request, IServerStreamWriter<ChatReply> responseStream,
ServerCallContext context) {
for (var i = 0; i < 5; i++) {
await responseStream.WriteAsync(new ChatReply { Response = $"{i}" });
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
}
同样是把 protobuf 里定义的俩方法实现了一遍。
代码比较易懂,跟上面的 Python 版差不多,这里就不重复解释了。
小结
gRPC 使用起来非常的丝滑,目前来说也没遇到什么坑,可以非常平滑地与已有项目集成,如果有类似的场景,强烈推荐尝试一下 gRPC !
gRPC 的功能很多,本文仅介绍了最基本的使用,更多的请阅读文档,详细有了本文的基础铺垫,读者再阅读文档深入使用时,会比较轻松上手。
PS:最近这个使用到 gRPC 的项目,接下来我会写个小结博客介绍一下~
参考资料
- Thrift 简单介绍 - https://www.jianshu.com/p/8f25d057a5a9
- https://grpc.io/docs/languages/python/quickstart/
- https://learn.microsoft.com/zh-cn/aspnet/core/grpc/
Asp-Net-Core学习笔记:gRPC快速入门的更多相关文章
- Asp.Net Core学习笔记:入门篇
Asp.Net Core 学习 基于.Net Core 2.2版本的学习笔记. 常识 像Django那样自动检查代码更新,自动重载服务器(太方便了) dotnet watch run 托管设置 设置项 ...
- ASP.NET Core 学习笔记 第一篇 ASP.NET Core初探
前言 因为工作原因博客断断续续更新,其实在很早以前就有想法做一套关于ASP.NET CORE整体学习度路线,整体来说国内的环境的.NET生态环境还是相对比较严峻的,但是干一行爱一行,还是希望更多人加入 ...
- Asp.net Core学习笔记
之前记在github上的,现在搬运过来 变化还是很大的,感觉和Nodejs有点类似,比如中间件的使用 ,努力学习ing... 优点 不依赖IIS 开源和跨平台 中间件支持 性能优化 无所不在的依赖注入 ...
- ASP.NET Core 学习笔记 第三篇 依赖注入框架的使用
前言 首先感谢小可爱门的支持,写了这个系列的第二篇后,得到了好多人的鼓励,也更加坚定我把这个系列写完的决心,也能更好的督促自己的学习,分享自己的学习成果.还记得上篇文章中最后提及到,假如服务越来越多怎 ...
- ASP.NET Core 学习笔记 第四篇 ASP.NET Core 中的配置
前言 说道配置文件,基本大多数软件为了扩展性.灵活性都会涉及到配置文件,比如之前常见的app.config和web.config.然后再说.NET Core,很多都发生了变化.总体的来说技术在进步,新 ...
- ASP.NET Core 学习笔记 第五篇 ASP.NET Core 中的选项
前言 还记得上一篇文章中所说的配置吗?本篇文章算是上一篇的延续吧.在 .NET Core 中读取配置文件大多数会为配置选项绑定一个POCO(Plain Old CLR Object)对象,并通过依赖注 ...
- asp.net.core学习笔记1:swagger的使用和webapi接收Jobject对象
环境:asp.net.core 3.1 (一觉醒来官方已经不推荐3.0了,于是没有任何core经验,也只能开始了3.1的开发学习) 由于现有项目前后端分离.微服务化日趋流行,所以上手不采用web应用( ...
- Asp.net core 学习笔记 ( Data protection )
参考 : http://www.cnblogs.com/xishuai/p/aspnet-5-identity-part-one.html http://cnblogs.com/xishuai/p/a ...
- Asp.net core 学习笔记 SignalR
refer : https://kimsereyblog.blogspot.com/2018/07/signalr-with-asp-net-core.html https://github.com/ ...
- Asp.net core (学习笔记 路由和语言 route & language)
https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-2.1 https://doc ...
随机推荐
- ASP.NET Core - 缓存之内存缓存(下)
话接上篇 [ASP.NET Core - 缓存之内存缓存(上)],所以这里的目录从 2.4 开始. 2.4 MemoryCacheEntryOptions MemoryCacheEntryOption ...
- opengauss配置远程白名单
DB_VERSION:openGauss 3.0.3 1.允许192.168网段用户使用jack用户登陆 --创建只读账号 CREATE USER jack WITH MONADMIN passwor ...
- 最新升级优化 shopee|美客多 Mercadolibre|shopfiy|lazada|独立货代贴单系统 可规模化的贴单打单系统 源码下载独立部署
七想网络 跨境猴 最新优化改进版本的 虾皮代打包-虾皮代贴单 独立部署源码版本货代贴单系统 介绍: 台湾海外仓_shopee货代_虾皮物流–虾皮代贴单 虾皮代打包-虾皮代贴单-虾皮货代平台 shope ...
- Django笔记二十三之case、when操作条件表达式搜索、更新等操作
本文首发于公众号:Hunter后端 原文链接:Django笔记二十三之条件表达式搜索.更新等操作 这一篇笔记将介绍条件表达式,就是如何在 model 的使用中根据不同的条件筛选数据返回. 这个操作类似 ...
- MySQL MHA信息的收集【Filebeat+logstash+MySQL】
一.项目背景 随着集团MHA集群的日渐增长,MHA管理平台话越来越迫切.而MHA平台的建设第一步就是将这些成百上千套的MHA集群信息收集起来,便于查询和管理. MHA主要信息如下: (1)基础配置信息 ...
- 04-webpack初体验
/** * index.js: webpack入口起点文件 * * 1.运行指令: * 开发环境:webpack ./src/index.js -o ./build --mode=developmen ...
- xcode历史版本下载
一.背景 较早之前做过一个项目,当时使用swift 3.x开发. 项目结束后就没再有新需求与更新. 但最近呢需要对项目的某些功能进行调整,项目又重新被拾了起来. 我们知道现在的swift 版本已经到了 ...
- 如何通过C#/VB.NET 代码调整PDF文档的页边距
PDF边距是页面主要内容区域和页面边缘之间的距离.与Word页边距不同,PDF文档的页边距很难更改.因为Adobe没有提供操作页边距的直接方法.但是,您可以通过缩放页面内容来改变页边距.本文将介绍如何 ...
- 极速进化,光速转录,C++版本人工智能实时语音转文字(字幕/语音识别)Whisper.cpp实践
业界良心OpenAI开源的Whisper模型是开源语音转文字领域的执牛耳者,白璧微瑕之处在于无法通过苹果M芯片优化转录效率,Whisper.cpp 则是 Whisper 模型的 C/C++ 移植版本, ...
- 【Visual Leak Detector】在 VS 高版本中使用 VLD
说明 使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记. 本篇介绍如何在 VS 高版本中使用 vld2.5.1.同系列文章目录可见 <内存泄漏检测工具>目录 目录 说明 1. 使用前 ...