gRPC - zilor-net/eShopOnContainers-CN-Wiki GitHub Wiki
.NET Core 3.0 的一大新闻是对gRPC的原生支持。 eShopOnContainers 利用 gRPC 进行微服务到微服务的内部同步通信。注意,在 eShop 中,大多数微服务之间的通信都是通过事件总线(支持 RabbitMQ 或 Azure Service Bus)来实现解耦和异步的。gRPC 是一种基于 HTTP/2 和 protobuf 的高性能通信协议。它应该是服务之间直接同步通信的首选(这与其他协议,如用于队列或发布/订阅等异步通信的 AMQP 相反)。
它比使用 HTTP 和 JSON的主要好处是:
- protobuf 是一种二进制的、高性能的序列化机制。根据语言实现的不同, protobuf 比 JSON 序列化的效率快了 8 倍,而消息大小也小了 60%-80%。
- 支持数据流
- 服务和客户机之间的契约是显式的(通过使用proto文件)
在当前的实现中,gRPC的使用仅限于聚合器和微服务之间的通信。
eShopOnContainers 目前在服务之间有以下同步通信:
- 外部客户端(即 Xamarin 应用或浏览器)到 Api 网关(BFFs):使用 HTTP/REST
- 从 BFFs 到聚合器:使用 HTTP/REST
- 这基本上是一个请求转发,根据请求路由,它从 BFF 转发到聚合器。
- 当单个客户端调用涉及从聚合器协调的各种微服务时,这将用于“逻辑复杂”的请求。
- 从 BFFs 到微服务: 使用 HTTP/REST
- 这基本上是一个请求转发,根据请求路由,它被从 BFF 转发到内部微服务。这是为简单的 CRUD 请求执行的。
- 从聚合器到微服务:使用 gRPC
目前还没有从 gRPC 到 HTTP/REST 的代码转换。这将允许从 BFFs 到聚合器和微服务使用 gRPC ,同时保持对客户端的 HTTP/REST 接口。gRPC<->HTTP/REST 的转换可以在 BFF 层完成。
以下微服务公开了 gRPC 端点:
- Ordering API
- Catalog API
- Basket API
以下 BFFs 为 gRPC 客户端:
- Mobile Shopping
- Web Shopping
gRPC 是语言无关的:所有的服务都是使用proto文件定义的(通常使用 .proto
的扩展)。这些文件基于protobuffer 语言,并定义了服务的接口。基于这个proto文件,可以为每种语言生成创建服务端和客户端的代码。
从 .NET Core 3 开始,gRPC 已经深度集成到了工具和框架中,以便尽可能的获得无缝使用 gRPC 的体验。
该工具集成在“msbuild”中(因此它可以被 Visual Studi o和 dotnet build
SDK命令使用),它允许基于一个原型文件生成创建 gRPC 服务端或客户端所需的代码。
proto文件必须在csproj
中使用<ProtoBuf>
标签(在<ItemGroup>
内)引用:
<ItemGroup>
<Protobuf Include="Protos\catalog.proto" GrpcServices="Client" />
</ItemGroup>
GrpcServices
属性用于指定生成Server
还是Client
stubs。
注意需要的话,你可以包括许多
<ProtoBuf>
标签。
当你编译代码时(从 Visual Studio 运行Build或dotnet build
),所有的代码都会生成并放在obj
文件夹中。
这是有意为之的:此代码永远不应该出现在源代码控制存储库中。
服务端 stub 生成的代码,定义了一个抽象基类和一组抽象方法,你必须实现这些方法。
你需要实现 proto文件中定义的每个 rpc
抽象方法:
service Catalog {
rpc GetItemById (CatalogItemRequest) returns (CatalogItemResponse) {}
rpc GetItemsByIds (CatalogItemsRequest) returns (PaginatedItemsResponse) {}
它们会生成一个“CatalogBase”抽象类:
public class CatalogService : CatalogBase
{
public CatalogService()
{
}
public override async Task<CatalogItemResponse> GetItemById(CatalogItemRequest request, ServerCallContext context)
{
// Code
}
public override async Task<PaginatedItemsResponse> GetItemsByIds(CatalogItemsRequest request, ServerCallContext context)
{
// Code
}
}
参数和返回值所需的 C# 类型也都会自动生成。
ASP.NET Core 支持直接集成 gRPC 管道。
You only have to use the method MapGrpcService
of the IEndpointRouteBuilder
in your Startup
class:
你只需要在你的 Startup
类中使用IEndpointRouteBuilder
的方法MapGrpcService
:
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapControllers();
endpoints.MapGrpcService<CatalogService>();
});
If you are creating a gRPC client instead of a server, you need to create a GrpChannel
and then a gRPC client using this channel:
如果你正在创建一个 gRPC 客户端,而不是服务端,那么你需要创建一个GrpChannel
,然后使用这个通道创建一个gRPC 客户端:
var channel = GrpcChannel.ForAddress(UrlOfService);
var client = new Basket.BasketClient(channel);
Basket.BasketClient
类是从 proto 文件生成的 stub,你可以调用BasketClient
类的方法。
gRPC 只支持 HTTP/2 协议。
Usually when a client connects to a server, the connection is done using HTTP1.1 and promoted to HTTP/2 only if both, server and client, support HTTP/2. This promotion is performed using a protocol negotiation, usually implemented using ALPN protocol which requires TLS.
通常,当客户端连接到服务器时,连接是使用 HTTP 1.1 完成的,只有当服务器和客户端都支持 HTTP/2 时,连接才会被提升到 HTTP/2。此提升使用协议协商来执行,通常使用需要 TLS 的 ALPN 协议来实现。
这意味着,在默认情况下,你需要启用一个 TLS 端点才能使用gRPC。
然而,在内部的微服务中,可能没有使用 TLS 的端点(因为这些端点都是内部的)。在这种情况下,你有两个选择:
- 打开单个 Kestrel 端点,在 HTTP/2 上侦听
- 打开两个Kestrel端点,一个在 HTTP 1.1 上侦听,另一个在 HTTP/2 上侦听
如果你的服务器必须支持除 gRPC 客户端之外的 HTTP 1.1 客户端,就选择第二个。下面的 C# 代码(在Program.cs
中)展示了第二种方法:
WebHost.CreateDefaultBuilder(args)
.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Any, ports.httpPort, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
});
options.Listen(IPAddress.Any, ports.grpcPort, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
})
但是,这还不够。我们需要告诉 gRPC 客户端,它可以直接连接到 HTTP/2 端点,而不需要 TLS。
在默认情况 ASP.NET 不允许 gRPC 客户端连接到非 TLS 端点。
客户端需要如下代码行:
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2Support", true);
这些设置只能在客户端开始时设置一次。