自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用 gRPC 和 .NET7 構(gòu)建高性能服務(wù)

開發(fā) 架構(gòu)
本文的目的是提供有關(guān)已添加到 gRPC .NET 7 框架的性能增強(qiáng)的信息,包括 gRPC JSON 轉(zhuǎn)碼功能、OpenAPI 規(guī)范和服務(wù)器反射功能,以及新的性能改進(jìn)。

.NET 7 正式推出標(biāo)準(zhǔn)期限支持,支持期限為 18 個(gè)月。 其中包括許多令人興奮的新功能,包括 Web API、gRPC、ASP.NET 和 C#11 的性能升級(jí)。

本文涵蓋以下主題:

  1. .NET 7 中的性能改進(jìn)。
  2. gRPC JSON 轉(zhuǎn)碼。
  3. 在 .NET 7 中創(chuàng)建 gRPC 服務(wù)。
  4. 在 Postman 使用 gRPC 服務(wù)。
  5. 使用服務(wù)器反射和 Postman
  6. 添加 Swagger 規(guī)范。

除了討論 .NET 7 中 gRPC 的新特性,我們還將實(shí)現(xiàn)一個(gè)能夠在一分鐘內(nèi)流式傳輸 500 萬(wàn)條記錄的真實(shí)微服務(wù)。

這是一個(gè)快速回顧:

  • gRPC 是由 CNCF 開發(fā)的流行的開源 RPC 框架。
  • 作為契約優(yōu)先、獨(dú)立于語(yǔ)言的框架,客戶端和服務(wù)器必須就消息的內(nèi)容和傳遞方式達(dá)成一致,契約在 .proto 文件中定義,然后使用 .NET7 的工具生成代碼。
  • 在單個(gè) tcp 連接上,HTTP/2 支持多路復(fù)用,您可以同時(shí)發(fā)送多個(gè)請(qǐng)求。
  • 此外,gRPC 支持?jǐn)?shù)據(jù)流,其中服務(wù)器可以同時(shí)向客戶端發(fā)送多個(gè)響應(yīng),反之亦然。

.NET 7 中有哪些新功能?

1、性能改進(jìn)

為了讓 gRPC 支持多路復(fù)用,HTTP/2 是必需的。 但是,Kestrel 的 HTTP/2 實(shí)現(xiàn)存在一個(gè)已知問(wèn)題,該問(wèn)題會(huì)在連接繁忙時(shí)通過(guò) HTTP/2 寫入響應(yīng)時(shí)出現(xiàn)瓶頸。 當(dāng)您在同一個(gè) TCP 連接上同時(shí)運(yùn)行多個(gè)請(qǐng)求,但一次只有一個(gè)線程能夠?qū)懭朐撨B接時(shí),就會(huì)發(fā)生這種情況。 這是通過(guò) .NET 6 中的線程鎖完成的,這會(huì)導(dǎo)致鎖爭(zhēng)用。

NET 7 使用一種巧妙的方法來(lái)解決此瓶頸,即實(shí)現(xiàn)一個(gè)隊(duì)列,該隊(duì)列會(huì)在寫入完成時(shí)通知所有其他線程,讓它們等待寫入完成。 因此,性能大大提升,CPU資源得到更好的利用——不再需要爭(zhēng)鎖。

.NET gRPC 團(tuán)隊(duì)的基準(zhǔn)測(cè)試表明服務(wù)器流式處理提高了 800%。

  • .NET 6–0.5M RPS
  • .NET 7–4.5M RPS

HTTP/2 上傳速度

通過(guò)增加緩沖區(qū)大小可將延遲減少 600%。 與 .NET 6 相比,.NET 7 將上傳 100MB 文件的時(shí)間從 26.9 秒減少到 4.3 秒。

.NET 7 gRPC 的性能現(xiàn)在超過(guò)了 Rust、Go 和 C++ 等流行框架。

2、gRPC JSON轉(zhuǎn)碼

.NET7 為 ASP.NET Core gRPC 提供了擴(kuò)展,以使 gRPC 服務(wù)能夠作為 RESTful Web 服務(wù)公開。 您現(xiàn)在可以通過(guò) HTTP 調(diào)用 gRPC 方法而無(wú)需任何重復(fù)。

gRPC JSON 轉(zhuǎn)碼支持:

  • HTTP 動(dòng)詞
  • URL參數(shù)綁定
  • JSON請(qǐng)求/響應(yīng)

