Connection Picker in fabrica-kit: Bridging Kratos and gRPC Load Balancing

The connection picker in fabrica-kit is a gRPC balancer.Picker implementation that integrates the Kratos selector load-balancing algorithm with gRPC's client-side balancer, determining which sub-connection handles each RPC call.

The connection picker serves as the critical integration point in the fabrica-kit router package, enabling intelligent request routing for microservices. By bridging the Kratos selector framework with gRPC's native balancer interface, it allows applications to leverage sophisticated load-balancing strategies including weighted round-robin and route table-based master/reader separation.

What Is the Connection Picker?

In fabrica-kit, the connection picker is implemented in router/balancer/picker.go as a concrete implementation of the gRPC balancer.Picker interface. Unlike standard gRPC pickers that rely solely on gRPC's built-in load balancing, this picker delegates selection logic to the Kratos selector — a pluggable load-balancing abstraction that supports multiple algorithms including weighted round-robin and consistent hashing.

The picker operates within the gRPC balancer lifecycle: when a gRPC client creates a connection via conn.NewConn (defined in router/conn/conn.go), the appropriate balancer is registered through balancer.RegisterMasterBalancer or RegisterReadOnlyBalancer. The picker is then instantiated by the picker builder when sub-connections become ready.

How the Connection Picker Works

The connection picker functionality spans three primary components that work together to route RPCs to appropriate service instances.

Balancer Registration

The registration process begins in router/balancer/register.go. When RegisterMasterBalancer or RegisterReadOnlyBalancer is called, the system creates a Kratos selector.Builder and registers a gRPC balancer that uses a custom picker builder:

// Conceptual flow based on register.go
balancer.RegisterMasterBalancer(serviceName, logger, routeTable, discovery)
// This internally creates a selector.Builder and registers the balancer
// with a picker builder that understands Kratos nodes.

The registration binds the service name to a specific balancer configuration, ensuring that when gRPC resolves the service address, it uses the fabrica-kit connection picker rather than the default round-robin implementation.

Picker Builder Construction

The newPickerBuilder function in router/balancer/pickerbuilder.go implements the gRPC base.PickerBuilder interface. Its Build method receives the ready sub-connections (info.ReadySCs) and constructs the picker:

// From pickerbuilder.go - conceptual structure
func (pb *pickerBuilder) Build(info base.PickerBuildInfo) balancer.Picker {
    nodes := make([]selector.Node, 0, len(info.ReadySCs))
    for subConn, info := range info.ReadySCs {
        // Wrap each SubConn into a grpcNode that implements selector.Node
        node := &grpcNode{
            subConn: subConn,
            address: info.Address,
            // ... other metadata
        }
        nodes = append(nodes, node)
    }
    
    // Create the picker with the selector populated with nodes
    p := &picker{
        selector: pb.builder.Build(), // Kratos selector.Builder
    }
    p.selector.Apply(nodes) // Populate the selector with wrapped nodes
    return p
}

Each gRPC SubConn is wrapped in a grpcNode struct that implements the Kratos selector.Node interface, allowing the Kratos selector to treat gRPC connections as part of its load-balancing pool.

The Pick Method

The core selection logic resides in router/balancer/picker.go within the Pick method. When gRPC needs to route an RPC, it calls this method:

// From picker.go - simplified structure
func (p *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
    // Extract node filters from context (e.g., from gRPC transport)
    filters := gtr.NodeFilters(info.Ctx)
    
    // Delegate to Kratos selector
    node, done, err := p.selector.Select(info.Ctx, selector.WithNodeFilter(filters...))
    if err != nil {
        return balancer.PickResult{}, err
    }
    
    // Cast to our wrapped node type
    grpcNode := node.(*grpcNode)
    
    // Construct the result with the chosen SubConn
    result := balancer.PickResult{
        SubConn: grpcNode.subConn,
        Done: func(doneInfo balancer.DoneInfo) {
            // Forward metrics to Kratos selector
            done(doneInfo.BytesSent, doneInfo.BytesReceived, doneInfo.Trailer, doneInfo.Err)
        },
    }
    return result, nil
}

