【微服务】03-HttpClientFactory与gRpc

文章目录

    • [1.HttpClientFactory :管理外向请求的最佳实践](#1.HttpClientFactory :管理外向请求的最佳实践)
      • [1.1 核心能力](#1.1 核心能力)
      • [1.2 核心对象](#1.2 核心对象)
      • [1.3 HttpClient创建模式](#1.3 HttpClient创建模式)
    • 2.gRPC:内部服务间通讯利器
      • [2.1 什么是gRPC](#2.1 什么是gRPC)
      • [2.2 特点gRPC特点](#2.2 特点gRPC特点)
      • 2.3.NET生态对gRPC的支持情况
      • [2.4 服务端核心包](#2.4 服务端核心包)
      • [2.5 客户端核心包](#2.5 客户端核心包)
      • [2.5 .proto文件](#2.5 .proto文件)
      • [2.6 gRPC异常处理](#2.6 gRPC异常处理)
      • [2.7 gRPC与HTTPS证书](#2.7 gRPC与HTTPS证书)
      • [2.8 gRPC命令行工具](#2.8 gRPC命令行工具)
        • [2.8.1 工具核心包](#2.8.1 工具核心包)
        • [2.8.2 核心命令](#2.8.2 核心命令)
        • [2.8.3 最佳实践](#2.8.3 最佳实践)

1.HttpClientFactory :管理外向请求的最佳实践

1.1 核心能力

  • 管理内部HttpMessageHandler的生命周期,灵活应用资源问题和DNS刷新问题
  • 支持命名化、类型化配置,集中管理配置,避免冲突
  • 灵活的出站请求管道配置,轻松管理请求声明周期
  • 内置管道最外层和最内层日志记录器,有Information和Trace输出

1.2 核心对象

  • HttpClient
  • HttpMessageHandler
  • SocketsHttpHandler
  • DelegatingHandler
  • IHttpClientFactory
  • IHttpClientBuilder

管道模型

请求过程

HttpClient发起请求,然后最外层的日志记录器来记录日志,然后再进到自定义的Handler中处理自定义的逻辑;

然后最内层的SocketsHttpHandler,这是真正去发起远程调用的处理程序,它回向远程站点发起HTTP请求并接受响应,在接收到响应以后,Http最内层的日志记录器会记录响应信息;

随后将响应结果交还给自定义的Handler,在接受响应后处理接受响应的逻辑,处理完成后最外层的日志记录器会输出响应日志,最终HttpClient拿到响应结果,输出给应用程序

1.3 HttpClient创建模式

  • 工厂模式

  • 命名客户端模式

  • 类型化客户端模式

    // 工厂模式
    public class OrderServiceClient
    {
    IHttpClientFactory _httpClientFactory;

    复制代码
      public OrderServiceClient(IHttpClientFactory httpClientFactory)
      {
      	_httpClientFactory = httpClientFactory;
      }
    
      public async Task<string> Get()
      {
      	var client = _httpClientFactory.CreateClient();
      	return await client.GetStringAsync("这里是远程服务路径地址");// 相对路径访问
      }

    }

    // 命名客户端
    public class NamedOrderServiceClient
    {
    IHttpClientFactory _httpClientFactory;
    const string _clientName = "NamedOrderServiceClient";
    public NamedOrderServiceClient(IHttpClientFactory httpClientFactory)
    {
    _httpClientFactory = httpClientFactory;
    }

    复制代码
      public async Task<string> Get()
      {
      	var client = _httpClientFactory.CreateClient(_clientName);
      	return await client.GetStringAsync("这里是远程服务路径地址");// 相对路径访问
      }

    }

    // 类型客户端
    public class TypeOrderServiceClient
    {
    HttpClient _client;
    public TypeOrderServiceClient(HttpClient client)
    {
    _client = client;
    }

    复制代码
      public async Task<string> Get()
      {	
      	return await _client.GetStringAsync("这里是远程服务路径地址");// 相对路径访问
      }

    }

    // HttpClientFactory注册,startup中ConfigurationService
    public void ConfigureServices(IServiceCollection services)
    {
    // 工厂模式
    services.AddHttpClient();
    services.AddScope<OrderServiceClient>();

    复制代码
      // 命名客户端模式
      services.AddHttpClient("NamedOrderServiceClient",client =>
      {
      	client.DefaultRequestHeaders.Add("client-name","nameclient");
      	client.BaseAddress = new Uri("远程站点根路径");
      })
      .AddHttpMessageHandler(provider => provider.GetService<RequestIdDelegatingHandler>());//自定义Handler
      services.AddScope<NamedOrderServiceClient>();
    
      //推荐使用
      // 类型客户端,
      services.AddHttpClient<TypeOrderServiceClient>(client =>
      {
      	client.BaseAddress = new Uri("远程站点根路径");
      });

    }

    // Controller中使用
    public class OrderController : ControllerBase
    {
    OrderServiceClient _orderServiceClient;
    public OrderController(OrderServiceClient orderServiceClient)
    {
    _orderServiceClient = orderServiceClient;
    }

    复制代码
      [HttpGet("Get")]
      public Task<string> Get()
      {
      	return await _orderServiceClient.Get();
      }
    
      [HttpGet("NameGet")]
      public Task<string> NamedGet([FromServices] NamedOrderServiceClient serviceClient)
      {
      	return await serviceClient.Get();
      }
    
      [HttpGet("TypeGet")]
      public async Task<string> TypeGet([FromServices] TypeOrderServiceClient client)
      {
      	return await client.Get();
      }

    }

2.gRPC:内部服务间通讯利器

2.1 什么是gRPC

  • 定义

gRPC是一个远程过程调用框架,作用是让我们可以像在调用本地的类一样调用远程的服务。由Google公司发起并开源,g表示Google公司,RPC表示远程调用

2.2 特点gRPC特点

  • 提供几乎所有主流语言的实现,打破语言隔阂
  • 基于HTTP/2,开放协议,收到广泛的支持,易于实现和集成
  • 默认使用ProtocolBuffers序列化,性能相较于RESTfulJson好很多
  • 工具链成熟,代码生成便捷,开箱即用
  • 支持双向流式的请求和响应,对批量处理、低延时场景友好

2.3.NET生态对gRPC的支持情况

  • 提供基于HttpClient的原生框架实现
  • 提供原生的ASP.NETCore集成库
  • 提供完整的代码生成工具
  • Visual Studio和Visual Studio Code提供proto文件的只能提示

2.4 服务端核心包

  • Grpc.AspNetCore

2.5 客户端核心包

  • Google.Protobuf ⇒ 序列化协议包
  • Grpc.Net.Client ⇒ 客户端包
  • Grpc.Net.ClientFactory ⇒ 与HttpClientFactory集成的包
  • Grpc.Tools ⇒ 提供命令行工具使用的包

2.5 .proto文件

  • 定义包、库名
  • 定义服务"service"
  • 定义输出和输入模型"message"

2.6 gRPC异常处理

  • 使用Grpc.Core.RpcException
  • 使用Grpc.Core.Interceptors.Interceptor

RpcException支持拦截器,可以通过注入拦截器处理异常

2.7 gRPC与HTTPS证书

  • 使用自制证书
  • 使用非加密的HTTP2

服务端

复制代码
// order.proto文件定义 *****核心*****
syntax = "proto3";// 定义proto协议类型为proto3
// 定义命名空间为GrpcServices
option csharp_namespace = "GrpcServices";
package GrpcServices;

// 定义服务OrderGrpc 
service OrderGrpc {
	rpc CreateOrder(CreateOrderCommand) returns (CreateOrderResult);
}

// 定义输入输出响应,需要为每个字段定义顺序,这也是序列化时候的顺序
// 序列化时是根据数据类型和顺序识别字段的值
message CreateOrderCommand {
	string buyerId = 1;
    int32 productId = 2;
    double unitPrice = 3;
    double discount = 4;
    int32 units = 5;
}

message CreateOrderResult {
    int32 orderId = 1;
}

// 定义OrderService
public class OrderService : OrderGrpc.OrderGrpcBase
{
     public override Task<CreateOrderResult> CreateOrder(CreateOrderCommand request, ServerCallContext context)
     {

         throw new System.Exception("order error");

         //添加创建订单的内部逻辑,录入将订单信息存储到数据库
         return Task.FromResult(new CreateOrderResult { OrderId = 24 });
     }
 }
// 注册gRPC服务
public void ConfigureServices(IServiceCollection services)
{
       services.AddGrpc(options =>
       {
           options.EnableDetailedErrors = true;// 内部错误信息输出设置,生产环境不对外输出
           options.Interceptors.Add<ExceptionInterceptor>();// 添加的异常拦截器
       });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 {
      if (env.IsDevelopment())
      {
          app.UseDeveloperExceptionPage();
      }

      app.UseRouting();

      app.UseEndpoints(endpoints =>
      {
          endpoints.MapGrpcService<OrderService>();
          endpoints.MapGet("/", async context =>
          {
              await context.Response.WriteAsync("Hello World!");
          });
      });
  }

定义好order.proto文件,那么会自动生成服务端代码,生成的代码在项目目录的obj文件夹下的Order.cs和OrderGrpc.cs

客户端

将服务端的order.proto文件引入客户端,可以基于proto文件生成客户端代码

复制代码
// startup
 public void ConfigureServices(IServiceCollection services)
 {
 	 //允许使用不加密的HTTP/2协议
	 //AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
 
 	// 注册OrderGrpcClient
 	// OrderGrpcClient文件是由order.proto文件生成的
 	services.AddGrpcClient<OrderGrpc.OrderGrpcClient>(options =>
    {
          options.Address = new Uri("https://localhost:5001");
      })
      .ConfigurePrimaryHttpMessageHandler(provider =>
      {
           var handler = new SocketsHttpHandler();
           handler.SslOptions.RemoteCertificateValidationCallback = (a, b, c, d) => true; //允许无效、或自签名证书
           return handler;
       }).AddTransientHttpErrorPolicy(p => p.WaitAndRetryForeverAsync(i => TimeSpan.FromSeconds(i * 3)));
 }

2.8 gRPC命令行工具

2.8.1 工具核心包

  • Grpc.Tools ⇒ 工程需要引用的工具
  • dotnet-grpc ⇒ 命令行工具,是.net命令行的工具插件

2.8.2 核心命令

  • dotnet grpc add-file ⇒ 将指定目录下的proto文件添加到工程中
  • dotnet grpc add-url ⇒ 将一个HTTP的URL地址指定的proto文件添加到我们的工程中
  • dotnet grpc remove ⇒ 将添加的proto文件的引用移除,文件不会移除
  • dotnet grpc refresh ⇒ 更新proto文件

2.8.3 最佳实践

  • 使用单独的Git仓库管理proto文件
  • 使用submodule将proto文件集成到工程目录中
  • 使用dotnet-grpc命令行添加proto文件及相关依赖包引用

备注

由proto生成的代码文件会存放在obj目录中,不会被签入到Git仓库

相关推荐
David爱编程11 分钟前
Java 守护线程 vs 用户线程:一文彻底讲透区别与应用
java·后端
小奏技术28 分钟前
国内APP的隐私进步,从一个“营销授权”弹窗说起
后端·产品
小研说技术1 小时前
Spring AI存储向量数据
后端
苏三的开发日记1 小时前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台处于同一台服务器)
后端
苏三的开发日记1 小时前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台不在同一服务器)
后端
陈三一1 小时前
MyBatis OGNL 表达式避坑指南
后端·mybatis
whitepure1 小时前
万字详解JVM
java·jvm·后端
我崽不熬夜1 小时前
Java的条件语句与循环语句:如何高效编写你的程序逻辑?
java·后端·java ee
我崽不熬夜2 小时前
Java中的String、StringBuilder、StringBuffer:究竟该选哪个?
java·后端·java ee
文火冰糖的硅基工坊2 小时前
[激光原理与应用-317]:光学设计 - Solidworks - 草图
开发语言·python·信息可视化·系统架构