gRPC Nedir, gRPC Neden Kullanılır

Dağıtık sistemlerde ve mikroservis mimarilerinde servisler arası iletişim (Inter-process Communication – IPC), genel sistem performansını ve ölçeklenebilirliğini doğrudan etkileyen en kritik katmandır. Geleneksel REST mimarisi ve JSON serileştirme yöntemleri, HTTP/1.1’in kısıtlamaları ve metin tabanlı veri aktarımının getirdiği işlemci/bant genişliği maliyetleri nedeniyle yüksek verimli sistemlerde yetersiz kalmaktadır.

Google tarafından iç sistemlerinde (Stubby) kullanılmak üzere tasarlanan ve sonrasında Cloud Native Computing Foundation (CNCF) standartlarına bağlanan gRPC, bu iletişim darboğazını çözmek için geliştirilmiş yüksek performanslı, açık kaynaklı bir Remote Procedure Call (RPC) framework’üdür.


1. Mimari Temeller: gRPC Neden Farklıdır?

gRPC, veriyi iletmek ve işlemek için iki temel teknolojiyi standart olarak kabul eder: Veri serileştirme için Protocol Buffers (Protobuf) ve taşıma katmanı için HTTP/2.

A. Taşıma Katmanı: HTTP/2 Entegrasyonu

gRPC, HTTP/1.1’in getirdiği “Head-of-Line Blocking” (bir isteğin diğerini beklemesi) sorununu HTTP/2’nin yerleşik özellikleriyle çözer:

  • Binary Framing Layer: HTTP/1.1’de metin olarak iletilen başlık (header) ve gövde (body) bilgileri, HTTP/2’de ikili (binary) çerçevelere bölünür. gRPC bu katmanı kullanarak veriyi ağ üzerinde en hızlı işlenecek formatta taşır.
  • Tam Çift Yönlü Çoğullama (Full-Duplex Multiplexing): Tek bir TCP bağlantısı (connection) açılır. Bu bağlantı üzerinden sanal “Stream”ler oluşturulur. İstemci ve sunucu, aynı TCP bağlantısı üzerinden yüzlerce asenkron isteği birbirini bloklamadan anında iletebilir.
  • HPACK Başlık Sıkıştırma: Her istekte tekrarlanan HTTP başlıkları (örneğin Authorization token’ları) HPACK algoritması ile sıkıştırılır ve sadece değişen (delta) kısımlar ağ üzerinden gönderilir.

B. Serileştirme Katmanı: Protocol Buffers (Protobuf)

JSON “self-describing” (kendi kendini tanımlayan) bir formattır; veri ile birlikte anahtar kelimeler de (örneğin "KullaniciAdi": "Mehmet") sürekli ağ üzerinden gider. Protobuf ise veriyi en küçük ikili formata indirger:

  • Interface Definition Language (IDL): İletişim kuracak servislerin sözleşmesi (contract) bir .proto dosyasında tanımlanır. Bu dosya, tüm mikroservisler için “tek doğru kaynak” (Single Source of Truth) görevi görür.
  • Field Tags (Alan Etiketleri): Protobuf’ta değişken isimleri ağ üzerinden gönderilmez. Bunun yerine .proto dosyasında tanımlanan 1, 2, 3 gibi etiket numaraları kullanılır. string name = 1; tanımında sadece 1 etiketi ve taşıdığı değer iletilir.
  • Varint Kodlaması: Sayısal değerler için standart 4-byte ayrılması yerine, değerin büyüklüğüne göre 1 ile 10 byte arasında dinamik alan tahsis edilir. Bu da ağ üzerindeki payload boyutunu JSON’a kıyasla %70-80 oranında küçültür.

2. İletişim Modelleri (Core Streaming Patterns)

gRPC, HTTP/2’nin çoğullama yeteneğini kullanarak dört farklı iletişim modeli sunar. Sistem mimarisine göre doğru modelin seçilmesi zorunludur.

1. Unary RPC (Tekil Çağrı)

REST mimarisindeki standart İstek-Cevap (Request-Response) döngüsünün karşılığıdır.

  • Çalışma Mekanizması: İstemci, parametreleri içeren tek bir istek gönderir. Sunucu isteği işler ve tek bir cevap paketi ile birlikte durum kodunu (status code) ve varsa hata detaylarını (trailing metadata) döner.
  • Kullanım Alanı: Veritabanından tekil kayıt okuma, standart form kayıtları, state değiştiren basit komutlar (Commands).

2. Server Streaming RPC (Sunucu Akışı)

İstemci sunucuya tek bir istek gönderir; sunucu ise bu isteğe karşılık bir mesaj dizisi (stream) oluşturarak veriyi parça parça gönderir.

  • Çalışma Mekanizması: Sunucu, istemciye verileri iletirken HTTP/2 stream’ini açık tutar. Tüm veriler gönderildikten sonra sunucu, stream’i sonlandırdığını belirten bir sinyal (half-close) yollar. İstemci bu süreçte gelen her parçayı anında (on-the-fly) işleyebilir.
  • Kullanım Alanı: Büyük veri setlerinin (örneğin milyonlarca satırlık raporların) tek seferde belleğe yüklenmeden istemciye aktarılması, canlı log izleme sistemleri, anlık fiyat değişikliklerinin (ticker) yayınlanması.

3. Client Streaming RPC (İstemci Akışı)

İstemci, sunucuya göndereceği veriyi parçalara (chunk) böler ve açık bir stream üzerinden peş peşe gönderir. Sunucu, veri aktarımı bitene kadar bekler veya veriyi geldikçe asenkron olarak işler.

  • Çalışma Mekanizması: İstemci, verinin bittiğini bildirdiğinde sunucu işlemleri tamamlar ve tek bir onay (veya özet) cevabı döner.
  • Kullanım Alanı: Büyük medya dosyalarının (video, yüksek çözünürlüklü imaj) yüklenmesi, IoT sensörlerinin topladığı telemetri verilerinin sunucuya yığın (batch) olarak aktarılması.

4. Bidirectional Streaming RPC (Çift Yönlü Akış)

Hem istemcinin hem de sunucunun, bağımsız ve asenkron olarak aynı TCP bağlantısı üzerinden birbirlerine mesaj dizileri gönderdiği, gRPC’nin en gelişmiş modelidir.

  • Çalışma Mekanizması: İki tarafın da okuma ve yazma stream’leri birbirinden tamamen bağımsızdır. Sunucu, istemciden tüm verilerin gelmesini beklemeden cevap üretmeye başlayabilir. Tam dubleks (full-duplex) iletişim sağlanır.
  • Kullanım Alanı: Gerçek zamanlı multiplayer oyun senaryoları, VoIP veya anlık mesajlaşma (Chat) altyapıları, istemciden gelen ses verisini anında analiz edip text olarak geri dönen yapay zeka (Speech-to-Text) servisleri.

3. İleri Seviye Mühendislik Konseptleri

Dağıtık sistemleri production ortamında ayakta tutmak için gRPC’nin sağladığı gelişmiş mekanizmaların kullanılması şarttır.

A. Deadlines ve Cancellation Propagation (İptal Yayılımı)

Mikroservis mimarilerinde bir isteğin servisler arasında dolaşırken sonsuza kadar asılı kalmasını (Resource exhaustion) önlemek için Deadline kullanılır.

  • İstemci, isteğin maksimum ne kadar süreceğini (örneğin 2 saniye) belirler.
  • Eğer İstek Servis A’ya, oradan Servis B’ye, oradan Servis C’ye gidiyorsa, bu 2 saniyelik deadline bilgisi gRPC metadata’sı üzerinden tüm zincire aktarılır (Propagation).
  • Süre dolduğu anda gRPC, zincirdeki tüm işlemleri eşzamanlı olarak iptal eder (CancellationToken tetiklenir) ve gereksiz veritabanı sorguları veya CPU döngüleri anında kesilir.

B. Interceptors (Ara Katman Denetimi)

gRPC çağrılarının arasına girerek metotlar çalışmadan önce veya sonra merkezi işlemler yapılmasını sağlayan mekanizmadır.

  • Kullanım Senaryoları: Authentication (her istekte gönderilen JWT token’ın doğrulanması), global exception handling (hata yönetimi), metrik toplama (işlem sürelerinin Prometheus vb. araçlara yazılması) ve yapısal loglama.

C. Load Balancing (Yük Dengeleme) Stratejileri

gRPC, HTTP/2 kullandığı için bağlantılar uzun ömürlüdür (Persistent Connection). Klasik Layer 4 (TCP) Load Balancer’lar, bağlantıyı ilk kurulan pod/sunucu üzerinde tutacağı için yük dağıtılamaz.

  • Proxy Load Balancing: Envoy, Nginx veya Istio gibi Layer 7 (Application Layer) load balancer’lar kullanılarak, HTTP/2 frame’leri (paketleri) bazında yük dağıtımı yapılmalıdır.
  • Client-side Load Balancing: İstemcinin, xDS API veya Consul gibi bir Service Discovery mekanizmasından güncel sunucu IP’lerini alıp, yük dengelemesini kendi içinde (Round-robin vb.) yapmasıdır.

D. Hata Yönetimi (Error Handling)

gRPC, HTTP’nin 200, 404, 500 gibi standart durum kodlarını kullanmaz. Kendi içinde 16 adet standart durum kodu barındırır (örneğin: OK, INVALID_ARGUMENT, NOT_FOUND, DEADLINE_EXCEEDED, UNAUTHENTICATED). Hatalar, platforma özgü exception’lar (örneğin .NET için RpcException) olarak fırlatılmalı ve ele alınmalıdır.


4. .NET Core Implementasyon Örneği

Sözleşme (.proto) tanımı:

syntax = "proto3";
option csharp_namespace = "Ficommerce.Payment.Grpc";
package payment;

service PaymentGateway {
  // Bidirectional streaming örneği
  rpc ProcessTransactions (stream TransactionRequest) returns (stream TransactionResult);
}

message TransactionRequest {
  string card_bin = 1;
  double amount = 2;
  int32 installments = 3;
}

message TransactionResult {
  string transaction_id = 1;
  bool is_successful = 2;
  string error_message = 3;
}

.NET Core Sunucu (Server) Tarafı:

C#

public class PaymentService : PaymentGateway.PaymentGatewayBase
{
    public override async Task ProcessTransactions(
        IAsyncStreamReader<TransactionRequest> requestStream,
        IServerStreamWriter<TransactionResult> responseStream,
        ServerCallContext context)
    {
        // İstemciden gelen stream okundukça işlem yapılır
        await foreach (var request in requestStream.ReadAllAsync(context.CancellationToken))
        {
            // İş mantığı (Örn: BIN kontrolü, sanal pos entegrasyonu)
            var isSuccess = ValidateBin(request.CardBin);
            
            var result = new TransactionResult
            {
                TransactionId = Guid.NewGuid().ToString(),
                IsSuccessful = isSuccess,
                ErrorMessage = isSuccess ? string.Empty : "Geçersiz BIN"
            };

            // Sonuç anında istemciye stream üzerinden geri dönülür
            await responseStream.WriteAsync(result);
        }
    }
}

5. İstemci (Client) Entegrasyonu ve Protobuf Tiplerinin Ayrışması

Burada yapılan en büyük amatörlük, aynı .proto dosyasını hem sunucu hem de istemci projesine kopyalayıp geçmektir. .proto dosyası bir sözleşmedir (contract), ancak bu sözleşmeden üretilecek C# sınıfları projenin rolüne göre farklı olmalıdır.

Sunucu tarafında bu sözleşmeden bir Base sınıf (PaymentGatewayBase) üretilirken, istemci tarafında bir Client sınıfı (PaymentGatewayClient) üretilmelidir. Bu ayrım .csproj dosyalarındaki GrpcServices parametresi ile yönetilir.

Sunucu (Server) Projesi .csproj Ayarı:

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

İstemci (Client) Projesi .csproj Ayarı:

<ItemGroup>
  <Protobuf Include="Protos\payment.proto" GrpcServices="Client" />
</ItemGroup>

(Not: Büyük mimarilerde bu .proto dosyaları genellikle ortak bir NuGet paketi veya Shared Class Library üzerinden dağıtılır.)

İstemci (Client) Tarafı: Bidirectional Stream Kodlaması

İstemci tarafında gRPC bağlantısı kurarken her defasında yeni bir kanal açmak ciddi bir performans katilidir. Bu yüzden .NET Core’un sunduğu Grpc.Net.ClientFactory kullanılarak, istemci Dependency Injection (DI) konteynerine eklenmelidir.

Program.cs (Client Yapılandırması):

// İstemciyi DI container'a ekliyor ve bağlantı havuzu (connection pooling) oluşturuyoruz
builder.Services.AddGrpcClient<PaymentGateway.PaymentGatewayClient>(options =>
{
    options.Address = new Uri("https://payment-service.ficommerce.internal:5001");
});

Stream İşlemini Tüketen Worker Sınıfı: Sunucudaki ProcessTransactions metoduna bağlanıp, hem veri gönderen hem de eşzamanlı olarak sunucudan gelen cevapları okuyan asenkron yapı şu şekilde kurulur:

public class PaymentWorker
{
    private readonly PaymentGateway.PaymentGatewayClient _client;

    public PaymentWorker(PaymentGateway.PaymentGatewayClient client)
    {
        _client = client;
    }

    public async Task RunStreamAsync(CancellationToken cancellationToken)
    {
        // 1. Çift yönlü akışı başlat
        using var call = _client.ProcessTransactions(cancellationToken: cancellationToken);

        // 2. OKUMA GÖREVİ (Background Task): Sunucudan gelen cevapları dinle
        // Bu işlem ayrı bir Task'ta çalışmalı ki yazma işlemi bloklanmasın.
        var readTask = Task.Run(async () =>
        {
            await foreach (var response in call.ResponseStream.ReadAllAsync(cancellationToken))
            {
                if (response.IsSuccessful)
                    Console.WriteLine($"[BAŞARILI] İşlem Onaylandı: {response.TransactionId}");
                else
                    Console.WriteLine($"[RED] Hata: {response.ErrorMessage}");
            }
        }, cancellationToken);

        // 3. YAZMA GÖREVİ: Sunucuya veri akıt
        var dummyBins = new[] { "454671", "554902", "invalid_bin", "540061" };
        
        foreach (var bin in dummyBins)
        {
            await call.RequestStream.WriteAsync(new TransactionRequest
            {
                CardBin = bin,
                Amount = 1500.50,
                Installments = 3
            }, cancellationToken);
            
            // Simüle edilmiş gecikme (Örn: RabbitMQ'dan mesaj okuma süresi)
            await Task.Delay(50, cancellationToken); 
        }

        // 4. İLETİŞİMİ SONLANDIR: Sunucuya "Gönderecek başka verim kalmadı" sinyali (half-close) ilet.
        // Bu tetiklenmezse sunucu sonsuza kadar veri bekler!
        await call.RequestStream.CompleteAsync();

        // 5. Sunucunun elindeki tüm cevapları bitirmesini bekle
        await readTask;
    }
}

Sonuç ve Kapanış

gRPC, mikroservis dünyasında “East-West Traffic” (servisler arası iç trafik) için tartışmasız endüstri standardıdır. REST’in metin tabanlı serileştirme yükünden ve HTTP/1.1’in bağlantı darboğazlarından kurtulup, donanımın sınırlarını zorlayan sistemler inşa etmek istiyorsanız, doğru konfigüre edilmiş bir gRPC mimarisi sisteminizin sinir ağını oluşturacaktır. Sadece, Bidirectional Stream gibi güçlü araçları kullanırken thread yönetimini ve CancellationToken aktarımlarını doğru yaptığınızdan emin olun.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir