『___知多少』资料

本文主要介绍『___知多少』资料 方法和在新技术下所面对的“挑战”,方便大家深入理解『___知多少』资料 过程。本文也将分享『___知多少』资料 所遇到的问题和应对策略,怎么解决怎么做的问题。
通过深入本文可以理解代码原理,进行代码文档的下载,也可以查看相应 Demo 部署效果。

引言

最近翻看最新3.0 eShopOncontainers源码,发现其在架构选型中补充了 gRPC 进行服务间通信。那就索性也写一篇,作为系列的补充。

gRPC

老规矩,先来理一下gRPC的基本概念。gRPC是Google开源的RPC框架,比肩dubbo、thrift、brpc。其优势在于:
1. 基于proto buffer:二进制协议,具有高性能的序列化机制。相较于JSON(文本协议)而言,首先从数据包上就有60%-80%的减小,其次其解包速度仅需要简单的数学运算完成,无需复杂的词法语法分析,具有8倍以上的性能提升。
2. 支持数据流。
3. 基于proto 文件:可以更方便的在客户端和服务端之间进行交互。
4. gRPC语言无关性: 所有服务都是使用原型文件定义的。这些文件基于protobuffer语言,并定义服务的接口。基于原型文件,可以为每种语言生成用于创建服务端和客户端的代码。其中protoc编译工具就支持将其生成C #代码。从.NET Core 3 中,gRPC在工具和框架中深度集成,开发者会有更好的开发体验。

gRPC 在 eShopOncontainers 的应用

首先来理一下eShopOncontainers 中服务间同步通信的技术选型,主要还是是基于HTTP/REST,gRPC作为补充。

在eShopOncontainers中Ordering API、Catalog API、Basket API微服务通过gRPC端点暴露服务。其中Mobile Shopping、Web Shopping BFFs使用gRPC客户端访问服务。以下以Ordering API gRPC 服务举例说明。

订单微服务中定义了一个gRPC服务,用于从购物车创建订单。

服务端实现

proto文件定义如下:

syntax = "proto3"; option csharp_namespace = "GrpcOrdering"; package OrderingApi; service OrderingGrpc {   rpc CreateOrderDraftFromBasketData(CreateOrderDraftCommand) returns (OrderDraftDTO) {} } message CreateOrderDraftCommand {   string buyerId = 1;     repeated BasketItem items = 2; } message BasketItem {     string id = 1;     int32 productId = 2;     string productName = 3;     double unitPrice = 4;     double oldUnitPrice = 5;     int32 quantity = 6;     string pictureUrl = 7; } message OrderDraftDTO {     double total = 1;     repeated OrderItemDTO orderItems = 2; } message OrderItemDTO {     int32 productId = 1;     string productName = 2;     double unitPrice = 3;     double discount = 4;     int32 units = 5;     string pictureUrl = 6; } 

服务实现,主要是借助Mediator充当CommandBus进行命令分发,具体实现如下:

namespace GrpcOrdering {     public class OrderingService : OrderingGrpc.OrderingGrpcBase     {         private readonly IMediator _mediator;         private readonly ILogger<OrderingService> _logger;          public OrderingService(IMediator mediator, ILogger<OrderingService> logger)         {             _mediator = mediator;             _logger = logger;         }          public override async Task<OrderDraftDTO> CreateOrderDraftFromBasketData(CreateOrderDraftCommand createOrderDraftCommand, ServerCallContext context)         {             _logger.LogInformation("Begin gRPC call from method {Method} for ordering get order draft {CreateOrderDraftCommand}", context.Method, createOrderDraftCommand);             _logger.LogTrace(                 "----- Sending command: {CommandName} - {IdProperty}: {CommandId} ({@Command})",                 createOrderDraftCommand.GetGenericTypeName(),                 nameof(createOrderDraftCommand.BuyerId),                 createOrderDraftCommand.BuyerId,                 createOrderDraftCommand);              var command = new AppCommand.CreateOrderDraftCommand(                             createOrderDraftCommand.BuyerId,                             this.MapBasketItems(createOrderDraftCommand.Items));             var data = await _mediator.Send(command);              if (data != null)             {                 context.Status = new Status(StatusCode.OK, $" ordering get order draft {createOrderDraftCommand} do exist");                  return this.MapResponse(data);             }             else             {                 context.Status = new Status(StatusCode.NotFound, $" ordering get order draft {createOrderDraftCommand} do not exist");             }              return new OrderDraftDTO();         }          public OrderDraftDTO MapResponse(AppCommand.OrderDraftDTO order)         {             var result = new OrderDraftDTO()             {                 Total = (double)order.Total,             };              order.OrderItems.ToList().ForEach(i => result.OrderItems.Add(new OrderItemDTO()             {                 Discount = (double)i.Discount,                 PictureUrl = i.PictureUrl,                 ProductId = i.ProductId,                 ProductName = i.ProductName,                 UnitPrice = (double)i.UnitPrice,                 Units = i.Units,             }));              return result;         }          public IEnumerable<ApiModels.BasketItem> MapBasketItems(RepeatedField<BasketItem> items)         {             return items.Select(x => new ApiModels.BasketItem()             {                 Id = x.Id,                 ProductId = x.ProductId,                 ProductName = x.ProductName,                 UnitPrice = (decimal)x.UnitPrice,                 OldUnitPrice = (decimal)x.OldUnitPrice,                 Quantity = x.Quantity,                 PictureUrl = x.PictureUrl,             });         }     } } 

同时,服务端还要注册gRPC的请求处理管道:

app.UseEndpoints(endpoints => {     endpoints.MapDefaultControllerRoute();     endpoints.MapControllers();     endpoints.MapGrpcService<OrderingService>(); }); 

客户端调用

接下来看下客户端[web.bff.shopping]怎么消费的:

public class OrderingService : IOrderingService     {         private readonly UrlsConfig _urls;         private readonly ILogger<OrderingService> _logger;         public readonly HttpClient _httpClient;          public OrderingService(HttpClient httpClient, IOptions<UrlsConfig> config, ILogger<OrderingService> logger)         {             _urls = config.Value;             _httpClient = httpClient;             _logger = logger;         }         public async Task<OrderData> GetOrderDraftAsync(BasketData basketData)         {             return await GrpcCallerService.CallService(_urls.GrpcOrdering, async channel =>             {                 var client = new OrderingGrpc.OrderingGrpcClient(channel);                 _logger.LogDebug(" gRPC client created, basketData={@basketData}", basketData);                 var command = MapToOrderDraftCommand(basketData);                 var response = await client.CreateOrderDraftFromBasketDataAsync(command);                 _logger.LogDebug(" gRPC response: {@response}", response);                  return MapToResponse(response, basketData);             });         }         private OrderData MapToResponse(GrpcOrdering.OrderDraftDTO orderDraft, BasketData basketData)         {             if (orderDraft == null)             {                 return null;             }             var data = new OrderData             {                 Buyer = basketData.BuyerId,                 Total = (decimal)orderDraft.Total,             };              orderDraft.OrderItems.ToList().ForEach(o => data.OrderItems.Add(new OrderItemData             {                 Discount = (decimal)o.Discount,                 PictureUrl = o.PictureUrl,                 ProductId = o.ProductId,                 ProductName = o.ProductName,                 UnitPrice = (decimal)o.UnitPrice,                 Units = o.Units,             }));             return data;         }          private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)         {             var command = new CreateOrderDraftCommand             {                 BuyerId = basketData.BuyerId,             };              basketData.Items.ForEach(i => command.Items.Add(new BasketItem             {                 Id = i.Id,                 OldUnitPrice = (double)i.OldUnitPrice,                 PictureUrl = i.PictureUrl,                 ProductId = i.ProductId,                 ProductName = i.ProductName,                 Quantity = i.Quantity,                 UnitPrice = (double)i.UnitPrice,             }));              return command;         }     } 

其中,GrpcCallerService是对gRPC Client的一层封装,主要是为了解决未启用TLS无法使用gRPC的问题。

不启用TLS使用gRPC

我们已经知道gRpc 是基于HTTP2.0 协议。然而,连接的建立,默认并不是一步到位直接基于HTTP2.0建立连接的。客户端是先基于HTTP1.1进行协议协商,协商成功后,确认服务端支持HTTP2.0后,才会建立HTT2.0连接,协议协商需要TLS的ALPN协议来实现。流程如下:
『___知多少』

这意味着,默认情况下,您需要启用TLS协议才能完成HTTP2.0协议协商,进而才能使用gRPC。

然而,在微服务架构中,并不是所有服务都需要启用安全传输层协议,尤其是微服务间的内部调用。那么在微服务内部如何使用gRPC进行通信呢?

客户端绕过协议协商,直连HTTP2.0(前提是:服务端必须支持HTTP2.0)

服务端配置如下:

WebHost.CreateDefaultBuilder(args)     .ConfigureKestrel(options =>     {         options.Listen(IPAddress.Any, ports.httpPort, listenOptions =>         {             listenOptions.Protocols = HttpProtocols.Http1AndHttp2; //同时监听协议HTTP1,HTTP2         });         options.Listen(IPAddress.Any, ports.gRPCPort, listenOptions =>         {             listenOptions.Protocols = HttpProtocols.Http2; // gRPC端口仅监听HTTP2.0         });      }) 

客户端需要添加以下设置,这些设置只能在客户端开始时设置一次:

AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2Support", true); 

知道了这些,再回过来看GrpcCallerService的实现,就一目了然了。

public static class GrpcCallerService {     public static async Task<TResponse> CallService<TResponse>(string urlGrpc, Func<GrpcChannel, Task<TResponse>> func)     {         AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);         AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2Support", true);          var channel = GrpcChannel.ForAddress(urlGrpc);          /*         using var httpClientHandler = new HttpClientHandler         {             ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }         };         */          Log.Information(@"Creating gRPC client base address urlGrpc ={@urlGrpc},                            BaseAddress={@BaseAddress} ", urlGrpc, channel.Target);          try         {             return await func(channel);         }         catch (RpcException e)         {             Log.Error("Error calling via gRPC: {Status} - {Message}", e.Status, e.Message);             return default;         }         finally         {             AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", false);             AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2Support", false);         }     }      public static async Task CallService(string urlGrpc, Func<GrpcChannel, Task> func)     {         AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);         AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2Support", true);          /*         using var httpClientHandler = new HttpClientHandler         {             ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }         };         */          var channel = GrpcChannel.ForAddress(urlGrpc);          Log.Debug("Creating gRPC client base address {@httpClient.BaseAddress} ", channel.Target);          try         {             await func(channel);         }         catch (RpcException e)         {             Log.Error("Error calling via gRPC: {Status} - {Message}", e.Status, e.Message);         }         finally         {             AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", false);             AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2Support", false);         }     } } 

最后

本文简要介绍了 eShopOnContainers 如何通过集成 gRPC 来完善服务间同步通信机制,希望对你在对微服务进行RPC相关技术选型时有一定的启示和帮助。

参考资料:

  1. HTTP2.0笔记之连接建立
  2. eShopOnContainers/wiki/gRPC
  3. Google Protocol Buffer 的使用和原理

『___知多少』资料部分资料来自网络,侵权毕设源码联系删除

区块链毕设网(www.qklbishe.com)全网最靠谱的原创区块链毕设代做网站
部分资料来自网络,侵权联系删除!
资源收费仅为搬运整理打赏费用,用户自愿支付 !
qklbishe.com区块链毕设代做网专注|以太坊fabric-计算机|java|毕业设计|代做平台 » 『___知多少』资料

提供最优质的资源集合

立即查看 了解详情