博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
eShopOnContainers 知多少[11]:服务间通信之gRPC
阅读量:4032 次
发布时间:2019-05-24

本文共 10858 字,大约阅读时间需要 36 分钟。

1. 引言

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

2. gRPC

老规矩,先来理一下gRPC的基本概念。gRPC是Google开源的RPC框架,比肩dubbo、thrift、brpc。其优势在于:

  1. 基于proto buffer:二进制协议,具有高性能的序列化机制。相较于JSON(文本协议)而言,首先从数据包上就有60%-80%的减小,其次其解包速度仅需要简单的数学运算完成,无需复杂的词法语法分析,具有8倍以上的性能提升。

  2. 基于proto 文件:可以更方便的在客户端和服务端之间进行交互。

  3. gRPC语言无关性: 所有服务都是使用原型文件定义的。这些文件基于protobuffer语言,并定义服务的接口。基于原型文件,可以为每种语言生成用于创建服务端和客户端的代码。其中protoc编译工具就支持将其生成C #代码。从.NET Core 3 中,gRPC在工具和框架中深度集成,开发者会有更好的开发体验。

  4. 支持数据流。

3. gRPC 在 eShopOncontainers 的应用

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

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

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

3.1 服务端实现

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
_logger; public OrderingService(IMediator mediator, ILogger
logger) { _mediator = mediator; _logger = logger; } public override async Task
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
MapBasketItems(RepeatedField
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
();});

3.2 客户端调用

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

public class OrderingService : IOrderingService    {        private readonly UrlsConfig _urls;        private readonly ILogger
_logger; public readonly HttpClient _httpClient; public OrderingService(HttpClient httpClient, IOptions
config, ILogger
logger) { _urls = config.Value; _httpClient = httpClient; _logger = logger; } public async Task
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的问题。

4. 不启用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
CallService
(string urlGrpc, Func
> 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
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); } }}

5. 最后

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

参考资料:

  1. [HTTP2.0笔记之连接建立:http://www.blogjava.net/yongboy/archive/2015/03/18/423570.html]

  2. [eShopOnContainers/wiki/gRPC:https://github.com/dotnet-architecture/eShopOnContainers/wiki/gRPC]

  3. [Google Protocol Buffer 的使用和原理:https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html]

转载地址:http://gizdi.baihongyu.com/

你可能感兴趣的文章
在unity中建立最小的shader(Minimal Shader)
查看>>
1.3 Debugging of Shaders (调试着色器)
查看>>
关于phpcms中模块_tag.class.php中的pc_tag()方法的含义
查看>>
vsftp 配置具有匿名登录也有系统用户登录,系统用户有管理权限,匿名只有下载权限。
查看>>
linux安装usb wifi接收器
查看>>
用防火墙自动拦截攻击IP
查看>>
补充自动屏蔽攻击ip
查看>>
谷歌走了
查看>>
多线程使用随机函数需要注意的一点
查看>>
getpeername,getsockname
查看>>
让我做你的下一行Code
查看>>
浅析:setsockopt()改善程序的健壮性
查看>>
关于对象赋值及返回临时对象过程中的构造与析构
查看>>
VS 2005 CRT函数的安全性增强版本
查看>>
SQL 多表联合查询
查看>>
Visual Studio 2010:C++0x新特性
查看>>
drwtsn32.exe和adplus.vbs进行dump文件抓取
查看>>
cppcheck c++静态代码检查
查看>>
在C++中使用Lua
查看>>
在Dll中调用自身的位图资源
查看>>