在此擴(kuò)展中,HTTP 動(dòng)詞通過(guò)使用 protobuf 注釋的概念映射到 gRPC 服務(wù),擴(kuò)展在 ASP.NET Core 應(yīng)用程序中運(yùn)行,然后將 JSON 反序列化為 protobuf 消息并直接調(diào)用 gRPC 服務(wù),而不必編寫自己的 gRPC 客戶端應(yīng)用程序。

我們將在下一節(jié)中研究如何實(shí)現(xiàn)它。

3、開放API規(guī)范

現(xiàn)在有一個(gè) Open API 規(guī)范,用于 .NET 7 中的 gRPC JSON 轉(zhuǎn)碼,使用以下 Nuget 包:

?https://www.nuget.org/packages/Microsoft.AspNetCore.Grpc.Swagger?

4、 Azure 應(yīng)用服務(wù)支持

最后但同樣重要的是,Azure 應(yīng)用服務(wù)現(xiàn)在完全支持 gRPC。 這是在 .NET 中使用 gRPC 構(gòu)建和部署高性能服務(wù)的一大進(jìn)步。

現(xiàn)在我們已經(jīng)完成了討論,讓我們實(shí)現(xiàn) gRPC 并看看新功能是什么樣的。

先決條件:

  • 下載并安裝 .NET 7 SDK
  • Visual Studio 2022 17.4+

我們需要做的第一件事是啟動(dòng) Visual Studio 并創(chuàng)建一個(gè)新項(xiàng)目。 選擇“ASP.NET Core gRPC 服務(wù)”,這將創(chuàng)建一個(gè)示例 hello world gRPC 服務(wù)。

確保選擇了 .NET7。

這將分別在 protos 和服務(wù)文件夾中的 GreeterService 中創(chuàng)建一個(gè)隨時(shí)可用的 gRPC 應(yīng)用程序。

這是一個(gè)用作契約的 greeting.proto 文件,定義了客戶端將接收的消息和服務(wù)。

syntax = "proto3";

option csharp_namespace = "gRPCUsingNET7Demo";

package greet;

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings.
message HelloReply {
string message = 1;
}

契約可以被認(rèn)為是接口,這些接口的實(shí)現(xiàn)將由服務(wù)定義,在我們的例子中是 GreeterService.cs——這個(gè)文件將描述契約的實(shí)現(xiàn)。

GreeterService 類是一個(gè)標(biāo)準(zhǔn)的 C# 類,它向響應(yīng)返回 hello。 protobuf 的實(shí)際實(shí)現(xiàn)是通過(guò)代碼生成實(shí)現(xiàn)的,并使用 GreeterBase 抽象出來(lái)。 如果您想確切地知道引擎下發(fā)生了什么,您可以轉(zhuǎn)到 GreeterBase,您會(huì)在那里找到所有底層細(xì)節(jié)。

public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger){
_logger = logger;
}

public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}

代碼生成是 .NET 7 的一項(xiàng)不錯(cuò)的功能,它允許您生成服務(wù)器端和客戶端 gRPC 代碼。 通過(guò)設(shè)置代碼生成設(shè)置,可以更改 .CS 項(xiàng)目文件中代碼生成過(guò)程的行為(例如從服務(wù)器到客戶端)。

  <ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>

讓我們啟動(dòng) Kestral 并在打開應(yīng)用程序后在瀏覽器中瀏覽 gRPC 端點(diǎn)。

我們無(wú)法通過(guò)網(wǎng)絡(luò)訪問(wèn)我們的 gRPC 服務(wù),因?yàn)樗枰褂?gRPC 客戶端。 但是,我們不需要使用 gRPC 客戶端,而是使用流行的測(cè)試工具 Postman 對(duì)其進(jìn)行測(cè)試。 它最近在其功能中添加了對(duì) gRPC 請(qǐng)求的支持。

第一步是打開 Postman 并創(chuàng)建一個(gè)新的 gRPC 請(qǐng)求。

請(qǐng)?jiān)谙旅娴目蛑休斎敕?wù)器地址(您的應(yīng)用程序運(yùn)行的地址)。 例如,https://localhost:7211。

Postman目前不了解我們的服務(wù)如何運(yùn)作,因此我們有幾個(gè)選擇。 一種是導(dǎo)入 .proto 文件或使用稱為“服務(wù)器反射”的東西。 它可以被認(rèn)為是 gRPC 調(diào)用的 OpenAPI 規(guī)范。

在 gRPC 服務(wù)中啟用服務(wù)器反射。

按照以下步驟啟用服務(wù)器反射非常簡(jiǎn)單。

下載并安裝以下 nuget 包:

Install-Package Grpc.AspNetCore.Server.Reflection -Version 2.49.0

