Load Balancing Strategies in the Kratos Selector Package: A Complete Guide

The Kratos selector package provides three production-ready load balancing strategies—Random, Weighted Round-Robin (WRR), and Pick-Two-Choices (P2C)—that implement the selector.Balancer interface to distribute RPC calls across service instances.

The go-kratos/kratos microservices framework includes a pluggable selector component that handles service instance selection for every RPC call. Within the load balancing strategies Kratos selector architecture, the core logic resides in implementations of the Balancer interface, which can be configured through builder patterns to suit different deployment scenarios and performance requirements.

Core Architecture of the Kratos Selector

The selector package decouples node discovery from load balancing through a clean interface abstraction defined in selector/selector.go and selector/balancer.go.

The Balancer Interface

At the heart of every load balancing strategy lies the Balancer interface defined in selector/balancer.go:

type Balancer interface {
    Pick(ctx context.Context, nodes []WeightedNode) (WeightedNode, DoneFunc, error)
}

When a client invokes Select, the selector passes a slice of WeightedNode objects to the configured balancer's Pick method. The balancer chooses one node and returns it along with a DoneFunc callback that reports the RPC result (success, failure, or latency) back to the selector.

WeightedNode Abstraction

The WeightedNode interface, also defined in selector/balancer.go, extends the base Node with runtime weight calculation:

type WeightedNode interface {
    Node
    Weight() float64
    Pick() DoneFunc
}

Concrete node builders provide different weight calculation strategies. The selector/node/direct package offers simple direct weights, while selector/node/ewma provides exponentially weighted moving average calculations that factor in recent latency and error rates. According to the source code in selector/node/ewma/node.go, EWMA nodes automatically decay older observations, making them ideal for latency-sensitive load balancing strategies like P2C.

Default Composite Selector

The selector/default_selector.go file implements the wiring logic that combines a WeightedNodeBuilder with a Balancer. When Select is invoked, the default selector:

  1. Optionally filters nodes based on criteria
  2. Retrieves current weighted nodes from the builder
  3. Delegates the choice to the configured balancer via Pick
  4. Returns the raw Node and DoneFunc for the caller to execute post-RPC

Available Load Balancing Strategies

Kratos ships three concrete balancers, each optimized for different cluster characteristics and performance requirements.

Random Balancer

The Random strategy, implemented in selector/random/random.go, provides the simplest distribution mechanism. It selects a node uniformly at random using rand.IntN(len(nodes)) and returns the node's Pick() function. This approach works well for homogeneous clusters where all instances have equal capacity, or during testing phases when deterministic behavior is unnecessary.

The random balancer uses the direct node builder by default, as shown in selector/random/random.go:

return &selector.DefaultBuilder{
    Balancer: &Builder{},
    Node:     &direct.Builder{},
}

Weighted Round-Robin (WRR)

The Weighted Round-Robin strategy, found in selector/wrr/wrr.go, implements the nginx-style weighted round-robin algorithm. It maintains a per-address currentWeight map that updates on every request according to the formula:

  1. Add the node's effective weight to its current weight
  2. Select the node with the highest accumulated weight
  3. Subtract the total weight of all nodes from the selected node's current weight

This algorithm naturally handles heterogeneous capacity—nodes with higher weights receive proportionally more traffic. The implementation also clears stale entries when the node list changes, preventing memory leaks during service scaling events.

Pick-Two-Choices (P2C)

The Pick-Two-Choices strategy in selector/p2c/p2c.go offers the most sophisticated load balancing for large, heterogeneous clusters. It randomly selects two distinct nodes, compares their Weight() values, and returns the heavier one. The algorithm also implements a forcePick mechanism that occasionally selects the lighter node to keep latency statistics fresh and prevent stale weight data from dominating the selection process.

P2C pairs with the EWMA node builder (selector/node/ewma/node.go) to leverage real-time latency and success rate data, making it ideal for production environments where node performance fluctuates.

Configuring Load Balancers in Your Application

Each balancer provides a convenience constructor that returns a fully wired selector.Selector. All three implementations share the same interface, allowing runtime swapping without changing client code.

Random Configuration

For simple, low-overhead balancing:

import "github.com/go-kratos/kratos/v2/selector/random"

func newRandomSelector() selector.Selector {
    // Returns selector wired with random balancer and direct nodes
    return random.New()
}

WRR Configuration

For capacity-aware distribution:

import "github.com/go-kratos/kratos/v2/selector/wrr"

func newWRRSelector() selector.Selector {
    // Uses weighted round-robin with direct node weights
    return wrr.New()
}

P2C Configuration

For latency-optimized selection in large clusters:

import "github.com/go-kratos/kratos/v2/selector/p2c"

func newP2CSelector() selector.Selector {
    // Automatically uses EWMA node builder for latency tracking
    return p2c.New()
}

Runtime Strategy Selection

Since all balancers implement selector.Selector, you can inject the appropriate strategy based on configuration:

var sel selector.Selector

switch cfg.LoadBalancingStrategy {
case "random":
    sel = random.New()
case "wrr":
    sel = wrr.New()
case "p2c":
    sel = p2c.New()
}

// Use in client call
node, done, err := sel.Select(ctx, selector.WithService("order"))
if err != nil {
    // handle no available nodes
}
defer done(ctx, selector.DoneInfo{Err: err, Reply: reply})

Summary

  • Random (selector/random/random.go) offers simple uniform distribution using rand.IntN, ideal for testing or homogeneous clusters.
  • Weighted Round-Robin (selector/wrr/wrr.go) implements nginx-style WRR with per-node current weights, suitable for capacity-aware routing.
  • Pick-Two-Choices (selector/p2c/p2c.go) selects between two random nodes based on EWMA-calculated weights, optimized for large production clusters with variable latency.
  • All balancers implement the selector.Balancer interface and wire into selector.DefaultBuilder alongside node builders (direct or EWMA) to create complete load balancing strategies for the Kratos selector package.

Frequently Asked Questions

What is the default load balancing strategy in Kratos?

Kratos does not enforce a single default strategy; instead, it requires explicit configuration through the selector builder. Most examples use the Random balancer for simplicity, but production deployments typically choose between WRR and P2C based on cluster heterogeneity and latency requirements.

How does P2C differ from WRR?

P2C (Pick-Two-Choices) randomly compares two nodes and selects the heavier one based on real-time EWMA weights, making it responsive to latency fluctuations. WRR (Weighted Round-Robin) distributes traffic according to static or slowly-changing capacity weights using a round-robin algorithm, which provides more predictable distribution but less responsiveness to transient performance issues.

Can I implement a custom load balancer?

Yes. Any struct implementing the Balancer interface defined in selector/balancer.go can serve as a custom load balancing strategy. You must implement the Pick(context.Context, []WeightedNode) (WeightedNode, DoneFunc, error) method, then wrap it in a selector.DefaultBuilder with your chosen node builder (direct or EWMA) to create a complete selector.

When should I use EWMA nodes versus direct nodes?

Use EWMA nodes (selector/node/ewma/node.go) when running P2C or any latency-sensitive balancer in production, as they track recent RPC latency and error rates. Use direct nodes (selector/node/direct/direct.go) for Random or WRR when weights are static or when you want minimal overhead and deterministic behavior based on configured capacity weights.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →