Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
08 Aug 2025 · 5 min read ·Article 61 / 110
Go

61. Load Balancing in the gRPC Client

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

61. Load Balancing in the gRPC Client: Maximizing Performance with Smart Distribution

Disclaimer: This article is written from the perspective of an engineer experienced in building distributed systems with gRPC, in the context of Go-based backend development. However, the concepts are universal, so they can be applied to other languages as well.


When we talk about scalable microservices, gRPC has by now become the de-facto standard for efficient service-to-service communication. But the fast performance of this RPC framework won’t reach its full potential without proper load balancing.

This article will cover the topic thoroughly: how load balancing in the gRPC client actually works, how to implement it, and the tips and best practices around it. I’ll also walk through the code, simulate load balancing, and provide visualizations using Mermaid.js.

1. Why Load Balancing on the gRPC Client?

By default, when we create a gRPC client that connects to multiple server instances, we need load balancing so that requests aren’t concentrated on just a single instance.

Danger
Without load balancing, bottlenecks and a single point of failure (SPOF) can happen all too easily.

In the following scenario, suppose we have 3 server instances:

InstanceIP Address
Server 110.0.0.1:50051
Server 210.0.0.2:50051
Server 310.0.0.3:50051

Without load balancing, requests from the client can become lopsided: for example, all requests stick to 10.0.0.1, while the others sit idle.

2. Load Balancing Models in gRPC

There are two levels of load balancing in a gRPC architecture:

  1. External (Level 4/7):
    • Like putting Nginx/Envoy/HAProxy in front of a gRPC server cluster.
    • Pro: Easy to manage.
    • Con: Adds latency; it doesn’t know the server state (difficult to do client-side routing).
  2. Client-side Load Balancing:
    • The request distribution logic runs in the gRPC client.
    • More efficient (no extra hop required).
    • Closer to service discovery.

Ideally, client-side load balancing is the one we choose.

3. How Load Balancing Works in the gRPC Client

At a high level, the request flow from the gRPC client can be visualized as follows:

MERMAID
flowchart TD
    Client-->|Resolve addresses|Resolver
    Resolver-->|Return Server List|Client
    Client-->|Select Server (Load Balancer)|LoadBalancer
    LoadBalancer-->Server1
    LoadBalancer-->Server2
    LoadBalancer-->Server3

Flow Explanation:

  1. The client needs resolved server addresses from a DNS/Consul/etc. resolution.
  2. The load balancer in the client picks (for example: round robin) where the request will be directed.

Table of Built-in Load Balancing Modes in Golang gRPC:

ModeDescription
round_robinEven distribution across all instances
pick_firstAlways pick the first server (default)

4. Implementing a Load Balancer in the gRPC Client (Go)

For production use, don’t use the default pick_first mode. We want the round_robin mode that distributes requests across multiple instances.

Sample Service Protobuf

For example, a simple protocol service:

proto
 1// hello.proto
 2syntax = "proto3";
 3service Greeter {
 4  rpc SayHello (HelloRequest) returns (HelloReply) {}
 5}
 6message HelloRequest {
 7  string name = 1;
 8}
 9message HelloReply {
10  string message = 1;
11}

4.1. Set Up the gRPC Server Cluster

Run multiple gRPC servers on different ports (for example: 50051, 50052, 50053).

go
1// main.go (server)
2lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) // port: 50051, etc.
3grpcServer := grpc.NewServer()
4pb.RegisterGreeterServer(grpcServer, &server{})
5grpcServer.Serve(lis)

Run the server in three different terminals, each using a different port.

4.2. Register and Implement the Load Balancer in the Client

A. Direct access to multiple addresses

go
 1import (
 2    "google.golang.org/grpc"
 3    "google.golang.org/grpc/balancer/roundrobin"
 4)
 5
 6var addresses = []string{
 7    "10.0.0.1:50051",
 8    "10.0.0.2:50051",
 9    "10.0.0.3:50051",
10}
11
12target := fmt.Sprintf("dns:///%s", strings.Join(addresses, ",")) // DNS resolver
13conn, err := grpc.Dial(target, grpc.WithInsecure(),
14    grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
15)

B. Service Discovery Integration (Advanced):

You can integrate with DNS, Consul, or a custom resolver. For the demo, we’ll use DNS.

For example, if your service is registered in a DNS SRV record, then it’s enough to do:

go
1conn, err := grpc.Dial(
2  "dns:///greeter-service:50051", // <service_dns_name>
3  grpc.WithInsecure(),
4  grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
5)

4.3. Call the RPC (Load Balanced)

go
1client := pb.NewGreeterClient(conn)
2resp, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "gRPC"})

If you loop several times (100 calls), the requests will take turns across all the servers.

4.4. Simulation and Visualization

With the following code, we can observe the distribution across servers:

go
1for i := 0; i < 10; i++ {
2    resp, _ := client.SayHello(context.Background(), &pb.HelloRequest{Name: fmt.Sprintf("Request-%d", i)})
3    fmt.Println(resp.Message)
4}

In each server’s log, the messages will be printed coming in alternately – this is proof that round robin is working.

5. Simulation: Load Balancer Accuracy

Suppose we make 60 requests in a row. With 3 servers, each server should get ~20 requests if load balancing is perfect.

ServerRequests ReceivedPercentage
Server 12033.3%
Server 22033.3%
Server 32033.3%

6. Troubleshooting and Best Practices

Common Issues

  1. gRPC Client Stuck on 1 server
    Check: has the load balancer policy been set?

  2. Slow failover
    Check: are health checks/connection retries enabled in the client?

  3. Service not registered in DNS
    Use a custom resolver, or a service registry like Consul.

Best Practices

  • Use a Flexible Balancing Provider: Choose based on your needs.
    • Round Robin: The most common
    • Weighted (custom): If you want selection based on each instance’s capacity.
  • Monitoring! Logging and metrics on request distribution are important for detecting bottlenecks.
  • Health Checking
    A service that has become unhealthy should not be added to the balancing pool.

7. Conclusion

Load balancing in the gRPC client is a crucial aspect when you want to build distributed services that are scalable and resilient. Client-side load balancing allows you to make optimal use of a server cluster, increase throughput, and reduce SPOF.

As an engineer, don’t forget to:

  • Always use a load balancer in the client (round_robin mode);
  • Integrate with service discovery for scaling;
  • Monitor traffic distribution regularly.

With the right configuration, in Go or any other platform, gRPC + client-side load balancing is a powerful weapon for achieving a reliable system in the microservices era.


I hope this article is useful! The comments section is open for discussion or for sharing your experiences with load balancing in production. 🚀

Related Articles

💬 Comments