Load Balancing
Implementing client-side load balancing for gRPC:
package main
import (
"context"
"log"
"time"
"github.com/example/service/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/resolver"
)
// Custom name resolver for client-side load balancing
type exampleResolver struct {
target resolver.Target
cc resolver.ClientConn
addrsStore map[string][]string
}
func (r *exampleResolver) ResolveNow(resolver.ResolveNowOptions) {
addresses := []resolver.Address{}
for _, addr := range r.addrsStore[r.target.Endpoint] {
addresses = append(addresses, resolver.Address{Addr: addr})
}
r.cc.UpdateState(resolver.State{Addresses: addresses})
}
func (*exampleResolver) Close() {}
type exampleResolverBuilder struct {
addrsStore map[string][]string
}
func (b *exampleResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
r := &exampleResolver{
target: target,
cc: cc,
addrsStore: b.addrsStore,
}
r.ResolveNow(resolver.ResolveNowOptions{})
return r, nil
}
func (*exampleResolverBuilder) Scheme() string {
return "example"
}
func main() {
// Set up a custom resolver for load balancing
addrsStore := map[string][]string{
"user-service": {
"localhost:50051",
"localhost:50052",
"localhost:50053",
},
}
// Register the resolver
rb := &exampleResolverBuilder{addrsStore: addrsStore}
resolver.Register(rb)
// Create a connection with the custom resolver and round-robin load balancing
conn, err := grpc.Dial(
"example:///user-service",
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
// Create client
client := proto.NewUserServiceClient(conn)
// Make multiple requests to demonstrate load balancing
for i := 0; i < 10; i++ {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
user, err := client.GetUser(ctx, &proto.GetUserRequest{
UserId: "user123",
})
if err != nil {
log.Printf("Request failed: %v", err)
} else {
log.Printf("Got user: %v", user.Name)
}
time.Sleep(time.Second)
}
}
Advanced Streaming Patterns
gRPC’s streaming capabilities enable sophisticated communication patterns that can significantly enhance performance and responsiveness.
Bidirectional Streaming for Real-Time Communication
Bidirectional streaming allows both client and server to send multiple messages independently:
// chat_service.proto
syntax = "proto3";
package chat;
option go_package = "github.com/example/chat";
import "google/protobuf/timestamp.proto";
service ChatService {
// Bidirectional streaming RPC for real-time chat
rpc ChatSession(stream ChatMessage) returns (stream ChatMessage);
}
message ChatMessage {
string user_id = 1;
string message = 2;
google.protobuf.Timestamp timestamp = 3;
}
Server implementation:
package main
import (
"io"
"log"
"net"
"sync"
"github.com/example/chat"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/timestamppb"
)
type chatServer struct {
chat.UnimplementedChatServiceServer
mu sync.Mutex
streams map[string][]chat.ChatService_ChatSessionServer
}
func newChatServer() *chatServer {
return &chatServer{
streams: make(map[string][]chat.ChatService_ChatSessionServer),
}
}
func (s *chatServer) ChatSession(stream chat.ChatService_ChatSessionServer) error {
// Register this stream
roomID := "global" // In a real app, you'd extract room ID from the context or first message
s.registerStream(roomID, stream)
defer s.unregisterStream(roomID, stream)
// Handle incoming messages
for {
msg, err := stream.Recv()
if err == io.EOF {
// Client closed the connection
return nil
}
if err != nil {
return err
}
// Set server timestamp
msg.Timestamp = timestamppb.Now()
//
---
### Error Handling and Resilience
Building resilient gRPC services requires sophisticated error handling and recovery mechanisms.