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.