The picker extracts any node filters attached to the RPC context, invokes the Kratos selector's Select method (which applies weighted round-robin or other algorithms), and returns the appropriate SubConn. After the RPC completes, the Done callback forwards telemetry data (bytes sent/received, trailers, errors) back to the Kratos selector for circuit-breaking and metrics collection.

End-to-End Usage Example

To utilize the connection picker in a service client, you instantiate a connection through the fabrica-kit router package:

package main

import (
    "context"
    "log"
    
    "github.com/go-pantheon/fabrica-kit/router/balancer"
    "github.com/go-pantheon/fabrica-kit/router/conn"
    "github.com/go-kratos/kratos/v2/log"
    "github.com/go-kratos/kratos/v2/registry"
)

func main() {
    // Initialize logger, route table, and service discovery
    var (
        logger     log.Logger
        routeTable routetable.ReadOnlyRouteTable
        discovery  registry.Discovery
    )
    
    // Create a connection using the master balancer (which includes the connection picker)
    c, err := conn.NewConn(
        "order-service",           // target service name
        balancer.TypeMaster,       // uses RegisterMasterBalancer
        logger,
        routeTable,
        discovery,
    )
    if err != nil {
        log.Fatal(err)
    }
    defer c.Close()
    
    // Create gRPC client
    client := pb.NewOrderServiceClient(c)
    
    // Execute RPC - the connection picker automatically selects the optimal sub-connection
    resp, err := client.CreateOrder(context.Background(), req)
    if err != nil {
        log.Fatal(err)
    }
}

In this flow, conn.NewConn triggers balancer.RegisterMasterBalancer, which registers the connection picker with gRPC. When client.CreateOrder is invoked, gRPC internally calls the picker's Pick method to determine which service instance handles the request.

Summary

  • The connection picker is a custom gRPC balancer.Picker implementation located in router/balancer/picker.go that integrates Kratos selector logic with gRPC load balancing.
  • It is constructed by newPickerBuilder in router/balancer/pickerbuilder.go, which wraps gRPC SubConn instances into Kratos-compatible grpcNode objects.
  • The Pick method delegates selection to the Kratos selector, supporting weighted round-robin and node filtering, then returns the chosen SubConn with a Done callback for metrics collection.
  • Registration occurs through balancer.RegisterMasterBalancer or RegisterReadOnlyBalancer in router/balancer/register.go, enabling transparent integration when using conn.NewConn.

Frequently Asked Questions

How does the connection picker differ from a standard gRPC round-robin picker?

The connection picker in fabrica-kit differs from standard gRPC pickers by delegating selection logic to the Kratos selector framework. While gRPC's default round-robin picker maintains its own connection pool logic, the fabrica-kit picker wraps each gRPC SubConn in a grpcNode struct and uses Kratos's selector.Select method. This enables advanced features like weighted round-robin algorithms, node filtering based on context metadata, and integration with route tables for master/reader separation that standard gRPC pickers do not provide natively.

What happens if the connection picker cannot find an available node?

If the Kratos selector cannot select a suitable node—whether due to circuit breaker states, health check failures, or empty node lists—the Pick method in router/balancer/picker.go returns the error directly from p.selector.Select(). This error propagates to the gRPC client, causing the RPC to fail immediately with the selector's error message. The picker does not implement retry logic at this level; retry policies should be configured at the gRPC client level or handled by the calling application.

Can I use the connection picker with read-only replicas?

Yes, the connection picker supports read-only replicas through the RegisterReadOnlyBalancer function in router/balancer/register.go. When creating a connection via conn.NewConn, you specify balancer.TypeReadOnly instead of TypeMaster. This registers a picker configured to route requests to read replica nodes rather than master nodes. The picker builder creates nodes based on the route table's read replica entries, and the selector applies the same weighted round-robin logic to distribute read traffic across available replicas while respecting any node filters provided in the RPC context.

How does the picker collect metrics after an RPC completes?

The picker collects metrics through the Done callback returned in the balancer.PickResult. When the RPC finishes, gRPC invokes this callback with DoneInfo containing bytes sent, bytes received, trailers, and any error. The picker's Done function (defined in router/balancer/picker.go) forwards these metrics to the Kratos selector's done callback, which was returned during the Select call. This allows the selector to update circuit breaker states, record latency metrics, and adjust node weights based on actual RPC performance data.

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 →