# How the Go-Kratos Weighted Round-Robin Load Balancer Works in Fabrica-Kit

> Discover how the go-kratos weighted round-robin load balancer in Fabrica-Kit combines NGINX WRR with sticky routing for consistent node selection. Optimize your distributed calls now.

- Repository: [Pantheon/fabrica-kit](https://github.com/go-pantheon/fabrica-kit)
- Tags: deep-dive
- Published: 2026-03-02

---

**Fabrica-Kit implements a weighted round-robin load balancer for go-kratos that combines the classic NGINX WRR algorithm with route-table-based sticky routing to ensure consistent node selection across distributed calls.**

The go-kratos weighted round-robin load balancer in Fabrica-Kit extends the standard Kratos selector framework with advanced routing capabilities. Located in the `router/balancer` package, this implementation manages traffic distribution across service nodes while maintaining session affinity through an external route table.

## Core Architecture

The balancer architecture centers on two primary components: the `weightBalancer` struct that manages selection state, and the `balancerBuilder` that integrates with Kratos' selector framework.

### The weightBalancer Struct

In [`router/balancer/balancer.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/balancer.go), the `weightBalancer` struct maintains the runtime state required for weighted selection:

- A **mutex** protecting concurrent access to weight calculations
- A `currentWeight` map tracking dynamic weights per node address
- A `ReadOnlyRouteTable` reference for sticky routing lookups
- A `balancerType` field distinguishing between `TypeMaster` and replica configurations

The struct implements the `selector.Selector` interface, allowing seamless integration with Kratos clients.

### Builder Pattern Integration

The `balancerBuilder` in [`router/balancer/balancerbuilder.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/balancerbuilder.go) constructs balancers using functional options:

```go
builder := balancer.NewBuilder(
    balancer.WithBalancerType(balancer.TypeMaster),
    balancer.WithRouteTable(rt),
)

```

This builder wraps the balancer in a Kratos-compatible selector through `selector.NewBuilder(builder).Build()`.

## Selection Flow and Sticky Routing

The `Pick` method orchestrates the complete node selection workflow, prioritizing route-table consistency before falling back to weighted distribution.

### Pick Method Implementation

When invoked with a context and node slice, `Pick` in [`router/balancer/balancer.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/balancer.go) executes the following sequence:

1. **Validation**: Returns `selector.ErrNoAvailable` if the node slice is empty
2. **Metadata extraction**: Retrieves **OID** (object ID) and **color** (routing group) from the outgoing context using `xcontext.OIDFromOutgoingContext` and `xcontext.ColorFromOutgoingContext`
3. **Sticky lookup**: Queries the route table via `p.routeTable.Get(color, oid)`; if an address exists, returns that specific node immediately
4. **Weighted selection**: Invokes `weightSelect` to determine the target node when no sticky entry exists

### Master-Type Persistence

For `TypeMaster` balancers, the implementation adds atomic persistence:

```go
if p.balancerType == TypeMaster {
    mrt := p.routeTable.(routetable.MasterRouteTable)
    ok, addr, err := mrt.SetNxOrGet(ctx, color, oid, selected.Address())
    // If another balancer won the race, use the stored address instead
}

```

The `SetNxOrGet` operation provides "set-if-not-exists" semantics, ensuring that concurrent initial requests for the same OID resolve to a single canonical node.

## Weighted Round-Robin Algorithm

The `weightSelect` method implements the classic NGINX weighted round-robin algorithm with float64 precision weights.

### weightSelect Implementation

Located in [`router/balancer/balancer.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/balancer.go), the algorithm maintains fairness while respecting node weights:

```go
func (p *weightBalancer) weightSelect(nodes []selector.WeightedNode) selector.WeightedNode {
    var totalWeight float64
    var selected selector.WeightedNode
    var selectWeight float64

    p.mu.Lock()
    defer p.mu.Unlock()

    for _, node := range nodes {
        totalWeight += node.Weight()
        cwt := p.currentWeight[node.Address()] + node.Weight()
        p.currentWeight[node.Address()] = cwt

        if selected == nil || selectWeight < cwt {
            selectWeight = cwt
            selected = node
        }
    }

    p.currentWeight[selected.Address()] = selectWeight - totalWeight
    return selected
}

```

**Key mechanics:**
- Each node's **current weight** increments by its static weight every round
- The node with the highest current weight wins selection
- After selection, the winner's weight decreases by the **total weight** of all nodes
- **Float64 precision** enables fine-grained weighting (e.g., 0.5 or 2.3 ratios)

## Route Table Integration

The route table abstraction in [`router/routetable/routetable.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/routetable/routetable.go) enables persistent sticky routing across service restarts.

**ReadOnlyRouteTable** provides `Get` and `BatchGet` methods for address lookups. **MasterRouteTable** extends this with `SetNxOrGet` for atomic write operations. The balancer accepts either interface depending on whether it needs to persist selections or merely respect existing mappings.

## Practical Implementation Example

Create a master balancer with Redis-backed sticky routing:

```go
import (
    "github.com/go-pantheon/fabrica-kit/router/balancer"
    "github.com/go-pantheon/fabrica-kit/router/routetable/redis"
    "github.com/go-kratos/kratos/v2/selector"
)

rt, _ := redis.NewRouteTable(redisClient, "myservice")

builder := balancer.NewBuilder(
    balancer.WithBalancerType(balancer.TypeMaster),
    balancer.WithRouteTable(rt),
)

sel := selector.NewBuilder(builder).Build()

```

Use the selector in a Kratos HTTP client:

```go
client := http.NewClient(
    http.WithEndpoint("http://example.com"),
    http.WithDiscovery(sel),
)

```

The client now routes requests via the weighted round-robin algorithm while maintaining OID-based session affinity through the Redis route table.

## Summary

- The **go-kratos weighted round-robin load balancer** in Fabrica-Kit resides in [`router/balancer/balancer.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/balancer.go) and implements the NGINX WRR algorithm using float64 precision weights and mutex-protected state.
- **Sticky routing** is achieved through the `ReadOnlyRouteTable` interface, with `TypeMaster` balancers utilizing `SetNxOrGet` for atomic address persistence.
- The **selection flow** extracts OID and color from context, checks the route table first, then falls back to `weightSelect` if no entry exists.
- **Builder pattern integration** in [`router/balancer/balancerbuilder.go`](https://github.com/go-pantheon/fabrica-kit/blob/main/router/balancer/balancerbuilder.go) allows configuration of balancer types and route table injection via `NewBuilder`.

## Frequently Asked Questions

### How does the balancer handle concurrent requests to the same OID?

The `weightBalancer` protects its `currentWeight` map with a mutex during the `weightSelect` operation. For sticky routing, `TypeMaster` balancers use the `SetNxOrGet` atomic operation to ensure that only the first request for a given OID establishes the node mapping, while subsequent concurrent requests receive the stored address.

### What is the difference between TypeMaster and other balancer types?

**TypeMaster** balancers write selected node addresses back to the route table using `MasterRouteTable.SetNxOrGet`, establishing persistent sticky sessions. Other types (such as `TypeReplica`) perform read-only route table lookups and weighted selection without persisting the choice, making them suitable for stateless or read-only operations.

### Why does the algorithm use float64 for weights instead of integers?

Float64 weights provide **fine-grained control** over traffic distribution, allowing weight ratios like 0.5:1 or 2.3:1. This precision enables more accurate proportional load balancing compared to integer-only systems, particularly useful when dealing with heterogeneous node capacities or gradual traffic shifting during deployments.

### Where is the route table implementation typically stored?

The route table interface abstracts the storage backend, with implementations available in subdirectories like `router/routetable/redis` or `router/routetable/postgresql`. The balancer interacts only with the `ReadOnlyRouteTable` or `MasterRouteTable` interfaces, making the storage backend pluggable while the core weighted round-robin logic remains storage-agnostic.