Modern Mikro Hizmetler Neden REST Yerine GRPC'yi Tercih Ediyor?

Modern Mikro Hizmetler Neden REST Yerine GRPC'yi Tercih Ediyor?

Monolitik bir mimaride bileşenler, anlık ve son derece güvenilir olan bellek içi yöntem çağrıları aracılığıyla iletişim kurar. Ancak mikro hizmet mimarisine geçerken bu bileşenler ağ sınırlarıyla ayrılır. İletişim, işlem dışı bir ağ çağrısına (İşlemler Arası İletişim veya IPC) dönüşür.

Yıllardır, JSON yükleriyle HTTP/1.1 üzerinden REST (Temsili Durum Aktarımı), web API’leri oluşturmak için varsayılan standart olmuştur. REST, halka açık web hizmetleri ve istemciler arası etkileşim için mükemmel olsa da, yüksek frekanslı, düşük gecikmeli, dahili hizmetten hizmete iletişim için kullanıldığında önemli darboğazlar ortaya çıkarır.

Modern mikro hizmetlerin hızla gRPC’ye (Google Uzaktan Prosedür Çağrısı) doğru kaymasının nedeni budur. Bu makalede, REST’in sınırlamalarını analiz edeceğiz, gRPC’nin mimari temellerini inceleyeceğiz ve Java‘da bir gRPC hizmetinin eksiksiz bir şekilde uygulanmasının üzerinden geçeceğiz.


1. Mikro Hizmetlerde REST’in Darboğazları

REST yirmi yılı aşkın bir süredir web’e güç veriyor. Ancak temel teknolojileri dahili dağıtılmış mimariler için optimize edilmemiştir:

A. Düz Metin JSON’un Ek Yükü

JSON insan tarafından okunabilir, bu da hata ayıklamayı kolaylaştırır, ancak makineler arası iletişim için son derece verimsizdir:

  • Serileştirme/Serileştirmeden Çıkarma Maliyeti: Metin dizesi belirteçlerini ayrıştırmak ve bunları nesnelere dönüştürmek önemli miktarda CPU döngüsü tüketir.
  • Büyük Yük Boyutu: JSON anahtarları her istekte tekrarlanır (ör. {"transactionId": "123", "amount": 99.99}). Saniyede milyonlarca isteği işleyen sistemler için bu yedekli meta veriler, çok büyük ağ bant genişliğini boşa harcar.

B. HTTP/1.1 Bağlantı Sınırlamaları

REST genellikle HTTP/1.1 üzerinde çalışır ve bu da çeşitli yapısal verimsizlikler gösterir:

  • Hat Başı (HoL) Engelleme: Tek bir TCP bağlantısında, istemcinin bir sonraki isteği göndermeden önce mevcut isteğin yanıtını beklemesi gerekir.
  • Bağlantı Havuzu Tükenmesi: Eşzamanlı istekleri işlemek için istemcilerin birden fazla TCP bağlantısı açması gerekir. Bu, bağlantılar sürekli olarak açılıp kapandığından önemli miktarda işletim sistemi kaynak yüküne, soket tükenmesine ve yavaş başlangıç ​​performansı cezalarına neden olur.

C. Zayıf API Sözleşmeleri

REST API’lerinde yerleşik bir derleme zamanı sözleşmesi yoktur. OpenAPI/Swagger, API’lerin belgelenmesine yardımcı olsa da kod tabanından ayrıdırlar. Bir arka uç geliştiricisinin bir JSON alan adını değiştirmesi ve derleme zamanı uyarıları olmadan yanlışlıkla aşağı akış hizmetlerini kesmesi kolaydır.


2. GRPC’nin Mimari Temelleri

Google tarafından 2015 yılında tanıtılan gRPC, bulutta yerel uygulamalar için özel olarak tasarlanmış açık kaynaklı, yüksek performanslı bir RPC çerçevesidir. Üç temel teknolojiyi kullanarak REST’in darboğazlarını çözer:

GRPC'nin 3 Mimari Direği: Protokol Arabellekleri, HTTP/2 Aktarımı ve Kod Oluşturma

A. Protokol Tamponları (Protobuf)

gRPC, Arayüz Tanımlama Dili (IDL) ve mesaj serileştirme formatı olarak JSON yerine Protokol Arabelleklerini kullanır.

  • Protobuf, yüksek derecede optimize edilmiş bir ikili serileştirme mekanizmasıdır.
  • Alanlar, metin adları yerine küçük sayısal etiketler olarak serileştirilir.
  • Yükler JSON’a göre %70-80’e kadar daha küçüktür ve serileştirme 10 kata kadar daha hızlı olup CPU ve bant genişliği kullanımını önemli ölçüde azaltır.

B. HTTP/2 Çoğullama

gRPC, aktarım protokolü olarak HTTP/2‘yi kullanarak çeşitli performans optimizasyonları sağlar:

  • Gerçek Çoğullama: Birden fazla istek ve yanıt, tek uzun ömürlü bir TCP bağlantısı üzerinden aynı anda serpiştirilebilir ve Hat Başı engellemesini tamamen ortadan kaldırır.
  • Başlık Sıkıştırma (HPACK): HTTP üstbilgileri sıkıştırılarak istek boyutu daha da azaltılır.
  • Akış Desteği: HTTP/2 yerel olarak tek yönlü istemci akışını, sunucu akışını ve tam çift yönlü akışı destekler.

C. Kod Oluşturma ve Katı Sözleşmeler

gRPC, API yapısını bir .proto dosyasında tanımlayarak düzinelerce dilde (Java, Go, Python, C++ vb.) istemci taslakları ve sunucu temel sınıfları oluşturur. Sözleşme derleme zamanı açısından güvenlidir; sunucu arayüzünü güncellerse ve yeni sözleşmeyle eşleşmezlerse istemci yapıları hemen başarısız olur.


3. Java Uygulaması: gRPC Hizmeti Oluşturma

Pratik bir Java gRPC uygulaması oluşturalım. Müşterinin ödeme isteği gönderdiği ve işlem onayı aldığı yüksek verimli bir Ödeme İşleme Hizmeti uygulayacağız.

Adım 1: API Sözleşmesini tanımlayın (payment.proto)

Hizmet arayüzümüzü ve mesaj yapılarımızı bir Protokol Buffer dosyasında tanımlayarak başlıyoruz.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.ghaznix.grpc.payment";
option java_outer_classname = "PaymentProto";

package payment;

// The Payment Service definition
service PaymentService {
  // Unary RPC: Sends a single payment request and receives a confirmation
  rpc ProcessPayment (PaymentRequest) returns (PaymentResponse);
}

// The Payment Request message
message PaymentRequest {
  string transaction_id = 1;
  string customer_id = 2;
  double amount = 3;
  string currency = 4;
}

// The Payment Response message
message PaymentResponse {
  string transaction_id = 1;
  string status = 2;
  string message = 3;
  string timestamp = 4;
}

Adım 2: Sunucu Tarafı Mantığını Uygulama

.proto dosyasını derledikten sonra (genellikle Maven veya Gradle gRPC eklentileri tarafından otomatik olarak işlenir), araç PaymentServiceImplBase sınıfını oluşturur. Bu sınıfı iş mantığını uygulamak için genişletiyoruz.

package com.ghaznix.grpc.payment;

import io.grpc.stub.StreamObserver;
import java.time.Instant;

public class PaymentServiceImpl extends PaymentServiceGrpc.PaymentServiceImplBase {

    @Override
    public void processPayment(PaymentRequest request, StreamObserver<PaymentResponse> responseObserver) {
        System.out.println("Received payment request for customer: " + request.getCustomerId() 
                + " with amount: " + request.getAmount() + " " + request.getCurrency());

        // Process payment logic (simulation)
        String status = request.getAmount() > 0 ? "SUCCESS" : "DECLINED";
        String message = status.equals("SUCCESS") ? "Payment processed successfully." : "Invalid payment amount.";

        PaymentResponse response = PaymentResponse.newBuilder()
                .setTransactionId(request.getTransactionId())
                .setStatus(status)
                .setMessage(message)
                .setTimestamp(Instant.now().toString())
                .build();

        // Send the response to the client
        responseObserver.onNext(response);
        
        // Signal that the RPC execution is complete
        responseObserver.onCompleted();
    }
}

Adım 3: gRPC Sunucusunu önyükleyin

Daha sonra gRPC sunucusunu belirli bir portta başlatmak için bir sunucu sınıfı oluşturuyoruz ve hizmet uygulamamızı kaydediyoruz.

package com.ghaznix.grpc.payment;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class PaymentServer {

    private Server server;

    public void start() throws IOException {
        int port = 50051;
        server = ServerBuilder.forPort(port)
                .addService(new PaymentServiceImpl())
                .build()
                .start();
        
        System.out.println("gRPC Server started, listening on port " + port);

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.err.println("*** Shutting down gRPC server since JVM is shutting down");
            try {
                PaymentServer.this.stop();
            } catch (InterruptedException e) {
                e.printStackTrace(System.err);
            }
            System.err.println("*** Server shut down");
        }));
    }

    public void stop() throws InterruptedException {
        if (server != null) {
            server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
        }
    }

    public void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        final PaymentServer server = new PaymentServer();
        server.start();
        server.blockUntilShutdown();
    }
}

Adım 4: İstemciyi Uygulama (Engelleme ve Eşzamansız Stub’lar)

GRPC’nin önemli bir yararı, istemci için iki tür taslak oluşturmasıdır:

  1. BlockingStub: Eşzamanlı, yürütmeyi engelliyor (standart REST çağrılarına benzer).
  2. Saplama: Geri arama gözlemcilerini kullanan eşzamansız, engellemesiz yürütme.
package com.ghaznix.grpc.payment;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import java.util.concurrent.TimeUnit;

public class PaymentClient {

    private final ManagedChannel channel;
    private final PaymentServiceGrpc.PaymentServiceBlockingStub blockingStub;

    public PaymentClient(String host, int port) {
        // Create a communication channel to the server
        this.channel = ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext() // Disables TLS for local development
                .build();
        
        // Create the synchronous blocking stub
        this.blockingStub = PaymentServiceGrpc.newBlockingStub(channel);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    public void executePayment(String txId, String customerId, double amount) {
        PaymentRequest request = PaymentRequest.newBuilder()
                .setTransactionId(txId)
                .setCustomerId(customerId)
                .setAmount(amount)
                .setCurrency("USD")
                .build();

        System.out.println("Sending payment request for: " + txId);
        
        // Execute unary synchronous call
        PaymentResponse response = blockingStub.processPayment(request);
        
        System.out.println("Server Response: " + response.getStatus() + " | " + response.getMessage());
    }

    public static void main(String[] args) throws InterruptedException {
        PaymentClient client = new PaymentClient("localhost", 50051);
        try {
            client.executePayment("TX-99081", "customer-abc-123", 250.75);
        } finally {
            client.shutdown();
        }
    }
}

4. Mimari Karşılaştırma: gRPC ve REST

Aşağıdaki tablo her iki paradigmanın temel özelliklerini özetlemektedir:

Mimari Özellik REST (Temsili Devlet Transferi) gRPC (Google Uzaktan Prosedür Çağrısı)
Protokol HTTP/1.1 (Standart), HTTP/2 (İsteğe Bağlı) HTTP/2 (Katı Gereksinim)
Yük Formatı Düz Metin JSON, XML, HTML İkili Protokol Tamponları (Protobuf)
API Paradigması Kaynaklar (URI yolları + GET/POST/PUT/DELETE) Uzaktan Prosedürler (Yöntemler/İşlev çağrıları)
Sözleşme Kalitesi Gevşek (OpenAPI/Swagger isteğe bağlıdır/haricidir) Strict (Derleme sırasında .proto dosyalarında tanımlanmıştır)
Çoğullama Hayır (HTTP/1.1’de Hat Başı engelleme) Evet (Tek TCP bağlantısında birden fazla akış)
Akış Türleri Yalnızca Tek Yönlü Sunucu Tarafından Gönderilen Olaylar (SSE) İstemci, Sunucu ve Çift Yönlü Akış
Kod Oluşturma Gerekli harici araçlar (ör. Swagger Codegen) Protobuf derleyicisi aracılığıyla yerleşik (protoc)
Tarayıcı Desteği Evrensel (Doğrudan web istemcisi erişimi) Sınırlı (grpc-web proxy çevirisi gerektirir)

5. REST yerine gRPC’yi Ne Zaman Seçmeliyiz?

GRPC’nin devasa performans avantajlarına rağmen sihirli bir değnek değil. Protokol seçimi sistem mimarinizdeki bağlama bağlıdır:

  • Dahili Mikro Hizmet Ağı için gRPC’yi kullanın: Yüksek hacimli hizmetten hizmete iletişim için gRPC’nin düşük gecikme süresi, ikili kompaktlığı ve akış yetenekleri onu üstün bir seçim haline getirir. Dahili sunucu CPU yükünü azaltır ve genel yanıt sürelerini hızlandırır.
  • Edge/Genel API’ler için REST’i kullanın: Web tarayıcıları çeviri katmanları olmadan yerel olarak gRPC çağrıları yapamadığından REST genel API’ler, üçüncü taraf web kancalarıyla entegrasyonlar ve istemci tarayıcılardan mimarinizin ucuna doğrudan iletişim için en iyi seçim olmaya devam ediyor.

Mimarlar, REST’i API Ağ Geçidi düzeyinde kullanıma sunma ve dahili mikro hizmet iletişimi için gRPC’den yararlanma gibi ikisini birleştirerek, hem birlikte çalışabilirliği yüksek hem de son derece hızlı sistemler oluşturabilir.


Ghaznix Blogunda daha fazla yazılım geliştirme ve arka uç mühendisliği öngörülerini keşfedin →