Deep Dive into Spring Cloud Ribbon: How Load Balancing Works Under the Hood

This article walks through the complete source‑code analysis of Spring Cloud Ribbon, covering dependency setup, RestTemplate integration, interceptor creation, load‑balancer execution flow, ILoadBalancer retrieval, server list management, ping strategies, and eager‑loading configuration to reveal how client‑side load balancing is implemented in Spring Cloud.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
Deep Dive into Spring Cloud Ribbon: How Load Balancing Works Under the Hood

Code Preparation

Dependency diagram:

+------------+              +------------+
|            |              |            |
|            |              |            |
|            |              |            |
|            |              |            |
|  consumer  +------------> |   provider |
|            | RestTemplate |            |
|            |              |            |
|            |              |            |
|            |              |            |
+------------+              +------------+

POM Dependency

Add Nacos service discovery, which internally pulls in spring-cloud-ribbon related dependencies.

<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

Calling the Client

Use a simple RestTemplate with Ribbon:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

// Controller uses RestTemplate to call provider
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://provider/req", String.class);

Source Code Analysis

1. Create Call Interceptor

Collect all RestTemplate beans annotated with @LoadBalanced:

public class LoadBalancerAutoConfiguration {
    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();
}

2. Add LoadBalancerInterceptor Logic

If spring-retry is not present:

@Bean
public LoadBalancerInterceptor ribbonInterceptor() {
    return new LoadBalancerInterceptor();
}

If spring-retry is present:

@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor() {
    return new RetryLoadBalancerInterceptor();
}

3. LoadBalancerInterceptor Business Logic

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept() {
        final URI originalUri = request.getURI();
        // Extract service name from URI, e.g., http://demo-provider/req
        String serviceName = originalUri.getHost();
        // Use the injected RibbonLoadBalancerClient to execute the request
        return this.loadBalancer.execute(serviceName,
                this.requestFactory.createRequest(request, body, execution));
    }
}

Executing the Interceptor

3. RibbonLoadBalancerClient Execution

@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
    return new RibbonLoadBalancerClient(springClientFactory());
}

4. execute Method

public class RibbonLoadBalancerClient implements LoadBalancerClient {
    public <T> T execute(){
        // Get concrete ILoadBalancer implementation
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        // Choose a server
        Server server = getServer(loadBalancer, hint);
        RibbonServer ribbonServer = new RibbonServer(serviceId, server,
                isSecure(server, serviceId),
                serverIntrospector(serviceId).getMetadata(server));
        // Record stats
        RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
        T returnVal = request.apply(serviceInstance);
        statsRecorder.recordStats(returnVal);
        return returnVal;
    }
}

Getting ILoadBalancer

5. SpringClientFactory

// Bean factory creates LoadBalancer implementation
protected ILoadBalancer getLoadBalancer(String serviceId) {
    return this.springClientFactory.getLoadBalancer(serviceId);
}

Bean definition for the Ribbon client (created lazily):

@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
        ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
        IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
    return new ZoneAwareLoadBalancer<>();
}

Key components and their default implementations:

IClientConfig – DefaultClientConfigImpl (client config such as timeouts)

ServerList – NacosServerList (list of service instances)

ServerListFilter – ZonePreferenceServerListFilter (filters server list)

IRule – ZoneAvoidanceRule (chooses a server)

IPing – DummyPing (checks server health)

ServerListUpdater – PollingServerListUpdater (updates the server list)

Getting Service Instance

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
    return loadBalancer.chooseServer(hint != null ? hint : "default");
}

Extension: ServerList Maintenance

Initialize ServerList (NacosServerList)

public class NacosServerList extends AbstractServerList<NacosServer> {
    @Override
    public List<NacosServer> getInitialListOfServers() {
        return getServers();
    }
    @Override
    public List<NacosServer> getUpdatedListOfServers() {
        return getServers();
    }
    private List<NacosServer> getServers() {
        String group = discoveryProperties.getGroup();
        List<Instance> instances = discoveryProperties.namingServiceInstance()
                .selectInstances(serviceId, group, true);
        return instancesToServerList(instances);
    }
}

Update ServerList (PollingServerListUpdater)

public class PollingServerListUpdater implements ServerListUpdater {
    @Override
    public synchronized void start(final UpdateAction updateAction) {
        final Runnable wrapperRunnable = () -> {
            updateAction.doUpdate();
            lastUpdated = System.currentTimeMillis();
        };
        scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                wrapperRunnable,
                initialDelayMs,
                refreshIntervalMs,
                TimeUnit.MILLISECONDS);
    }
}

Update action implementation:

public void doUpdate() {
    DynamicServerListLoadBalancer.this.updateListOfServers();
}

Extension: Server Status Maintenance

Ping Task Setup

public BaseLoadBalancer() {
    this.name = DEFAULT_NAME;
    this.ping = null;
    setRule(DEFAULT_RULE);
    // Start ping check task
    setupPingTask();
    lbStats = new LoadBalancerStats(DEFAULT_NAME);
}
void setupPingTask() {
    if (canSkipPing()) {
        return;
    }
    // Execute PingTask
    lbTimer.schedule(new BaseLoadBalancer.PingTask(), 0, pingIntervalSeconds * 1000);
    new BaseLoadBalancer.Pinger(pingStrategy).runPinger();
}

Serial Ping Strategy

private static class SerialPingStrategy implements IPingStrategy {
    @Override
    public boolean[] pingServers(IPing ping, Server[] servers) {
        int numCandidates = servers.length;
        boolean[] results = new boolean[numCandidates];
        for (int i = 0; i < numCandidates; i++) {
            results[i] = false; // Default answer is DEAD.
            if (ping != null) {
                results[i] = ping.isAlive(servers[i]);
            }
        }
        return results;
    }
}

URL‑Based Ping Implementation

public class PingUrl implements IPing {
    public boolean isAlive(Server server) {
        urlStr = urlStr + server.getId();
        urlStr = urlStr + this.getPingAppendString();
        HttpClient httpClient = new DefaultHttpClient();
        HttpUriRequest getRequest = new HttpGet(urlStr);
        HttpResponse response = httpClient.execute(getRequest);
        return response.getStatusLine().getStatusCode() == 200;
    }
}

Extension: RibbonClient Lazy Loading

By default Ribbon creates the LoadBalancer lazily on the first request, which can cause initial latency or time‑outs when combined with circuit breakers.

Configure eager loading to pre‑instantiate required Ribbon clients:

ribbon:
  eager-load:
    clients:
      - provider

The RibbonApplicationContextInitializer listens for the application‑ready event and forces the factory to create the specified clients:

public class RibbonApplicationContextInitializer implements ApplicationListener<ApplicationReadyEvent> {
    private final List<String> clientNames;
    protected void initialize() {
        if (clientNames != null) {
            for (String clientName : clientNames) {
                this.springClientFactory.getContext(clientName);
            }
        }
    }
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        initialize();
    }
}

References

Spring Cloud OpenFeign source analysis – https://juejin.im/post/5e46057f518825495b298cd1

Spring Cloud Ribbon source analysis – https://juejin.im/editor/posts/5e5c598551882549052f49e4

RBAC permission management system based on Spring Boot 2.2, Spring Cloud Hoxton & Alibaba, OAuth2 – https://github.com/pigxcloud/pig

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javaload balancingNacosSpring BootSpring CloudRibbon
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.