gRPC ve .NET 6 kullanarak Yüksek Performanslı Mikro Hizmetler oluşturma

Paylaş :

5 Milyon kaydı bir esinti içinde teslim göndermek için sunucu akışından nasıl yararlanacağımızı öğrenelim.

gRPC ve .NET 6 kullanarak Yüksek Performanslı Mikro Hizmetler oluşturma

Microsoft, .NET Core'un uzun vadeli kararlı sürümü ve birçok yeni API, performans ve dil geliştirmesiyle şimdiye kadarki en hızlı .NET sürümünü kullanıma sundu: .NET 6. Bu makalede, gRPC Hizmetleri için .NET 6'daki yenilikleri (çarpıcı hızlı performans ve serileştirme, daha iyi hata toleransı, istemci tarafı yük dengeleme ve HTTP/3 desteği) göreceğiz.

Ayrıca, gRPC ve .NET 6 kullanarak yüksek performanslı mikro hizmet oluşturmak için bu özelliklerden nasıl yararlanabileceğimizi öğreneceğiz. Ayrıca, beş milyon kaydı işlemek ve teslim etmek için sunucu akışını kullanan bir gerçek dünya gRPC hizmeti oluşturacağız.

Not : .NET'teki gRPC Hizmetlerinde yeniyseniz, önce bazı temel kavramları açıklayan ve ayrıca gRPC'nin WCF ve REST gibi diğer teknolojilerle nasıl karşılaştırıldığını açıklayan bu makaleyi incelemeniz önerilir.

Kısa Bir Özet:

  • gRPC, CNCF tarafından yürütülen popüler bir açık kaynaklı RPC Çerçevesidir .
  • Bu, ilk dilden bağımsız bir sözleşme platformudur - bu, basitçe, istemci ve sunucunun, mesajların ne ve nasıl iletileceği konusunda bir sözleşme üzerinde anlaşmaları gerektiği anlamına gelir. Sözleşme, .NET6'nın sağladığı araçlar aracılığıyla kod oluşturma sürecini türeten bir .proto dosyasında tanımlanır.
  • Çapraz platform olduğundan hem istemci hem de sunucu farklı bir teknoloji yığını kullanabilir.
  • HTTP/2 kullanır ve Protobuf'u (mesajlar için yüksek performanslı bir serileştirme teknolojisi) gönderir. Verileri insan tarafından okunabilir metin biçiminde depolayan JSON'dan farklı olarak, Protobuf ikili değişim biçimini kullanır ve insan tarafından okunamaz ve bu nedenle kesinlikle yazılan istemci ve temel sınıflar oluşturmak için araçlara ihtiyaç duyar. Neyse ki .NET ile herhangi bir NuGet paketine başvurmak kadar kolay.
  • HTTP/2, tek bir bağlantı üzerinde aynı anda birden çok istek gönderdiğiniz yerde çoğullamaya olanak tanır.
  • Aynı zamanda, bir sunucunun istemciye birden çok yanıt gönderebildiği ve bunun tersinin de yapılabildiği akışı destekler.
  • Çift Yönlü Akış - hem istemcinin hem de sunucunun birden çok mesajı ileri geri gönderdiği yer.
gRPC Üst Düzey Genel Bakış (Yazarın Resmi)

.NET 6'da ilk gRPC Hizmetinizi oluşturma

Daha fazla veda etmeden, .NET 6'da ilk gRPC hizmetini oluşturalım.

Önkoşullar:

  • Visual Studio 2022
  • .NET 6 SDK'sı .
  1. Yeni bir proje oluşturun ve 'ASP.NET Core gRPC' Hizmet şablonunu seçin
gRPC Proje Şablonu (Yazara Göre Resim)

Hedef çerçeve olarak .NET 6.0'ın seçildiğinden emin olun

  • Bu hizmeti bir kapsayıcıda çalıştırmak istiyorsanız Docker Desteği etkinleştirilebilir.

Oluştur'a tıklamak, aşağıda gösterildiği gibi varsayılan karşılama hizmetiyle projeyi oluşturacaktır.

greet.proto dosyasını inceleyelim

syntax = “proto3”;option csharp_namespace = “myGRPCService”;package greet;// The greeting service definition.service Greeter {// Sends a greetingrpc 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;}

Bu, hizmetin nasıl görüneceğini ve tükettiği mesajları tanımlayan dilden bağımsız bir sözleşmedir.

Şimdi bu sözleşmenin uygulaması olan GreeterService.cs dosyasına bakalım (.proto dosyası)

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});}}

Bu hizmet sadece SayHello eylemini uygular ve HelloReply mesajını istemciye geri döndürür. GreeterBase, HelloReply, HelloRequest türlerinin henüz tanımlanmadığını fark edebilirsiniz. GreeterBase tipinin tanımını kontrol ederseniz, bu tiplerin .NET6 araçları tarafından otomatik olarak oluşturulduğunu göreceksiniz.

Otomatik Oluşturulan Türler

Kod oluşturma nasıl çalışır?

.csproj dosyasını düzenlerseniz, greet.proto'yu referans alan protobuf öğesini ve sunucu kodu oluşturmayı gösteren bir özelliği fark edeceksiniz.

Hizmeti çalıştırmak için komut istemini açın ve 'dotnet run' yazın.

Hizmetin https://localhost:7057 üzerinde çalıştığını fark edebilirsiniz . Şimdi, REST API'lerinden farklı olarak, bunu bir tarayıcıda test edemiyoruz ve bir istemci oluşturmamız gerekiyor.

Hizmeti kullanmak için yeni bir konsol uygulaması oluşturun. Bağımlılıklara sağ tıklayın ve 'Bağlı Hizmeti Yönet'i seçin

Şimdi bir hizmet referansı ekleyeceğiz - gRPC'yi seçin.

Proto dosyanızı bulun ve gRPC hizmetini kullanmak için gerekli kodu oluşturacak 'İstemci' türünü seçin.

'Yalnızca istemci' saplama sınıfları oluşturmak için greet.proto dosyasının özelliklerini doğruladığınızdan emin olun.

Şimdi program.cs dosyasını açın ve gRPC Servisinizi çağırmak için aşağıdaki satırları ekleyin.

