How Xray-core's Balancer Override Feature Enables Dynamic Load Balancing Changes
Xray-core's balancer override feature lets operators change load balancing targets at runtime via API calls without restarting the proxy or editing configuration files.
The balancer override mechanism in XTLS/Xray-core bridges the gap between static configuration and operational flexibility. Proxy administrators often need to reroute traffic during incidents, maintenance windows, or performance optimizations—this feature solves that by injecting runtime overrides directly into the routing layer. This article breaks down the implementation from API surface to core routing logic, referencing actual source paths and method signatures from the Xray-core repository.
API Entry Point: The api bo Command
The override feature exposes a clean CLI interface through main/commands/all/api/balancer_override.go. This command constructs and dispatches gRPC requests to the internal routing service.
Command Structure
# Force a specific balancer to use a designated outbound
xray api bo -b <balancer_tag> <outbound_tag>
# Remove the override and restore normal load balancing
xray api bo -b <balancer_tag> -r
The -b flag specifies the balancer tag to target, while the positional argument or -r flag determines whether to set or clear the override.
gRPC Client Construction
Inside balancer_override.go, the command instantiates a RoutingService client and builds an OverrideBalancerTargetRequest:
request := &command.OverrideBalancerTargetRequest{
BalancerTag: *balancerTag,
Target: args.First(),
}
The Target field carries the forced outbound tag—or remains empty when removing an override.
RoutingService RPC Implementation
The server-side handling lives in app/router/command/command.go. The OverrideBalancerTarget method receives requests and delegates to the underlying router through the routing.BalancerOverrider interface.
Type Assertion and Delegation
func (s *RoutingService) OverrideBalancerTarget(ctx context.Context, request *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error) {
if bo, ok := s.router.(routing.BalancerOverrider); ok {
return &OverrideBalancerTargetResponse{}, bo.SetOverrideTarget(request.BalancerTag, request.Target)
}
return nil, errors.New("router does not implement BalancerOverrider")
}
This design maintains clean separation: the command layer handles transport, while the router layer manages state.
Core Router: Override Storage and Retrieval
The app/router/balancing.go file implements the actual override logic. The Router struct maintains a map of balancers[tag]*Balancer, and each Balancer carries a private override field.
SetOverrideTarget Implementation
func (r *Router) SetOverrideTarget(tag, target string) error {
if b, ok := r.balancers[tag]; ok {
b.override.Put(target) // thread-safe write
return nil
}
return errors.New("cannot find tag: " + tag)
}
The Put method synchronizes access to prevent race conditions during concurrent API calls and routing decisions.
GetOverrideTarget Implementation
func (r *Router) GetOverrideTarget(tag string) (string, bool) {
if b, ok := r.balancers[tag]; ok {
return b.override.Get(), true
}
return "", false
}
This method supports the api bi (balancer-info) command for operational visibility.
Thread-Safe Override Data Structure
The app/router/balancing_override.go file defines the override struct, which wraps a string with sync.RWMutex protection.
Implementation Details
type override struct {
target string
mu sync.RWMutex
}
func (o *override) Get() string {
o.mu.RLock()
defer o.mu.RUnlock()
return o.target
}
func (o *override) Put(target string) {
o.mu.Lock()
defer o.mu.Unlock()
o.target = target
}
func (o *override) Clear() {
o.Put("")
}
Read-heavy operations (routing decisions) use RLock for concurrency, while write operations (API updates) acquire exclusive locks.
Balancing Decision Point: PickOutbound
The critical integration occurs in app/router/balancing.go within the PickOutbound method. Every routing decision checks the override before consulting the load-balancing strategy.
Override Short-Circuit Logic
func (b *Balancer) PickOutbound(candidates []string) (string, error) {
// Check for active override first
if o := b.override.Get(); o != "" {
return o, nil //forced selection, bypass strategy
}
// No override: delegate to configured strategy
return b.strategy.PickOutbound(candidates)
}
When an override exists, the balancer returns it immediately. When empty or cleared, normal strategy behavior resumes—whether round-robin, least-latency, random, or custom implementations.
Operational Visibility: The api bi Command
The main/commands/all/api/balancer_info.go command complements the override feature by exposing current state. It calls GetOverrideTarget and displays both the active override and the strategy's normal selection.
Typical Output
Balancer Tag: myBalancer
Strategy: roundRobin
Override Target: fast-proxy (active)
Principle Target: server-03
This visibility prevents configuration drift and aids incident response.
Practical Code Examples
CLI Override Operations
# Emergency traffic diversion: force all traffic through emergency proxy
xray api bo -b production-balancer emergency-proxy
# Maintenance complete: restore automatic load balancing
xray api bo -b production-balancer -r
# Verify current state
xray api bi -b production-balancer
Programmatic Override in Go
package main
import (
"context"
"log"
"time"
"github.com/xtls/xray-core/app/router/command"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := command.NewRoutingServiceClient(conn)
// Apply override: force balancer to use "direct" outbound
_, err = client.OverrideBalancerTarget(ctx, &command.OverrideBalancerTargetRequest{
BalancerTag: "api_balancer",
Target: "direct",
})
if err != nil {
log.Fatal("override failed:", err)
}
log.Println("Override applied successfully")
// Clear override to restore normal behavior
_, err = client.OverrideBalancerTarget(ctx, &command.OverrideBalancerTargetRequest{
BalancerTag: "api_balancer",
Target: "", // empty string clears override
})
if err != nil {
log.Fatal("clear failed:", err)
}
log.Println("Override cleared, normal balancing restored")
}
Key Implementation Files
| File | Purpose |
|---|---|
main/commands/all/api/balancer_override.go |
CLI command parsing and gRPC client construction |
app/router/command/command.go |
RoutingService RPC handler with OverrideBalancerTarget |
app/router/balancing.go |
Core router with SetOverrideTarget, GetOverrideTarget, and PickOutbound override logic |
app/router/balancing_override.go |
Thread-safe override struct with sync.RWMutex |
main/commands/all/api/balancer_info.go |
Status query command for operational visibility |
Summary
- Xray-core's balancer override enables runtime traffic redirection without process restarts or configuration reloads
- The feature flows through CLI → gRPC → RoutingService → Router → Balancer with clean interface boundaries
- Thread-safe storage via
sync.RWMutexprotects concurrent reads (routing decisions) and writes (API updates) - Override short-circuits the normal
PickOutboundstrategy, forcing a specific outbound tag when active - Empty target clears the override, immediately restoring the configured load-balancing strategy
Frequently Asked Questions
How do I check if a balancer override is currently active?
Use the xray api bi -b <balancer_tag> command or call the GetBalancerInfo RPC. The response includes both the override field (active forced target) and the principleTarget (what the strategy would normally select).
Does setting an override persist across Xray-core restarts?
No. The balancer override is purely in-memory. When the process restarts, all overrides are cleared and balancing resumes according to the static configuration file. For persistent changes, modify the config and restart.
Can multiple balancers have independent overrides simultaneously?
Yes. Each balancer maintains its own override struct. The balancers map in app/router/balancing.go stores per-balancer state, so you can force balancer-A to outbound-1 while balancer-B continues normal operation or points to outbound-2.
What happens to existing connections when an override is applied?
Existing connections are not affected. The override only influences new routing decisions made after the SetOverrideTarget call completes. Established TCP streams, UDP sessions, and active WebSocket connections continue through their originally selected outbound.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →