gRPC API Nedir: REST’e Alternatif Yüksek Performanslı İletişim Rehberi
Microservice mimarisine geçtiğinizde ya da yüksek trafikli sistemler kurmaya başladığınızda, REST API’lerinin bir noktada yetersiz kaldığını fark edersiniz. Geç gelen yanıtlar, şişirilmiş JSON payload’ları, tip güvenliğinin yokluğu… İşte tam burada gRPC devreye giriyor. Google tarafından geliştirilen ve 2016’da açık kaynak olarak yayınlanan gRPC, özellikle servisler arası iletişimde REST’e göre ciddi avantajlar sunuyor. Bu rehberde gRPC’yi sıfırdan anlayacak, pratik örneklerle nasıl kurulup kullanıldığını görecek ve gerçek dünya senaryolarında REST ile karşılaştırmalı olarak değerlendireceğiz.
gRPC Nedir ve Neden Önemlidir
gRPC, Google Remote Procedure Call kelimelerinin kısaltmasıdır. Temelde bir servisin başka bir servisteki metodu sanki lokal bir fonksiyonmuş gibi çağırmasını sağlar. Bu RPC (Remote Procedure Call) konsepti aslında yeni değil, ama gRPC bunu modern altyapıyla yeniden tanımladı.
gRPC’nin altında iki temel teknoloji yatıyor:
- HTTP/2: Multiplexing, header compression ve binary framing ile HTTP/1.1’e göre çok daha verimli
- Protocol Buffers (protobuf): Google’ın geliştirdiği, JSON’a kıyasla 5-10 kat daha küçük ve hızlı binary serialization formatı
REST API’lerde her şeyi kendiniz tanımlarsınız: URL yapısı, HTTP metodları, request/response formatı, hata kodları. gRPC’de ise önce bir .proto dosyası yazarsınız, bu dosya hem servis tanımını hem de veri yapılarını içerir. Sonra bu dosyadan otomatik olarak client ve server kodu üretilir. Bu sayede tip güvenliği garantidir ve API kontratı merkezi bir yerde yaşar.
Protocol Buffers (Protobuf) Anlamak
Protobuf, gRPC’nin kalbidir. Önce bunu anlamadan gRPC’yi tam kavrayamazsınız.
JSON ile bir kullanıcı verisi şöyle gönderirsiniz:
# JSON boyutu örneği - tipik bir kullanıcı objesi
echo '{"id":1,"name":"Ahmet Yilmaz","email":"[email protected]","age":30}' | wc -c
# Çıktı: 65 byte
Aynı veriyi protobuf ile tanımlayıp serialize ettiğinizde bu boyut 15-20 byte’a kadar düşebilir. Ağ üzerinden milyonlarca istek geçtiğinde bu fark dramatik hale gelir.
Bir .proto dosyası şöyle görünür:
# user.proto dosyasını oluşturalım
cat > user.proto << 'EOF'
syntax = "proto3";
package user;
option go_package = "./proto/user";
// Kullanıcı veri modeli
message User {
int32 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
repeated string roles = 5;
}
// İstek ve yanıt mesajları
message GetUserRequest {
int32 id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
int32 age = 3;
}
message UserResponse {
bool success = 1;
string message = 2;
User user = 3;
}
// Servis tanımı
service UserService {
rpc GetUser(GetUserRequest) returns (UserResponse);
rpc CreateUser(CreateUserRequest) returns (UserResponse);
rpc ListUsers(ListUsersRequest) returns (stream UserResponse);
}
message ListUsersRequest {
int32 page = 1;
int32 limit = 2;
}
EOF
echo "Proto dosyası oluşturuldu"
Burada dikkat edilmesi gereken nokta: her field’ın bir numarası var (1, 2, 3…). Bu numaralar binary encoding’de field’ları tanımlamak için kullanılır. Protobuf, field adlarını değil bu numaraları kullanır, bu yüzden boyut bu kadar küçük olur.
Go ile gRPC Server Kurulumu
Şimdi pratik bir örnek yapalım. Go, gRPC için en popüler dillerden biri. Önce gerekli araçları kuralım:
# Go kurulu değilse önce onu kurun
# Ubuntu/Debian için:
sudo apt update && sudo apt install -y golang-go
# Protobuf compiler kurulumu
sudo apt install -y protobuf-compiler
# Go için protobuf ve gRPC plugin'leri
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
# PATH'e ekleyin
export PATH="$PATH:$(go env GOPATH)/bin"
# Kurulumu doğrulayın
protoc --version
protoc-gen-go --version
Projeyi oluşturalım:
# Proje dizini oluştur
mkdir grpc-user-service && cd grpc-user-service
go mod init github.com/sysadmin/grpc-user-service
# Bağımlılıkları ekle
go get google.golang.org/grpc
go get google.golang.org/protobuf
# Dizin yapısını oluştur
mkdir -p proto server client
# Proto dosyasını taşı
mv ../user.proto proto/
# Kodu üret
protoc --go_out=. --go_opt=paths=source_relative
--go-grpc_out=. --go-grpc_opt=paths=source_relative
proto/user.proto
ls proto/
# user.pb.go ve user_grpc.pb.go dosyaları oluşmuş olmalı
Şimdi server implementasyonunu yazalım:
# Server kodunu oluştur
cat > server/main.go << 'EOF'
package main
import (
"context"
"log"
"net"
pb "github.com/sysadmin/grpc-user-service/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
// UserServer struct - servis implementasyonu
type UserServer struct {
pb.UnimplementedUserServiceServer
users map[int32]*pb.User
}
// GetUser - tek kullanıcı getir
func (s *UserServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.UserResponse, error) {
log.Printf("GetUser çağrıldı, ID: %d", req.Id)
user, exists := s.users[req.Id]
if !exists {
return &pb.UserResponse{
Success: false,
Message: "Kullanıcı bulunamadı",
}, nil
}
return &pb.UserResponse{
Success: true,
Message: "Kullanıcı bulundu",
User: user,
}, nil
}
// CreateUser - yeni kullanıcı oluştur
func (s *UserServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.UserResponse, error) {
newID := int32(len(s.users) + 1)
user := &pb.User{
Id: newID,
Name: req.Name,
Email: req.Email,
Age: req.Age,
}
s.users[newID] = user
log.Printf("Yeni kullanıcı oluşturuldu: %s (ID: %d)", user.Name, user.Id)
return &pb.UserResponse{
Success: true,
Message: "Kullanıcı oluşturuldu",
User: user,
}, nil
}
func main() {
// TCP listener oluştur
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("Dinleme başarısız: %v", err)
}
// gRPC server oluştur
grpcServer := grpc.NewServer()
// Servisi kaydet
userServer := &UserServer{
users: make(map[int32]*pb.User),
}
pb.RegisterUserServiceServer(grpcServer, userServer)
// Reflection ekle (grpcurl gibi araçlar için gerekli)
reflection.Register(grpcServer)
log.Println("gRPC server :50051 portunda başlatıldı")
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("Server hatası: %v", err)
}
}
EOF
# Server'ı başlat
go run server/main.go &
echo "Server PID: $!"
gRPC Client Yazımı ve Test
Server hazır, şimdi client tarafına geçelim:
# Client kodunu oluştur
cat > client/main.go << 'EOF'
package main
import (
"context"
"log"
"time"
pb "github.com/sysadmin/grpc-user-service/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
// Server'a bağlan (production'da TLS kullanın!)
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalf("Bağlantı hatası: %v", err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
// 5 saniyelik timeout context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Kullanıcı oluştur
createResp, err := client.CreateUser(ctx, &pb.CreateUserRequest{
Name: "Ahmet Yilmaz",
Email: "[email protected]",
Age: 30,
})
if err != nil {
log.Fatalf("CreateUser hatası: %v", err)
}
log.Printf("Oluşturuldu: %+v", createResp.User)
// Kullanıcıyı getir
getResp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: createResp.User.Id})
if err != nil {
log.Fatalf("GetUser hatası: %v", err)
}
log.Printf("Getirildi: %+v", getResp.User)
}
EOF
go run client/main.go
grpcurl ile API Test Etmek
REST’te Postman veya curl kullanırsınız. gRPC için grpcurl aracı son derece kullanışlı:
# grpcurl kurulumu
# Linux için:
curl -sSL "https://github.com/fullstorydev/grpcurl/releases/download/v1.8.9/grpcurl_1.8.9_linux_x86_64.tar.gz" | tar -xz
sudo mv grpcurl /usr/local/bin/
# Mevcut servisleri listele (reflection açık olmalı)
grpcurl -plaintext localhost:50051 list
# Servis metodlarını listele
grpcurl -plaintext localhost:50051 list user.UserService
# Kullanıcı oluştur
grpcurl -plaintext -d '{"name":"Mehmet Demir","email":"[email protected]","age":25}'
localhost:50051 user.UserService/CreateUser
# Kullanıcı getir
grpcurl -plaintext -d '{"id":1}'
localhost:50051 user.UserService/GetUser
# Performans testi için hey veya ghz kullanabilirsiniz
# ghz kurulumu ve test
go install github.com/bojand/ghz/cmd/ghz@latest
ghz --insecure
--proto proto/user.proto
--call user.UserService.GetUser
-d '{"id":1}'
-n 1000
-c 50
localhost:50051
Streaming ile Gerçek Zamanlı Veri Akışı
gRPC’nin REST’e göre en güçlü olduğu alan streaming. Dört tip streaming var:
- Unary: Tek istek, tek yanıt (klasik RPC)
- Server Streaming: Tek istek, çoklu yanıt akışı
- Client Streaming: Çoklu istek, tek yanıt
- Bidirectional Streaming: Her iki taraftan da veri akışı
Log streaming için server-side streaming mükemmel bir kullanım senaryosudur. Diyelim ki sistem loglarını gerçek zamanlı istemciye aktarıyorsunuz:
# Streaming proto tanımı ekleyelim
cat >> proto/user.proto << 'EOF'
// Log streaming için ek tanımlar
message LogEntry {
string timestamp = 1;
string level = 2;
string message = 3;
string service = 4;
}
message LogStreamRequest {
string service_name = 1;
string min_level = 2;
}
service LogService {
// Server-side streaming: client bir istek yapar, server sürekli log gönderir
rpc StreamLogs(LogStreamRequest) returns (stream LogEntry);
}
EOF
# Log server implementasyonu
cat > server/log_server.go << 'EOF'
package main
import (
"fmt"
"log"
"time"
pb "github.com/sysadmin/grpc-user-service/proto"
)
type LogServer struct {
pb.UnimplementedLogServiceServer
}
func (s *LogServer) StreamLogs(req *pb.LogStreamRequest, stream pb.LogService_StreamLogsServer) error {
log.Printf("Log streaming başlatıldı: servis=%s, seviye=%s", req.ServiceName, req.MinLevel)
// Gerçek senaryoda burası dosyayı tail -f gibi okur
// Örnek olarak simüle ediyoruz
levels := []string{"INFO", "WARN", "ERROR", "DEBUG"}
messages := []string{
"Bağlantı kuruldu",
"İstek işlendi",
"Yavaş sorgu tespit edildi",
"Cache miss",
"Retry yapılıyor",
}
for i := 0; i < 50; i++ {
// Stream context iptal edildiyse dur
if err := stream.Context().Err(); err != nil {
return err
}
entry := &pb.LogEntry{
Timestamp: time.Now().Format(time.RFC3339),
Level: levels[i%len(levels)],
Message: fmt.Sprintf("[%s] %s", req.ServiceName, messages[i%len(messages)]),
Service: req.ServiceName,
}
if err := stream.Send(entry); err != nil {
return fmt.Errorf("stream gönderme hatası: %v", err)
}
time.Sleep(200 * time.Millisecond)
}
return nil
}
EOF
echo "Log server kodu oluşturuldu"
TLS ve Güvenlik Yapılandırması
Production ortamında gRPC bağlantılarınızı mutlaka TLS ile şifrelemeniz gerekiyor. Aksi halde microservice’ler arası trafik açık gezebilir:
# Self-signed sertifika oluştur (test için)
mkdir -p certs && cd certs
# CA key ve sertifikası
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key
-subj "/C=TR/O=SysAdmin Corp/CN=gRPC CA"
-out ca.crt
# Server key ve CSR
openssl genrsa -out server.key 4096
openssl req -new -key server.key
-subj "/C=TR/O=SysAdmin Corp/CN=localhost"
-out server.csr
# SAN (Subject Alternative Name) ile sertifika imzala
cat > server-ext.cnf << 'EOF'
[SAN]
subjectAltName=DNS:localhost,DNS:grpc-server,IP:127.0.0.1
EOF
openssl x509 -req -days 365 -in server.csr
-CA ca.crt -CAkey ca.key -CAcreateserial
-extfile server-ext.cnf -extensions SAN
-out server.crt
cd ..
# TLS'li server başlatma kodu
cat > server/tls_main.go << 'EOF'
package main
import (
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func startTLSServer() {
creds, err := credentials.NewServerTLSFromFile("certs/server.crt", "certs/server.key")
if err != nil {
log.Fatalf("TLS sertifika yükleme hatası: %v", err)
}
grpcServer := grpc.NewServer(
grpc.Creds(creds),
// Maksimum mesaj boyutu: 10MB
grpc.MaxRecvMsgSize(10*1024*1024),
grpc.MaxSendMsgSize(10*1024*1024),
)
lis, err := net.Listen("tcp", ":50052")
if err != nil {
log.Fatalf("Dinleme hatası: %v", err)
}
log.Println("TLS gRPC server :50052 portunda başlatıldı")
grpcServer.Serve(lis)
}
EOF
echo "TLS yapılandırması tamamlandı"
Kubernetes’te gRPC Load Balancing
gRPC ve Kubernetes birlikte kullanıldığında önemli bir detay var: HTTP/2 persistent connection özelliği nedeniyle standart Kubernetes Service load balancing çalışmaz. Tüm istekler aynı pod’a gider.
Bunu çözmek için ya headless service + client-side load balancing ya da Envoy/Linkerd gibi bir service mesh kullanmalısınız:
# Headless service tanımı
cat > k8s/grpc-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: grpc-user-service
labels:
app: grpc-user-service
spec:
# clusterIP: None = headless service
# Bu sayede DNS, pod IP'lerini direkt döner
clusterIP: None
selector:
app: grpc-user-service
ports:
- name: grpc
port: 50051
targetPort: 50051
protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: grpc-user-service
spec:
replicas: 3
selector:
matchLabels:
app: grpc-user-service
template:
metadata:
labels:
app: grpc-user-service
spec:
containers:
- name: user-service
image: your-registry/grpc-user-service:latest
ports:
- containerPort: 50051
# gRPC health check
readinessProbe:
exec:
command:
- /bin/grpc_health_probe
- -addr=:50051
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
exec:
command:
- /bin/grpc_health_probe
- -addr=:50051
initialDelaySeconds: 10
periodSeconds: 30
EOF
# Kubernetes'e uygula
kubectl apply -f k8s/grpc-service.yaml
# Pod durumlarını kontrol et
kubectl get pods -l app=grpc-user-service
kubectl logs -l app=grpc-user-service --tail=50
gRPC vs REST: Hangisini Seçmeli
Hem gRPC hem REST’in doğru kullanım alanları var. İkisini körce karşılaştırmak yerine senaryoya göre düşünmek gerekiyor:
gRPC tercih edin:
- Microservice’ler arası dahili iletişimde (en güçlü kullanım alanı)
- Düşük latency kritik olduğunda (trading sistemleri, real-time oyunlar)
- Güçlü tip güvenliği ihtiyacınız olduğunda
- Streaming veri aktarımında (log aggregation, telemetri, canlı metrikler)
- Çok dilli sistemlerde (protobuf her major dili destekler)
- Bant genişliği pahalı olduğunda (IoT cihazlar, mobil arka uç)
REST tercih edin:
- Public API’ler için (herkes curl ile test edebilir)
- Browser’dan direkt tüketim gerektiğinde (gRPC-Web olmadan çalışmaz)
- Basit CRUD operasyonları için
- API gateway’ler ve üçüncü parti entegrasyonlar için
- Önbellekleme (CDN level caching) kritik olduğunda
- Team’inizde protobuf bilgisi yoksa
Gerçek dünya konfigürasyonunda çoğu şirket hibrit yaklaşım kullanır: dış dünyaya REST API, servisler arası iletişimde gRPC. Bu yaklaşım hem developer experience’ı hem de performance’ı optimize eder.
Monitoring ve Observability
gRPC servislerini izlemek için Prometheus entegrasyonu şart:
# go-grpc-prometheus middleware kurulumu
go get github.com/grpc-ecosystem/go-grpc-prometheus
# Prometheus metrics ile server
cat > server/metrics_server.go << 'EOF'
package main
import (
"net"
"net/http"
"log"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"google.golang.org/grpc"
)
func startWithMetrics() {
grpcServer := grpc.NewServer(
grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
)
// gRPC server'ı başlat
lis, _ := net.Listen("tcp", ":50051")
go grpcServer.Serve(lis)
// Prometheus metrics endpoint'i
grpc_prometheus.EnableHandlingTimeHistogram()
http.Handle("/metrics", promhttp.Handler())
log.Println("Metrics :9090/metrics adresinde")
http.ListenAndServe(":9090", nil)
}
EOF
# Prometheus config
cat > prometheus.yml << 'EOF'
scrape_configs:
- job_name: 'grpc-user-service'
static_configs:
- targets: ['localhost:9090']
scrape_interval: 15s
EOF
# Docker ile Prometheus başlat
docker run -d
--name prometheus
--network host
-v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml
prom/prometheus
echo "Prometheus http://localhost:9090 adresinde çalışıyor"
Grafana dashboard’larında izlemeniz gereken kritik gRPC metrikleri şunlar:
- grpc_server_handled_total: Toplam işlenen RPC sayısı (status code’a göre grupla)
- grpc_server_handling_seconds: RPC işlem süresi histogramı
- grpc_server_started_total: Başlatılan RPC sayısı (akıştaki RPC’leri hesapla)
- grpc_server_msg_received_total: Alınan mesaj sayısı
- grpc_server_msg_sent_total: Gönderilen mesaj sayısı
Sonuç
gRPC, özellikle microservice mimarisinde ciddi bir oyun değiştirici. HTTP/2 ve protobuf kombinasyonu sayesinde REST API’lere kıyasla genellikle 5-10 kat daha az bant genişliği kullanır ve 2-3 kat daha düşük latency sunar. Streaming desteği, güçlü tip güvenliği ve otomatik kod üretimi de üstüne cabası.
Ancak gRPC her derde deva değil. Public API’leriniz için REST’i bırakmanıza gerek yok. Pragmatik yaklaşım şu: dış dünyayla konuşurken REST, servisler arası konuşurken gRPC. Bu hibrit yaklaşımla hem geliştirici deneyimini hem de sistem performansını maksimize edersiniz.
Eğer bugün bir microservice projesi başlatıyorsanız, gRPC’yi başından beri tasarıma dahil etmenizi kesinlikle öneririm. Sonradan REST’ten gRPC’ye geçiş yapmak, başından yazmaktan çok daha zahmetli. .proto dosyalarınızı bir Git repository’sinde merkezi olarak yönetin, tüm takımların aynı kontrata göre çalışmasını sağlayın. Bu yaklaşım hem servislerin birbirinden bağımsız gelişmesine hem de sözleşme uyumluluğunun korunmasına yardımcı olur.
