NET Core使用Grpc通信(一):一元
gRPC是一个现代的开源高性能远程过程调用(RPC)框架,它可以高效地连接数据中心内和跨数据中心的服务,支持负载平衡、跟踪、运行状况检查和身份验证。
gRPC通过使用 Protocol Buffers 作为数据传输格式,实现了在不同平台上的通信,并支持双向流和流式传输。RPC 是远程过程调用的缩写,实现跨服务器调用。在开发中,规定调用规则、网络传输协议以及数据序列化反序列化规范是确保前后端通信规范性的关键。
了解GRpc前需要了解Rpc概念。
什么是 RPC
RPC 是 Remote Procedure Call 的简称,中文叫远程过程调用。
说的白话一点,可以这么理解:比如有两台服务器A和B,A服务器上的应用想调用B服务器上的另一个应用提供的方法,但由于不在同一个内存空间,无法直接调用,所以需要通过网络来实现调用效果。
其实大家在平时开发中有接触过,例如:前端去请求后端的接口。我们来想一下前后端要制定什么规则,才能进行接口请求:
- 调用的语义,也可以理解为接口规范。(比如 RESTful )
- 网络传输协议 (比如 HTTP )
- 数据序列化反序列化规范(比如 JSON )
只有制定了这些规则,才能保证前后端通信的规范性
交互图
从上图中可以看出,RPC 是一种客户端-服务端(Client/Server)模式。从某种角度来看,所有本身应用程序之外的调用都可以归类为 RPC。无论是微服务、第三方 HTTP 接口,还是读写数据库中间件 Mysql、Redis。
RPC 特点
- RPC 是一种协议。RPC实现包括:Dubbo、Thrift、Grpc、Netty等。
- 网络协议和网络 IO 模型对其透明。RPC 的客户端认为自己是在调用本地对象,因此其对使用的网络协议( HTTP 协议等)以及网络 IO 模型,是不关心的。
- 信息格式对其透明。调用方法是需要传递参数的,对于远程调用来说,传递过程中参数的信息格式是怎样构成,以及提供者如何使用这些参数,都是不用关心的。
- 有跨语言能力。因为调用方实际上也不清楚远程服务器的应用程序是使用什么语言运行的。那么对于调用方来说,无论服务器方使用的是什么语言,本次调用都应该成功,并且返回值也应该按照调用方程序语言所能理解的形式进行描述。
RPC 与 HTTP 的对比
其实 RPC 跟 HTTP 不是一个层级的东西,RPC 应该是跟 HTTP + RestFul 进行对比。
深入了解:HTTP 与 RPC 接口区别
传输协议
RPC 可以基于 HTTP 或者 TCP 进行传输,而 HTTP 只能基于 HTTP
传输效率
RPC 包含了 HTTP2 的优点,所以他的传输效率比 HTTP1 更高~
性能消耗
RPC 包含 HTTP2 的优点,比如二进制传输、头部压缩等,所以性能消耗自然比 HTTP1 低~
负载均衡
RPC 基本都自带负载均衡策略,而 HTTP 需要配置 Nginx/HAProxy 来完成
服务治理
RPC 能做到自动通知,不影响上游,而 HTTP 需要事先通知,修改 Nginx/HAProxy 配置
gRPC 和 RPC 的关系
Grpc 是由谷歌开源的一种 RPC 框架,设计之初便是为了解决谷歌内部的 RPC 使用场景所遇到的问题。因此你可以说 gRPC 就是一种 RPC 框架类型。具体来说:
- RPC是一种编程范式,定义了客户端像调用本地函数一样调用远程函数的方式。
- gRPC 是 Google 基于 HTTP/2 和 Protocol Buffers 实现的 RPC 框架。
- gRPC 支持双向流、流控、头压缩等,性能优异。
所以 gRPC 是 RPC 模式的一种高效实现,提供了语言中立、高性能、安全的 RPC 服务框架,使得RPC服务调用更加高效、简单、通用。它是 RPC 模式的一种优秀代表。
gRPC 的优势有哪些?
gRPC 是基于 HTTP/2 设计的~所以 gRPC 的优点自然也包含了 HTTP/2 的优点:
- 数据传输二进制分帧
- 多路复用
- 服务端推送
- 头部压缩
gRPC的主要优势及其简要描述:
优势
|
描述
|
高性能
|
利用HTTP/2提供高效的网络传输,支持双向流、头部压缩、多路复用。
|
跨语言支持
|
支持多种编程语言间的无缝通信和集成。
|
自动化生成代码
|
使用Protobuf定义服务,自动生成客户端和服务器代码。
|
错误处理
|
定义丰富的错误码和状态码,便于异常处理和调试。
|
通信模式多样
|
支持多种RPC通信模型,如一对一、服务端流、客户端流、双向流等。
|
可扩展性
|
拦截器和插件机制允许功能的扩展和定制。
|
社区和生态系统
|
拥有活跃的社区支持和丰富的相关工具及库。
|
gRPC 是怎么传输的?
从上图的 gRPC 传输模型可以看出,客户端 Stub 从 gRPC Core 库发起请求,序列化成 Protobuf 消息格式,然后传输至服务端。
服务端 Stub 接收客户端请求,处理请求中的 Protobuf 数据并进行反序列化,然后将请求对象传入服务器并实现业务逻辑处理。最终再将响应序列化后返回给客户端,从而形成一次完整的接口调用过程。
以上概念以及相关知识点来自apifox。
NET Core实现Grpc调用
话不多说,以下内容详细介绍一元调用的过程,贴代码。
-----------------服务端代码 Start-----------------
步骤一:
在Grpc服务端(Server)先创建一个.potos文件。文件名(IBook_Service.proto)文件路径(Protos/IBook_Service.proto)
//表明使用protobuf的编译器版本为v2,目前最新的版本为v3。
syntax = "proto3";
//定义命名空间
option csharp_namespace = "ZP_BookService_Grpc.Application.Book_Service";
//包名:多个 .proto 文件生成代码时,可避免命名冲突。
package Book_Service;
//1、定义接口名称,用于后期实现
service IBook_Service{
// 1.1 根据商品主键,获取商品
rpc GetBook (BookFrom) returns (BookDto);
}
// 2、定义入参(类)Form:顺序要写,且不能重复
message BookFrom{
string BookName = 1;
}
// 3、定义出参Dto(类):顺序要写,且不能重复
message BookDto{
string ID = 1;
string CreateTime = 2;
string BookName = 3;
string BookPrice =4;
string PublicationDate = 5;
string Type = 6;
string Publisher = 7;
int32 Residue = 8;
}
步骤二:
文件建立好后需要在项目的Project内加上文件的目录(Protos\IBook_Service.prot) GrpcServices意思是服务端,一定要加上<Protobuf Include="Protos\IBook_Service.proto" GrpcServices="Server" />
引用包(Google.Protobuf、Grpc.AspNetCore、Grpc.Tools)
完成以上步骤,右键项目选择 “重新生成”。生成以后可以在项目的obj文件夹(~\Application_Grpc\obj\Debug\net7.0\Protos)看到一个自动生成的Protos文件夹
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.26.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.61.0" />
<PackageReference Include="Grpc.Tools" Version="2.62.0" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\IBook_Service.proto" GrpcServices="Server" />
</ItemGroup>
步骤三:
创建一个实现类(Book_Service)用于实现protos文件夹内自动生成的接口(IBook_Service.IBook_ServiceBase)。注:实现带有Base的接口。接口名称是定义proto文件的时候自定义的。
namespace Application_Grpc.Application.BusinessServices
{
//注入作用域生命周期
[Service(ServiceLifetime.Scoped)]
public class Book_Service : IBook_Service.IBook_ServiceBase
{
private IBook_Repository _bookRepository { get; }
private IMapper _mapper { get; }
public Book_Service(IBook_Repository book_Repository, IMapper mapper)
{
_bookRepository = book_Repository;
_mapper = mapper;
}
public override async Task<BookDto> GetBook(BookFrom request, ServerCallContext context)
{
BookDto data=new BookDto();
data.ID = Guid.NewGuid().ToString();
data.CreateTime = DateTime.Now.ToString();
data.BookName = request.BookName;
data.BookPrice = "29.99";
data.PublicationDate = "1999-03-21";
data.Type = "经典";
data.Publisher = "清华大学出版社";
data.Residue = 5;
return data;
}
}
}
至此,服务端的Grpc就完成了。剩下的就是把项目进行服务依赖注入操作,本实例代码通过贴特征方式注入。可以在 Program.cs 以常规方式注入自己的服务。
如:builder.Services.AddSingleton<Book_Service>();
-----------------服务端代码 END-----------------
-----------------客户端代码 Start-----------------
步骤一:
步骤一:
在Grpc服务端(Client)先创建一个.potos文件。文件名(IBook_Service.proto)文件路径(Protos/IBook_Service.proto)。其实就是复制服务端的 proto 修改下 命名空间
//表明使用protobuf的编译器版本为v2,目前最新的版本为v3。
syntax = "proto3";
//定义命名空间
option csharp_namespace = "ZP_ProjectEntrance.MicroService.Book_Service";
//包名:多个 .proto 文件生成代码时,可避免命名冲突。
package Book_Service;
//1、定义接口名称,用于后期实现
service IBook_Service{
// 1.1 根据商品主键,获取商品
rpc GetBook (BookFrom) returns (BookDto);
}
// 2、定义入参(类)Form:顺序要写,且不能重复
message BookFrom{
string BookName = 1;
}
// 3、定义出参Dto(类):顺序要写,且不能重复
message BookDto{
string ID = 1;
string CreateTime = 2;
string BookName = 3;
string BookPrice =4;
string PublicationDate = 5;
string Type = 6;
string Publisher = 7;
int32 Residue = 8;
}
步骤二:
文件建立好后需要在项目的加上文件的目录(Protos\IBook_Service.prot) GrpcServices意思是客户端(Client),一定要加上 <Protobuf Include="Protos\IBook_Service.proto" GrpcServices="Client" />
引用包(Google.Protobuf、Grpc.AspNetCore、Grpc.Tools)
完成以上步骤,右键项目选择 “重新生成”。生成以后可以在项目的obj文件夹(~\ZP_ProjectEntrance\obj\Debug\net7.0\Protos)看到一个自动生成的Protos文件夹
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.26.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.61.0" />
<PackageReference Include="Grpc.Tools" Version="2.62.0" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\IBook_Service.proto" GrpcServices="Client" />
</ItemGroup>
步骤三:重点、重点、重点~
创建一个客户端类(ClientHelper)用于对接到Grpc的服务端,通过Func型委托形式构建,做成公共的请求服务端入口。
public static class GrpcClientHelper
{
/// <summary>
/// 一元rpc调用
/// </summary>
/// <typeparam name="TClient">客户端类型</typeparam>
/// <typeparam name="TRequest">请求类型 </typeparam>
/// <typeparam name="TResponse">服务端响应返回类型</typeparam>
/// <typeparam name="TResult">方法返回类型 </typeparam>
/// <param name="serverAddress">请求服务端地址</param>
/// <param name="callFunc">异步调用 gRPC方法的委托 </param>
/// <param name="request">封装请求对象(数据)</param>
/// <param name="clientFactory">创建 gRPC 客户端的委托工厂方法 </param>
/// <returns></returns>
public static async Task<TResult> CallGrpcServiceAsync<TClient, TRequest, TResponse, TResult>(
string serverAddress, Func<TClient, TRequest, Task<TResponse>> callFunc, TRequest request, Func<GrpcChannel, TClient> clientFactory)
where TClient : class
where TRequest : class
where TResponse : class
{
using var channel = GrpcChannel.ForAddress(serverAddress);
var client = clientFactory(channel);
try
{
var response = await callFunc(client, request);
// 这里添加转换逻辑,如果 TResponse 不是 TResult,强制类型转换,需要确保类型兼容
return (TResult)(object)response;
}
catch (RpcException)
{
// 处理异常
throw;
}
}
/// <summary>
/// 获取某个字段的值
/// </summary>
/// <typeparam name="Source"></typeparam>
/// <param name="source"></param>
/// <param name="field"></param>
/// <returns></returns>
public static object GetFieldValue<Source>(Source source, string field)
{
var fieldProperty = source.GetType().GetProperty(field);
if (fieldProperty != null)
return fieldProperty.GetValue(source);
else
return null;
}
}
步骤四:
通过Controllers定义的方法,以请求方法形式进行调用到GrpcClientHelper
namespace ZP_ProjectEntrance.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class BooKController : ControllerBase
{
private readonly ILogger<BooKController> _logger;
public BooKController(ILogger<BooKController> logger)
{
_logger = logger;
}
/// <summary>
/// 获取书籍信息
/// </summary>
/// <returns></returns>
[HttpGet(Name = "GetBook")]
public async Task<BookDto> GetBookAsync()
{
// 服务端地址,可以扩展为请求分布式集群
string serverAddress = "http://localhost:5031";
// 使用 GrpcClientHelper 来调用 gRPC 服务 ,
BookDto bookDto = await GrpcClientHelper.CallGrpcServiceAsync<IBook_Service.IBook_ServiceClient, BookFrom, BookDto, BookDto>(
serverAddress,
async (client, request) => await client.GetBookAsync(request), // 异步调用 gRPC 方法的委托
new BookFrom { BookName = "三国演义" }, // 封装请求对象(值)
(channel) => new IBook_Service.IBook_ServiceClient(channel) // 实现 gRPC 客户端的委托方法
);
_logger.LogInformation(JsonConvert.SerializeObject(bookDto));
return bookDto;
}
}
}
至此,客户端的Grpc就完成了。
-----------------客户端代码 END-----------------
项目的结构
附:贴特征形式实现依赖注入的代码,通过反射机制实现。
注:业务层和仓储层必须是独立层项目,或相同形式进行隔离(要么统一接口I_BLL、I_DLL,要么单纯的BLL、DLL),否则自行对自动注册类进行改造。
代码可以写在公共层 ExternalService 项目中,作为基础服务进行引用。
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
namespace ExternalService.RegisterServices
{
/// <summary>
/// 通过反射机制自动化进行依赖注入服务
/// </summary>
public static class ServiceCollectionExtension
{
/// <summary>
/// 注册接口类型服务,继承接口(贴特征)
/// </summary>
/// <param name="services">this服务</param>
/// <param name="assembly">程序集</param>
/// <returns></returns>
public static IServiceCollection RegisterIntfaceTypeService(this IServiceCollection services, Assembly assembly)
{
var interfaces = assembly.GetTypes().Where(t => t.IsInterface).ToList();
var types = assembly.GetTypes().Where(t => t.IsClass).ToList();
foreach (var interf in interfaces)
{
if (interf == null)
continue;
var type = types.FirstOrDefault(interf.IsAssignableFrom);
if (type == null)
continue;
var liftTime = ServiceLifetime.Scoped;
var attr = type.GetCustomAttribute<ServiceAttribute>();
if (attr != null)
liftTime = attr.LifeTime;
else
continue;
switch (liftTime)
{
default:
case ServiceLifetime.Scoped:
{
//作用生命周期:同一请求之间状态共享,跟随HTTP请求生命周期
services.AddScoped(interf, type);
break;
}
case ServiceLifetime.Transient:
{
//瞬时生命周期:无状态化,每次使用是 new ()
services.AddTransient(interf, type);
break;
}
case ServiceLifetime.Singleton:
{
//单例生命周期:整个程序所有请求状态共享,整个程序只有一个实例
services.AddSingleton(interf, type);
break;
}
}
}
return services;
}
/// <summary>
/// 注册普通类服务,非接口类型(贴特征)
/// </summary>
/// <param name="services">this服务</param>
/// <param name="assembly">程序集</param>
/// <param name="NamespaceKeyWord">命名空间关键字</param>
/// <returns></returns>
public static IServiceCollection RegisterClassService(this IServiceCollection services, Assembly assembly, string NamespaceKeyWord = "")
{
var ClassTypes = assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract).ToList();
if (!string.IsNullOrEmpty(NamespaceKeyWord))
ClassTypes = assembly.GetTypes().Where(t => t.Name.Contains(NamespaceKeyWord)).ToList();
foreach (var types in ClassTypes)
{
if (types == null)
continue;
var liftTime = ServiceLifetime.Scoped;
var attr = types.GetCustomAttribute<ServiceAttribute>();
if (attr != null)
liftTime = attr.LifeTime;
else
continue;
switch (liftTime)
{
default:
case ServiceLifetime.Scoped:
{
//作用生命周期:同一请求之间状态共享,跟随HTTP请求生命周期
services.AddScoped(types);
break;
}
case ServiceLifetime.Transient:
{
//瞬时生命周期:无状态化,每次使用是 new ()
services.AddTransient(types);
break;
}
case ServiceLifetime.Singleton:
{
//单例生命周期:整个程序所有请求状态共享,整个程序只有一个实例
services.AddSingleton(types);
break;
}
}
}
return services;
}
}
/// <summary>
/// 生命周期特征
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ServiceAttribute : Attribute
{
internal ServiceLifetime LifeTime { get; set; }
public ServiceAttribute(ServiceLifetime lifeTime) => LifeTime = lifeTime;
}
}
在使用的项目程序集内创建一个扩展服务 AddApplication_Register。然后调用注册类扩展服务services.RegisterClassService(Assembly.GetExecutingAssembly());
using System.Reflection;
using ExternalService.RegisterServices;
using Microsoft.Extensions.DependencyInjection;
namespace Application_Grpc
{
public static class Application_GrpcExtension
{
public static IServiceCollection AddApplication_Register(this IServiceCollection services)
{
services.RegisterClassService(Assembly.GetExecutingAssembly());
return services;
}
}
}
在Program.cs内注册添加项目程序集的扩展服务,把上面Application_GrpcExtension类的AddApplication_Register进行调用:builder.Services.AddApplication_Register();
NET Core使用Grpc通信(一):一元的更多相关文章
- .NET Core使用gRPC打造服务间通信基础设施
一.什么是RPC rpc(远程过程调用)是一个古老而新颖的名词,他几乎与http协议同时或更早诞生,也是互联网数据传输过程中非常重要的传输机制. 利用这种传输机制,不同进程(或服务)间像调用本地进程中 ...
- 使用 ASP.NET Core 的 gRPC 服务
将 gRPC 服务添加到 ASP.NET Core 应用 gRPC 需要gRPC包. 配置 gRPC 在 Startup.cs 中: gRPC 是通过AddGrpc方法启用的. 每个 gRPC 服务通 ...
- dotnet core 之 gRPC
dotnet core gRPC 原文在本人公众号中,欢迎关注我,时不时的会分享一些心得 HTTP和RPC是现代微服务架构中很常用的数据传输方式,两者有很多相似之处,但是又有很大的不同.HTTP是一种 ...
- .net core中Grpc使用报错:The remote certificate is invalid according to the validation procedure.
因为Grpc采用HTTP/2作为通信协议,默认采用LTS/SSL加密方式传输,比如使用.net core启动一个服务端(被调用方)时: public static IHostBuilder Creat ...
- .net core 用grpc实现微服务
GRPC 是Google发布的一个开源.高性能.通用RPC(Remote Procedure Call)框架.提供跨语言.跨平台支持.以下以.NET Core 使用控制台.docker中演示如何使用G ...
- 重新整理 .net core 实践篇—————grpc[三十三]
前言 简单整理一下grpc. 正文 什么是grpc? 一个远程过程调用框架,可以像类一样调用远程方法. 这种模式一般来说就是代理模式,然后都是框架自我生成的. 由google 公司发起并开源,故而前面 ...
- .net core中Grpc使用报错:The response ended prematurely.
当我们调用Grpc是出现下面的一堆异常时,一般是由于LTS导致的: Call failed with gRPC error status. Status code: 'Unavailable', Me ...
- grpc基础讲解和golang实现grpc通信小案例
grpc简介 gRPC由google开发,是一款语言中立.平台中立.开源的远程过程调用系统 gRPC客户端和服务端可以在多种环境中运行和交互,例如用java写一个服务端,可以用go语言写客户端调用 g ...
- 记录core中GRPC长连接导致负载均衡不均衡问题 二,解决长连接问题
题外话: 1.这几天收到蔚来的面试邀请,但是自己没做准备,并且远程面试,还在上班时间,再加上老东家对我还不错.没想着换工作,导致在自己工位上做算法题不想被人看见,然后非常紧张.估计over了.不过没事 ...
- .net core中Grpc使用报错:Request protocol 'HTTP/1.1' is not supported.
显然这个报错是说HTTP/1.1不支持. 首先,我们要知道,Grpc是Google开源的,跨语言的,高性能的远程过程调用框架,它是以HTTP/2作为通信协议的,所以当我启动启用一个服务作为Grpc的服 ...
随机推荐
- BentoML:如何使用 JuiceFS 加速大模型加载
BentoML 是一个开源的大语言模型(LLM) AI 应用的开发框架和部署工具,致力于为开发者提供最简单的构建大语言模型 AI 应用的能力,其开源产品已经支持全球数千家企业和组织的核心 AI 应用. ...
- An Introduction to ANYDATA
以下内容来自Oracle FAQ writen By Kevin,关于ANYDATA类型在项目中的应用. My newest project needed to create a record kee ...
- Linux驱动开发笔记(四):设备驱动介绍、熟悉杂项设备驱动和ubuntu开发杂项设备Demo
前言 驱动的开发需要先熟悉基本概念类型,本篇讲解linux杂项设备基础,还是基于虚拟机ubuntu去制作驱动,只需要虚拟机就可以尝试编写注册杂项设备的基本流程. linux三大设备驱动 字符设 ...
- postgresql表结构查询sql
数据库表结构查询sql SELECT t1.attnum as "序号", t1.attname as "字段名", concat_ws ( '', t2.ty ...
- PMP考试计算题汇总
第6章 项目时间管理 本节术语较多.涉及的工具&技术也不少. 主要包括活动定义.活动排序.活动资源估算.活动历时估算.进度制定.进度控制6个子过程. 1.1活动定义:就是对WBS的进一步分解. ...
- 2、hystrix原理
hystrix熔断机制 1.隔离机制 线程隔离: Hystrix在用户请求和服务之间加入了线程池. Hystrix为每个依赖调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速 ...
- Hello 2024C. Grouping Increases(贪心)
我们只需要记录每个数结尾的数是多少(有点最长上升子序列的味道) 这种子序列的题目很多都是这样的,因为不需要连续很多时候我们只记录最后一个元素是多少. \(记s为较大子序列结尾当前的数,t为较小子序列结 ...
- python Ai 应用开发基础训练,字符串,字典,文件
-------------------------------------- 编程能是大模型应用的天花板............................................. ...
- liunx 设置默认python版本方法,
Linux 中把Python3设为默认Python版本的几种方法 由于工作中要用到到python3.6 而服务器是2.7 ,这个低版本的2.7很多系统都要依赖,还不能删,同事建议建一个虚拟环境,但是 ...
- [SCOI 2009] 迷路 (矩阵快速幂)
[SCOI 2009]迷路 传送门 问题描述 Windy 在有向图中迷路了. 该有向图有 \({N}\) 个节点,Windy 从节点 \({1}\) 出发,他必须恰好在 \({T}\) 时刻到达节点 ...