var channel = GrpcChannel.ForAddress(“https://localhost:7057");var client = new Greeter.GreeterClient(channel);var reply = await client.SayHelloAsync(       new gRPCDemo.HelloRequest { Name = “gRPC Demo” });Console.WriteLine(“from server: “ + reply);

Yukarıdaki kod, daha sonra SayHello yöntemini çağıran bir karşılama istemcisine iletilen yeni bir gRPC kanalı oluşturur.

Hizmetten gelen yanıtı görmek için projeyi çalıştırma.

.NET 6'da ilk gRPC hizmetimizi oluşturduğumuza göre, şimdi .NET 6'daki bazı yeni özellikleri ve gelişmiş kavramları keşfedelim.

.NET 6'daki gRPC'deki yenilikler

  1. Performans geliştirmeleri:
  • NET 6'da, protobuf için ASCII dize serileştirmesi, yüksek performanslı CPU yönergelerini kullanarak karakterleri paralel olarak işlemek için SIMD — Tek Yönergeyi kullanır ve %20 performans genel iyileştirme sağlar.
  • gRPC, ByteString kullanarak ham bayt gönderir ve alır. Yeni .NET 6, bir kopya oluşturma/dahili bir dizi ayırma ihtiyacını ortadan kaldıran Sıfır Kopya ByteString API'sini sunar. Gereksiz tahsisi önlemek için büyük mesajlar gönderiyor ve alıyorsanız, bu gerçekten yararlıdır.
Yazara göre resim
  • .NET 5'te HTTP/2 kitaplığı, gecikme olduğunda indirme hızını sınırlayan sabit arabellek boyutu kullanıyordu. Bu sorun, indirme performansını %118 artıran dinamik arabellek boyutunu kullanmak üzere HTTP/2 kitaplığı güncellenerek .NET 6'da çözüldü .

2. Geçici Arıza İşleme

Artık RPC İstisnalarını yakalayabilir, geçici hataları (ağ bağlantısı kaybı veya zaman aşımları gibi) tespit edebilir ve artık bir kanalda yapılandırılabilen otomatik yeniden denemeler için yerleşik mantığı kullanabilirsiniz.

İpucu: gRPC yeniden denemeleriyle geçici hata işleme hakkında daha fazla bilgi edinmek istiyorsanız bu bağlantıyı kontrol edin.

3. İstemci Tarafı Yük Dengeleme

İstemci tarafı yük dengeleme ile gRPC istemcilerinizin yükü sunucularınız arasında en iyi şekilde dağıtmasını sağlayabilirsiniz. Yük dengeleme için bir proxy'ye sahip olma ihtiyacını ortadan kaldırır. Yeni bir kanal oluştururken istemci tarafı yük dengelemeyi yapılandırabiliriz. İki bileşenden oluşur:

  • Hizmet Keşfi: Çözümleyici görevi görür ve gRPC'nin barındırıldığı sunucunun IP'lerini almak için bir DNS sorgusu yapar.
  • PickFirst ve RoundRobin mantığı gibi çeşitli konfigürasyonları kullanarak bir bağlantı oluşturan ve adresi seçen yük dengeleyici.
İstemci Tarafı Yük Dengeleme (Yazara Göre Resim)

Uç noktalardan herhangi biri başarısız olursa, yük dengeleyici otomatik olarak diğer sağlıklı uç noktalara geçecektir.

gRPC — İstemci Tarafı Yük Dengeleme (Yazara Göre Resim)

3. HTTP/3 Desteği

.NET 6, uçtan uca HTTP/3'ü destekleyen ilk gRPC uygulamasıdır.

HTTP/2, birden fazla akışa olanak tanır ve aynı bağlantı üzerinden aynı anda birden fazla isteğin işlenmesini sağlamak için çerçeveleme kullanır, ancak dünya, Wi-Fi ve hücresel bağlantıları kullanarak mobil hale geldi ve bu bazen güvenilmez olabilir ve bu durumlarda , TCP paketi kaybolacak ve tüm akışlar engellenecek - hat başı engelleme sorunu. HTTP/3 Bunu, UDP kullanan ve yerleşik TLS'ye sahip olan QUIC adlı yeni bir bağlantı protokolü kullanarak çözer, bu nedenle bağlantı kurmak daha hızlıdır ve IP adresinden bağımsızdır, böylece mobil istemciler wifi ve hücresel ağlar arasında geçiş yapabilir; aynı mantıksal bağlantı.

.NET 6, QUIC'yi destekler ve bunun açık kaynaklı bir uygulamasına sahiptir — MSQUIC ve aşağıdaki faydaları sağlar:

  • İlk isteğin daha hızlı yanıt süresi
  • bağlantı paketi kaybı olduğunda gelişmiş deneyim
  • ağlar arasında geçişi destekler

Not: HTTP/3 için RFC henüz kesinleştirilmemiştir ve değişebilir, bu nedenle NET6'da bir önizleme özelliğidir.

Bir sonraki bölümde, istemciye 5 milyon kayıt sağlayan yüksek performanslı Microservice oluşturmak için gRPC sunucu akışından nasıl yararlanılacağını öğreneceğiz.

Karşılama hizmetimize geri dönersek , müşterinin bir istek mesajı göndermesiyle başlayan ve hizmetin işlenmesi bittiğinde bir yanıt mesajı döndürülen tekli aramayı kullandığını fark edeceksiniz.

service Greeter {rpc SayHello (HelloRequest) returns (HelloReply);}

gRPC Sunucu Akışı

Sunucu akışı, gRPC istemcisinin hizmete bir istek göndermesini sağlar ve bir yanıt akışı alır. İstemci, başka ileti kalmayana kadar döndürülen akıştan okur. gRPC, garanti edilen mesaj sıralamasıyla ilgilenir.

Örneğimizde, 5 milyon satış kaydı içeren 196MB'lık bu örnek CSV dosyasını kullanacağız. Şimdi, bu kayıtları teslim etmek tek bir aramada verimli olmayacak. Ayrıca, geleneksel dinlenme tarzı çağrı, birden çok istemci isteği gerektirir - istemciden sunucuya ileri geri iletişim.

gRPC Sunucu akışı, bu sorunu verimli bir şekilde çözer.

  • İstemci yalnızca hizmet yöntemini çağıracaktır.
  • gRPC hizmetimiz, StreamReader kullanarak satır satır CSV dosyasından okuyacak, satırları gRPC'nin anladığı modele dönüştürecek ve her seferinde bir satır olmak üzere kaydı istemciye geri gönderecektir.
  • Müşteri, yanıt akışını alacaktır.

Aşağıdaki alanlara sahip mesajları iletmek için bir proto dosyası tanımlayarak başlayalım.

Örnek Satış Verileri (CSV) Alanları

Protos-> sales.proto

syntax = “proto3”;import “google/protobuf/timestamp.proto”;option csharp_namespace = “gRPCDemoUsingNET6.Protos”;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;}

akış anahtar sözcüğü, SalesDataModel'in bir akış olarak teslim edileceğini belirtir

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

Yeni bir servis ekleyelim - SalesaDataService.cs aşağıdaki gibi

public class SalesDataService : Protos.SalesService.SalesServiceBase{public override async TaskGetSalesData(Protos.Request request,IServerStreamWriter<Protos.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 Protos.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()));}}}}}

Daha iyi anlamak için parçalayalım.

public class SalesDataService : Protos.SalesService.SalesServiceBase

Sınıf, proto dosya yapılandırması kullanılarak .NET6 araçları tarafından otomatik olarak oluşturulan SalesServiceBase'i uygular.

public override async TaskGetSalesData(Protos.Request request,IServerStreamWriter<Protos.SalesDataModel> responseStream, ServerCallContext context){} 

Ardından, dosyadaki verileri okumak için GetSalesData yöntemini geçersiz kılıyoruz ve veri modelimizi aşağıda gösterildiği gibi answerStream nesnesine yazarak bir akış olarak döndürüyoruz :

await responseStream.WriteAsync(_model);

Şimdi onu çalışırken görmek için servisi çalıştıralım.

gRPC Hizmeti (Yazara Göre Resim)

Müşterimizi oluşturabilmemiz için Hizmet çalışıyor ve çalışıyor.

Yeni bir konsol projesi oluşturun, protobuf kullanarak gRPC hizmet referansı ekleyin (daha önce paylaşıldığı gibi)

Müşteri uygulamamız böyle görünüyor

//Create a channel for your gRPC Service
var channel = GrpcChannel.ForAddress("https://localhost:7143");
//Create SalesService Client to open a connection
var client = new SalesService.SalesServiceClient(channel);
//Invoke the method using var call = client.GetSalesData(new Request { Filters = "" });int Count = 0;//Get response stream
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++;}
Console.WriteLine("Stream ended: Total Records: "+Count.ToString());
Console.Read();

İstemciyi çalıştıralım ve gRPC Hizmetinizden gelen ileti akışını görebilirsiniz.

gRPC İstemci Çıktısı — 5 milyon kayıt (Yazara Göre Resim)

Tüm 5 milyon satış kayıtlarının gRPC hizmetinden yüklenmesi yaklaşık 2 dakika sürer. Ancak, bazı durumlarda, bir aramanın ne kadar süreceğini belirtmek isteyebilirsiniz. Bunu yapmak için son tarihi yapılandırmanız gerekir .

using var call = client.GetSalesData(new Request { Filters = “” }, deadline: DateTime.UtcNow.AddSeconds(5));

bu nedenle, bu süre aşıldığında, istemci akışı işlemeyi durduracak ve aşağıdaki gibi ele alınabilecek bir istisna atacaktır:

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

Aşağıdaki örnek, 5 saniyelik bir üst sınır kullanır, bu nedenle yalnızca 77829 kaydı işlemiştir.

gRPC İstemci Çıktısı — Son Tarihli (Yazara Göre Resim)

Hesabınızı yönetmek için giriş yapın

veya