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.
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:
- providerThe 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
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