2、在Program.cs文件中,需要注冊(cè)如下服務(wù),并將該服務(wù)映射到我們的http管道中,如下:

builder.Services.AddGrpcReflection();

app.MapGrpcReflectionService();

現(xiàn)在我們已經(jīng)完成了所有這些前置需求,讓我們回到 Postman,再次運(yùn)行應(yīng)用程序。

我們可以看到我們的 greet.greeter 服務(wù)和它的 SayHello 方法。

可以通過(guò)單擊帶有 JSON 正文(將由 Postman 轉(zhuǎn)換為 protobuf)的 Invoke 按鈕來(lái)調(diào)用此端點(diǎn)。

在 49 毫秒內(nèi)得到了服務(wù)器響應(yīng)。

將您的 gRPC 服務(wù)轉(zhuǎn)變?yōu)?REST

本節(jié)將實(shí)現(xiàn) gRPC JSON 轉(zhuǎn)碼以通過(guò) HTTP 訪問(wèn) gRPC。

  1. 將以下 nuget 包添加到您的項(xiàng)目中:
Install-Package Microsoft.AspNetCore.Grpc.JsonTranscoding -Version 7.0.0

2. 導(dǎo)航到 Program.cs 并添加 JSONTranscoding 服務(wù):

builder.Services.AddGrpc().AddJsonTranscoding();

下一步,我們將向您的項(xiàng)目添加兩個(gè)配置文件。

添加這些文件后,我們需要修改 greet.proto 并添加 import “google/api/annotations.proto” 以便我們可以注解服務(wù)方法。

  // Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply)
{
option (google.api.http) =
{
get: "/v1/greeter/{name}"
}

};

基本上,我們向我們的 RPC 方法添加了一個(gè)路由,以便它可以作為 REST 方法被調(diào)用。 讓我們?cè)俅芜\(yùn)行應(yīng)用程序并使用瀏覽器執(zhí)行端點(diǎn)。

就是這樣! 該 API 現(xiàn)在作為基于 REST 的 API 工作,但它仍然可以作為 gRPC 接口使用。 來(lái)自 Postman 的 gRPC 響應(yīng)如下所示。

添加開放 API 規(guī)范

本節(jié)的目的是解釋我們?nèi)绾问褂?gRPC.Swagger 向我們的應(yīng)用程序添加開放 API 規(guī)范。

  1. 安裝以下 nuget 包:
Install-Package Microsoft.AspNetCore.Grpc.Swagger -Version 0.3.0

2.注冊(cè)Swagger服務(wù)和中間件,如下

   builder.Services.AddGrpcSwagger();
builder.Services.AddSwaggerGen( c=>
{
c.SwaggerDoc("v1",
new Microsoft.OpenApi.Models.OpenApiInfo { Title = "gRPC using .NET 7 Demo", Version = "v1" } );
});

最后,您的 program.cs 應(yīng)該如下所示:

using gRPCUsingNET7Demo.Services;

namespace gRPCUsingNET7Demo
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682

// Add services to the container.
builder.Services.AddGrpc().AddJsonTranscoding();
builder.Services.AddGrpcReflection();
builder.Services.AddGrpcSwagger();

builder.Services.AddSwaggerGen( c=>
{
c.SwaggerDoc("v1",
new Microsoft.OpenApi.Models.OpenApiInfo { Title = "gRPC using .NET 7 Demo", Version = "v1" }

});
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI(c
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "gRPC using .NET7 Demo");
}
);
// Configure the HTTP request pipeline.
app.MapGrpcService<GreeterService>();
app.MapGrpcReflectionService();
app.MapGet("/", () "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
app.Run();
}
}
}

啟動(dòng)應(yīng)用程序后調(diào)用 Swagger 端點(diǎn):
https://localhost:7211/swagger/index.html。

您可以像調(diào)用任何 Restful API 一樣嘗試調(diào)用端點(diǎn)。

在本節(jié)之后,我們將演示如何使用 gRPC 服務(wù)器流將 5M 記錄(大約 600MB 數(shù)據(jù))使用流式傳輸?shù)娇蛻舳恕?/span>

gRPC 服務(wù)器流

在服務(wù)器流中,gRPC 客戶端發(fā)送請(qǐng)求并獲取響應(yīng)流。 客戶端讀取這些響應(yīng),直到所有消息都已傳遞。 gRPC 確保消息排序。

使用此示例 CSV 文件作為示例。

該 CSV 文件包含大約 500 萬(wàn)條銷售記錄,因此不可能在一個(gè)調(diào)用中將它們?nèi)堪l(fā)送出去。

