首页 > 其他分享 >NET Core使用Grpc通信(一):一元请求

NET Core使用Grpc通信(一):一元请求

时间:2024-03-30 16:55:05浏览次数:19  
标签:Core 调用 HTTP string Service Grpc RPC NET

gRPC是一个现代的开源高性能远程过程调用(RPC)框架,它可以高效地连接数据中心内和跨数据中心的服务,支持负载平衡、跟踪、运行状况检查和身份验证。

gRPC通过使用 Protocol Buffers 作为数据传输格式,实现了在不同平台上的通信,并支持双向流和流式传输。RPC 是远程过程调用的缩写,实现跨服务器调用。在开发中,规定调用规则、网络传输协议以及数据序列化反序列化规范是确保前后端通信规范性的关键。

了解GRpc前需要了解Rpc概念。

什么是 RPC 

RPC 是 Remote Procedure Call 的简称,中文叫远程过程调用

说的白话一点,可以这么理解:比如有两台服务器A和B,A服务器上的应用想调用B服务器上的另一个应用提供的方法,但由于不在同一个内存空间,无法直接调用,所以需要通过网络来实现调用效果。

其实大家在平时开发中有接触过,例如:前端去请求后端的接口。我们来想一下前后端要制定什么规则,才能进行接口请求:

  • 调用的语义,也可以理解为接口规范。(比如 RESTful )
  • 网络传输协议 (比如 HTTP )
  • 数据序列化反序列化规范(比如 JSON )

只有制定了这些规则,才能保证前后端通信的规范性

什么是 gRPC交互图

从上图中可以看出,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 是怎么传输的?

服务端 Stub 接收客户端请求,处理请求中的 Protobuf 数据并进行反序列化,然后将请求对象传入服务器并实现业务逻辑处理。最终再将响应序列化后返回给客户端,从而形成一次完整的接口调用过程。

什么是 gRPC

 

以上概念以及相关知识点来自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();

标签:Core,调用,HTTP,string,Service,Grpc,RPC,NET
From: https://www.cnblogs.com/LaoPaoEr/p/18105579

相关文章

  • Kubernetes之Pod
    什么是Pod通俗的来讲就是以pause为基础容器,其它容器共享pause容器的网络名称空间、主机名以及进程间通信,组成的一个逻辑的容器集合。•KubernetesPod是Kubernetes的基础单元,一个Pod是一组功能相关的部署到一起的容器的集合。•在Kubernetes中,每个Pod会有自己独立的内部动......
  • skynet非单点类型节点的管理(一):玩家代理节点
    单个skynet进程,或者说单台机器的承载业务能力是有上限的,对于负责玩家主要业务的节点,横向扩展以提高游戏承载能力是必须的。对于滚服架构,玩家角色与指定业务节点(单服)固定对应,连接游戏业务前通过中央后台获取到指定信息进行连接。承载能力通过新增单服完成,这里我们只对世界服架构做......
  • Kubernetes资源管理
    为了避免集群中的Pod负载加大时节点资源不足,导致某些用户进程被“杀掉”,Kubernetes需要有一套完备的资源配额限制及对应的Pod服务等级机制,解决思路如下:(1)可以全面限制一个应用及其中的Pod所能占用的资源配额。具体包括三种方式:<1>定义每个Pod上资源配额相关的参......
  • Kubernetes超详细教程,一篇文章帮助你从零开始学习k8s,从入门到实战
    k8s概述k8sgithub地址:https://github.com/kubernetes/kubernetes官方文档:https://kubernetes.io/zh-cn/docs/home/k8s,全程是kubernetes,这个名字源于希腊语,意为"舵手"或"飞行员”k8s这个缩写是因为k和s之间有八个字符Google在2014年开源了k8s项目,k8s是一个......
  • Kubernetes之存储原理和应用——持久卷(PV)
    一、概念解析1.PV与PVC为了能够屏蔽底层存储实现的细节,让用户方便使用及管理员方便管理,Kubemetes从1.0版本开始引入了PersistentVolume(PV)与PersistentVolumeClaim(PVC)资源对象来实现存储管理子系统。PV是对存储资源的抽象,将存储定义为一种容器应用可以使......
  • 深度学习pytorch——经典卷积网络之ResNet(持续更新)
    错误率前五的神经网络(图-1):图-1可以很直观的看到,随着层数的增加Error也在逐渐降低,因此深度是非常重要的,但是学习更好的网络模型和堆叠层数一样简单吗?通过实现表明(图-2),并不是如此,会出现梯度消失和梯度爆炸的现象,甚至比堆叠之前的训练效果更差,这种现象被称为梯度......
  • Netlify 就你妈傻逼
    因为脑残所以想从GithubPages转移到Netlify上。然后我他妈登陆Netlify告诉我账号有可疑行为被停用了。我可疑你妈呢?行行行,用manualverification行了吧?我现在等了快一周了还是什么消息都没有。注册一个新的号行了吧?收不到验证码。草这不是我的问题啊。几遍都收不到。......
  • 隐藏ASP.NET MVC的版本信息,使其不在HTTP Header中显示
    隐藏ASP.NETMVC的版本信息,使其不在HTTPHeader中显示。一、隐藏:X-AspNetMvc-Version在Global.asax文件的Application_Start方法中添加:1MvcHandler.DisableMvcResponseHeader= true;二、移除Header中的Server在Global.asax文件中添加:12345......
  • gRPC入门学习之旅(四)
    gRPC入门学习之旅(一)gRPC入门学习之旅(二)gRPC入门学习之旅(三)实现定义的服务9.在“解决方案资源管理器”中,使用鼠标左键选中“Services”文件夹,然后在菜单栏上,依次选择“添加-->新建项”。10.在“添加新项”对话框中,选择“ASP.NETCore-->代码”节点,然后选择“类”项。......
  • gRPC框架
    读了songguojun大佬的一篇文章gRPC框架详解,总结一下关于gRPC的知识点RPC是什么RPC是远程调用,是一种通过网络从远程计算机程序上请求服务,不需要了解底层网络技术的协议,简单的理解就是一个节点请求另一个节点提供的服务。RPC只是一套协议,基于这套协议规范来实现的框架称为RPC框架......