此外,傳統(tǒng)的基于 REST 的分頁(yè)涉及多個(gè)客戶端請(qǐng)求,并且需要在客戶端和服務(wù)器之間來(lái)回通信。

gRPC Server streaming 是解決這個(gè)問(wèn)題的絕佳方案。

  • 客戶端將簡(jiǎn)單地調(diào)用服務(wù)方法。
  • CSV 文件將逐行讀取,轉(zhuǎn)換為原型模型,然后使用 StreamReader 發(fā)送回客戶端。
  • 響應(yīng)流將被發(fā)送到客戶端。

我們將從定義一個(gè)原型文件開始:

Protos-> sales.proto

syntax = "proto3";
import "google/protobuf/timestamp.proto";
csharp_namespace = "gRPCUsingNET7Demo";

package sales;

service SalesService {
rpc GetSalesData(Request) returns (stream SalesDataModel) {}
}

message Request{
string filters=1;

}

message SalesDataModel {
int32 OrderID = 1;
string Region = 2;
string Country = 3;
string ItemType=4;
google.protobuf.Timestamp OrderDate=5;
google.protobuf.Timestamp ShipDate=6;
int32 UnitsSold=7;
float UnitCost=8;
float UnitPrice=9;
int32 TotalRevenue=10;
int32 TotalCost=11;
int32 TotalProfit=12;
}

使用 stream 關(guān)鍵字,我們可以指定 SalesDataModel 將作為流傳遞。

我們的下一步是通過(guò)以下方式添加一個(gè)新服務(wù)——SalesDataService.cs:

using Grpc.Core;
using gRPCUsingNET7Demo;

namespace gRPCUsingNET7Demo.Services
{
public class SalesDataService : SalesService.SalesServiceBase

{
public override async Task
GetSalesData(Request request,
IServerStreamWriter<SalesDataModel> responseStream, ServerCallContext context){

using (var reader = new StreamReader(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data", "sales_records.csv")))
{
string line; bool isFirstLine = true;
while ((line = reader.ReadLine()) != null)
{
var pieces = line.Split(',');

var _model = new SalesDataModel();

try
{
if (isFirstLine)
{
isFirstLine = false;
continue;
}

_model.Region = pieces[0];
_model.Country = pieces[1];

_model.OrderID = int.TryParse(pieces[6], out int _orderID) ? _orderID : 0;
_model.UnitPrice = float.TryParse(pieces[9], out float _unitPrice) ? _unitPrice : 0;

_model.ShipDate = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime
((DateTime.TryParse(pieces[7], out DateTime _dateShip) ? _dateShip : DateTime.MinValue).ToUniversalTime());

_model.UnitsSold = int.TryParse(pieces[8], out int _unitsSold) ? _unitsSold : 0;

_model.UnitCost = float.TryParse(pieces[10], out float _unitCost) ? _unitCost : 0;

_model.TotalRevenue = int.TryParse(pieces[11], out int _totalRevenue) ? _totalRevenue : 0;
_model.TotalCost = int.TryParse(pieces[13], out int _totalCost) ? _totalCost : 0;

await responseStream.WriteAsync(_model);

}

catch (Exception ex)
{
throw new RpcException(new Status(StatusCode.Internal, ex.ToString()));
}

}
}

}

}
}

此服務(wù)實(shí)現(xiàn) SalesServiceBase 類,該類由 .NET7 工具使用 proto 文件自動(dòng)生成。

它只是重寫 GetSalesData 以逐行從文件中讀取數(shù)據(jù)并將其作為流返回。

await responseStream.WriteAsync(_model);

讓我們構(gòu)建項(xiàng)目并運(yùn)行應(yīng)用程序。

應(yīng)用程序按預(yù)期運(yùn)行。 要從服務(wù)器獲取訂單流,我們需要?jiǎng)?chuàng)建一個(gè)單獨(dú)的 RPC 客戶端,這將在下一節(jié)中介紹。

使用 .NET7 創(chuàng)建 gRPC 客戶端

讓我們?cè)谀慕鉀Q方案中創(chuàng)建一個(gè)新的控制臺(tái)應(yīng)用程序,并向其中添加以下包

<PackageReference Include="Google.Protobuf" Version="3.21.9" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.49.0" />
<PackageReference Include="Grpc.Tools" Version="2.40.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
  1. 確保添加了 Protos 文件夾并將 sales.proto 文件復(fù)制到那里。
  2. 為了為客戶端生成 gRPC 類,您需要修改 .csproj 文件。
<ItemGroup>
<Protobuf Include="Protos\sales.proto" GrpcServices="Client" />
</ItemGroup>

3. 保存并構(gòu)建項(xiàng)目(以便生成客戶端代碼)

4. 第一步是打開 Program.cs 并為您的 gRPC 服務(wù)創(chuàng)建一個(gè)通道。

var channel = GrpcChannel.ForAddress("https://localhost:7211");

5. 創(chuàng)建一個(gè)新的SalesService對(duì)象(使用gRPC工具創(chuàng)建)如下:

var client = new SalesService.SalesServiceClient(channel);

6.服務(wù)方法應(yīng)按如下方式調(diào)用:

using var call = client.GetSalesData(new Request { Filters = "" });

7. 我們的代碼只是調(diào)用服務(wù)器上的 ReadAllAsync 來(lái)檢索流,然后在收到流后立即在控制臺(tái)上打印輸出。

await foreach (var each in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine(String.Format("New Order Receieved from {0}-{1},Order ID = {2}, Unit Price ={3}, Ship Date={4}", each.Country, each.Region, each.OrderID, each.UnitPrice,each.ShipDate));
Count++;
}

這就是完整實(shí)現(xiàn):

using Grpc.Core;
using Grpc.Net.Client;
using gRPCUsingNET7Demo;

namespace gRPCClient
{
internal class Program
{
static async Task Main(string[] args)
{
var channel = GrpcChannel.ForAddress("https://localhost:7211");
int Count = 0;
var watch = System.Diagnostics.Stopwatch.StartNew();
try
{
var client = new SalesService.SalesServiceClient(channel);

using var call = client.GetSalesData(new Request { Filters = "" }
, deadline: DateTime.UtcNow.AddMinutes(10)
);

await foreach (var each in call.ResponseStream.ReadAllAsync())
{

Console.WriteLine(String.Format("New Order Receieved from {0}-{1},Order ID = {2}, Unit Price ={3}, Ship Date={4}", each.Country, each.Region, each.OrderID, each.UnitPrice, each.ShipDate));
Count++;

}
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
{
Console.WriteLine("Service timeout.");
}

watch.Stop();

Console.WriteLine($"Stream ended: Total Records:{Count.ToString()} in {watch.Elapsed.TotalMinutes} minutes and {watch.Elapsed.TotalSeconds});
Console.Read();

}
}
}

正如您在上面的示例中看到的,服務(wù)方法調(diào)用是在deadline的幫助下完成的。 您可以使用deadline指定通話的持續(xù)時(shí)間,這樣您就可以指定通話應(yīng)該持續(xù)多長(zhǎng)時(shí)間。

using var call = client.GetSalesData(new Request { Filters = "" }
, deadline: DateTime.UtcNow.AddMinutes(10)
);

客戶端現(xiàn)在允許您查看來(lái)自 gRPC 服務(wù)的傳入消息。

結(jié)論:

本文的目的是提供有關(guān)已添加到 gRPC .NET 7 框架的性能增強(qiáng)的信息,包括 gRPC JSON 轉(zhuǎn)碼功能、OpenAPI 規(guī)范和服務(wù)器反射功能,以及新的性能改進(jìn)。 本文還解釋了如何使用 gRPC 服務(wù)器流式處理來(lái)創(chuàng)建能夠立即處理和交付數(shù)百萬(wàn)條記錄的高性能服務(wù)。

責(zé)任編輯:姜華 來(lái)源: 今日頭條
相關(guān)推薦

2025-02-04 13:53:18

NixGogRPC

2011-02-13 09:17:02

ASP.NET

2011-02-23 09:49:40

ASP.NET

2018-12-03 08:00:00

微服務(wù)gRPC

2011-02-15 09:31:56

ASP.NET

2011-02-16 09:08:27

ASP.NET

2025-03-04 08:00:00

機(jī)器學(xué)習(xí)Rust開發(fā)

2022-12-08 08:00:00

.NET?7BitArray數(shù)據(jù)執(zhí)行

2022-06-07 08:19:30

gRPCBallerina微服務(wù)

2011-02-13 09:37:55

ASP.NET

2016-05-20 14:20:31

ASP.NET建議

2011-02-22 09:16:24

高性能ASP.NET

2023-12-26 00:58:53

Web應(yīng)用Go語(yǔ)言

2017-05-25 10:50:16

Python高性能服務(wù)

2011-02-14 09:32:16

ASP.NET

2011-02-17 09:13:57

ASP.NET

2023-12-01 07:06:14

Go命令行性能

2023-12-14 08:01:08

事件管理器Go

2011-04-13 13:49:50

ASP.NET網(wǎng)站優(yōu)化

2021-08-03 15:33:05

gRPC服務(wù)器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)