Spring Cloud LoadBalancer: 8 Classic Interview Questions with Deep-Dive Answers
This article presents eight detailed interview questions covering Spring Cloud LoadBalancer internals, including Ribbon migration reasons, client vs server load balancing, OpenFeign call chain, LoadBalancerClientFactory child contexts, ServiceInstanceListSupplier decorator chain, smooth weighted round-robin algorithm, consistent hashing with virtual nodes, and troubleshooting Connection refused errors after instance shutdown.
Interview Self-Test
Q1 : Why did Spring Cloud deprecate Ribbon in favor of LoadBalancer? What Ribbon problems were solved? — Reference: Section 4: Ribbon to LoadBalancer Evolution
Q2 : What is the fundamental difference between client-side and server-side load balancing (Nginx/LVS)? What scenarios suit each? — Reference: Section 3: Client-side vs Server-side Load Balancing
Q3 : Describe the complete chain of an OpenFeign call from request initiation to instance selection. Which core components are involved? — Reference: Section 5: Core Architecture + Section 9: OpenFeign Integration
Q4 : Why does LoadBalancerClientFactory maintain an independent child context per service? What benefits does this bring? — Reference: Section 5: Core Architecture (5.3 Source Code Flow)
Q5 : How does the ServiceInstanceListSupplier chain work? Draw the DiscoveryClient → HealthCheck → Caching chain. — Reference: Section 11: Instance List Caching and Refresh
Q6 : Write the core logic of the smooth weighted round-robin algorithm and explain why it is "smoother" than naive weighted round-robin. — Reference: Section 10: Load Balancing Algorithm Deep Dive (10.4)
Q7 : How does consistent hashing solve traffic migration when instances are added or removed? What is the role of virtual nodes? — Reference: Section 10: Load Balancing Algorithm Deep Dive (10.5)
Q8 : After a production instance goes offline, the caller still reports Connection refused. What are the possible causes and troubleshooting steps? — Reference: Section 13: Pitfall Guide (Pit 1, Pit 2)
Q1: Why did Spring Cloud deprecate Ribbon in favor of LoadBalancer? What Ribbon problems were solved?
Four direct reasons:
Maintenance stopped : Netflix announced Ribbon entering maintenance mode in ribbon#1115, no longer accepting new features or PRs; community essentially stalled.
Heavy RxJava dependency : A load balancing library dragged an entire reactive stack, increasing both size and cognitive burden.
Blocking design : Ribbon's ServerList, IPing, IRule are all synchronous blocking interfaces, unable to collaborate elegantly with WebFlux.
Community ownership : Spring Cloud wanted to place common abstractions into spring-cloud-commons rather than depend on Netflix implementations.
Problems solved by LoadBalancer:
Blocking → Provides ReactiveLoadBalancer reactive SPI; synchronous scenarios use BlockingLoadBalancerClient as a bridge.
Depends on RxJava → Based on Reactor, consistent with Spring 5 ecosystem.
Maintenance stalled → Maintained by Spring Cloud team, evolves with mainline versions.
Abstractions scattered in Netflix packages → Abstractions moved into spring-cloud-commons, unified entry point.
Child context isolation granularity coarse → LoadBalancerClientFactory gives each service its own child context, clearer configuration isolation.
Concept mapping: ServerList → ServiceInstanceListSupplier, IPing → HealthCheckServiceInstanceListSupplier, IRule → ReactorServiceInstanceLoadBalancer, Server → ServiceInstance.
Reference : [Section 4: Ribbon to Spring Cloud LoadBalancer Evolution]
Q2: What is the fundamental difference between client-side and server-side load balancing (Nginx/LVS)? What scenarios suit each?
Fundamental difference : Decision point location differs.
Client-side LB : Decision made inside caller process; caller pulls instance list from registry, selects, and connects directly to target instance — only one hop .
Server-side LB : Caller only connects to LB address; LB selects a backend and forwards — two hops .
Comparison:
Network hops : Client-side LB = 1 hop; Server-side LB = 2 hops
Single point risk : Client-side LB = None; Server-side LB = LB itself may become single point
Policy flexibility : Client-side LB = High, customizable per caller; Server-side LB = Medium, centralized in LB config
Multi-language : Client-side LB = Java only; Server-side LB = Language agnostic
Deployment : Client-side LB = Co-located with business process; Server-side LB = Requires separate LB cluster maintenance
Traffic governance : Client-side LB = Can read registry metadata for fine-grained routing; Server-side LB = Requires separate upstream configuration
Applicable scenarios :
Client-side LB: Microservice internal RPC (within Spring Cloud ecosystem), need business-tag routing, seamless integration with OpenFeign / WebClient.
Server-side LB: Layer 4/7 ingress traffic, TLS offloading, cross-language backends, external traffic ingress.
In practice, both are used together : Edge traffic handled by Nginx/LVS; after entering business gateway, microservices use Spring Cloud LoadBalancer for client-side load balancing.
Reference : [Section 3: Client-side vs Server-side Load Balancing]
Q3: Describe the complete chain of an OpenFeign call from request initiation to instance selection. Which core components are involved?
Complete chain (blocking scenario example):
OpenFeign initiates call : UserServiceClient.getUser(id) triggers JDK dynamic proxy, enters FeignInvocationHandler.
Enter Client.execute : Default Client is FeignBlockingLoadBalancerClient (auto-wrapped when LoadBalancerClient is on classpath).
Call LoadBalancerClient.choose : Pass serviceId (e.g., user-service) and Request.
Get LoadBalancer from factory : BlockingLoadBalancerClient calls LoadBalancerClientFactory.getInstance(serviceId), retrieves corresponding ReactiveLoadBalancer from that service's child context.
Reactive choose : ReactiveLoadBalancer.choose(request) returns Mono<Response<ServiceInstance>>.
Fetch instance list : LoadBalancer internally calls ServiceInstanceListSupplier.get(), chain walks to DiscoveryClientServiceInstanceListSupplier, finally queries NacosDiscoveryClient, returns instance list.
Strategy decision : RoundRobinLoadBalancer (or custom strategy) picks one from list, wraps as DefaultResponse.
Block back to synchronous : BlockingLoadBalancerClient blocks Mono to synchronous result, obtains ServiceInstance.
Rewrite URL : Use instance.getUri() + original Request path to rewrite Request.url.
Send real request : Delegate to underlying Client (default HttpURLConnection, swappable to OkHttp/Apache HttpClient) to issue HTTP request.
Return response : Response passed back to Feign's Decoder to decode into method return type.
Core components involved: FeignBlockingLoadBalancerClient, BlockingLoadBalancerClient, LoadBalancerClientFactory, ReactiveLoadBalancer, ServiceInstanceListSupplier, DiscoveryClient, NacosDiscoveryClient.
Reference : [Section 9: OpenFeign Integration]
Q4: LoadBalancerClientFactory — why maintain an independent child context per service? What benefits?
LoadBalancerClientFactoryextends NamedContextFactory; each service name maps to an independent Spring child context . Its getInstance(serviceId) retrieves the ReactiveLoadBalancer Bean from that service's dedicated context.
Why this design:
Strategy isolation : Different services can use different load balancing strategies. E.g., user-service uses round-robin, order-service uses weighted random; each specifies via @LoadBalancerClient(name=..., configuration=...) without interference.
Configuration isolation : Each service can independently configure ServiceInstanceListSupplier chain, cache TTL, health check path.
Instance list isolation : Different services' instance lists and caches are maintained separately; one service's instance refresh doesn't affect another.
Bean reuse : Same strategy Bean definition can, via child context mechanism, generate independent Bean instances (each with its own serviceId) for different services.
Benefit example: Use @LoadBalancerClients to assign different configuration classes to multiple services:
@Configuration
@LoadBalancerClients({
@LoadBalancerClient(name = "user-service",
configuration = WeightedLoadBalancerConfiguration.class),
@LoadBalancerClient(name = "order-service",
configuration = ConsistentHashLoadBalancerConfiguration.class)
})
public class LoadBalancerConfig {}Key note : Custom configuration classes must not be annotated with @Configuration to avoid being scanned by the main context; otherwise they would be shared across all services, breaking isolation. They are only passed as values to @LoadBalancerClient.configuration, instantiated separately per service by the factory.
Reference : [Section 5: Core Architecture]
Q5: How does the ServiceInstanceListSupplier chain work? Draw the DiscoveryClient → HealthCheck → Caching chain.
ServiceInstanceListSupplieris a provider returning Flux<List<ServiceInstance>>. Spring Cloud LoadBalancer uses the decorator pattern to chain them; each layer adds processing on top of upstream:
CachingServiceInstanceListSupplier
└── HealthCheckServiceInstanceListSupplier
└── DiscoveryClientServiceInstanceListSupplier
└── NacosDiscoveryClientLayer responsibilities:
DiscoveryClientServiceInstanceListSupplier : Bottom layer; calls DiscoveryClient.getInstances to pull instances from Nacos.
HealthCheckServiceInstanceListSupplier : Periodically pings instance health endpoint (default actuator/health), filters out unhealthy instances.
CachingServiceInstanceListSupplier : Outermost layer; caches upstream result, default TTL 35s, reduces upstream pressure.
Call chain:
Key parameters : spring.cloud.loadbalancer.cache.ttl: Cache TTL, default 35s (production recommended 5~10s). spring.cloud.loadbalancer.health-check.interval: Health check interval, default 25s. spring.cloud.loadbalancer.health-check.path.default: Health endpoint path.
Reference : [Section 11: Instance List Caching and Refresh]
Q6: Write the core logic of the smooth weighted round-robin algorithm and explain why it is "smoother" than naive weighted round-robin.
Let instance i have fixed weight weight[i], dynamic weight current[i], total weight total = sum(weight). Each request:
For each i, current[i] += weight[i].
Select best = argmax(current[i]). current[best] -= total.
Return instances[best].
Java implementation:
public synchronized ServiceInstance choose() {
int total = weights.stream().mapToInt(Integer::intValue).sum();
for (int i = 0; i < current.size(); i++) {
current.set(i, current.get(i) + weights.get(i));
}
int best = 0;
for (int i = 1; i < current.size(); i++) {
if (current.get(i) > current.get(best)) best = i;
}
current.set(best, current.get(best) - total);
return instances.get(best);
}Example with weight = [5, 1, 1], total = 7; 7-round selection sequence: A, A, B, A, C, A, A — A selected 5 times, B and C once each, matching 5:1:1 with relatively even distribution.
Why "smooth" :
Naive weighted round-robin : Replicates instances by weight into candidate array [A,A,A,A,A,B,C] then round-robins, yielding A,A,A,A,A,B,C — five consecutive A's, instantaneous pressure concentration.
Smooth weighted round-robin : Through dynamic current weights and subtraction of total, high-weight instance requests are spread across multiple rounds , avoiding consecutive hits.
Mathematical intuition: Subtracting total after selection is equivalent to "borrowing" one weight from every instance; next round other instances' current relatively grow, making them more likely to be chosen — forming self-balancing.
Reference : [Section 10: Load Balancing Algorithm Deep Dive]
Q7: How does consistent hashing solve traffic migration when instances are added or removed? What is the role of virtual nodes?
Core idea : Map both instances and request keys onto the same hash ring ( 0 ~ 2^32-1); request key walks clockwise to find the next instance as target.
Construction and selection:
Build ring:
for each instance:
for v in [0, VIRTUAL_NODES):
ring[hash(instance + "#" + v)] = instance
Select instance:
h = hash(requestKey)
next = ring.ceiling(h) or ring.first()
return ring[next]Traffic migration on instance changes :
Add instance X : Only inserts X's virtual nodes on ring; only keys falling in the segment before X's nodes remap to X; other keys unchanged.
Remove instance X : X's virtual nodes removed; keys originally landing on X move clockwise to next instance; other keys unchanged.
Compared to modulo hashing ( hash(key) % N): When N changes, almost all keys remap, cache entirely invalidated. Consistent hashing limits impact to adjacent segments , key to cache affinity.
Virtual node roles :
Balanced distribution : With few instances (e.g., 3), only 3 points on ring — extremely uneven; adding 150 virtual nodes per instance yields 450 points, distribution approaches uniform.
Reduce migration ratio : More virtual nodes → single instance change affects key ratio closer to theoretical 1/N.
Mitigate data skew : Prevents certain instances bearing far above average traffic.
Typical config: 150 virtual nodes per instance (early Memcached recommendation).
Use cases : Session stickiness (same userId lands on same instance), local cache affinity, sharding routing.
Reference : [Section 10: Load Balancing Algorithm Deep Dive]
Q8: After production instance goes offline, caller still reports Connection refused . Possible causes and troubleshooting?
Possible causes :
Cache TTL too long : spring.cloud.loadbalancer.cache.ttl default 35s; instance may still be selected up to 35s after going offline.
Health check interval too long : HealthCheckServiceInstanceListSupplier default 25s interval; slow detection.
Nacos client cache not refreshed : Nacos client long-polling has latency; or instance not truly deregistered (process killed with kill -9 without heartbeat, waits 30s for removal).
Instance not gracefully shut down : Process exits without deregistering from Nacos; registry still thinks it's online.
DiscoveryClientServiceInstanceListSupplier not configured with cache refresh : Chain configuration incomplete.
Troubleshooting steps :
Check Nacos console : Is the instance deregistered/removed on registry side?
Already removed but caller still errors → client cache issue, see steps 2,3.
Still online → registry unaware, process didn't gracefully shut down.
Check LoadBalancer cache TTL : Is spring.cloud.loadbalancer.cache.ttl too large?
Check health check config : Is HealthCheckServiceInstanceListSupplier enabled? What's the interval?
Check Nacos client logs : Receiving pushes? Long-polling normal?
Packet capture or actuator : Confirm whether caller's instance list still contains offline instance.
Countermeasures :
Reduce cache TTL : Production recommended 5~10s.
spring:
cloud:
loadbalancer:
cache:
enabled: true
ttl: 5sEnable health check :
spring:
cloud:
loadbalancer:
health-check:
interval: 25s
path:
default: actuator/healthGraceful shutdown on server side : Deregister from Nacos first → sleep one TTL → then exit process.
@PreDestroy
public void shutdown() throws InterruptedException {
nacosServiceManager.deregister(serviceName, ip, port);
Thread.sleep(10_000); // wait for client cache expiry
}Client-side retry fallback : Enable LoadBalancer retry, max-retries-on-next-service-instance: 1, auto-switch on failure.
Reference : [Section 13: Pitfall Guide]
References
Spring Cloud LoadBalancer Official Docs: https://docs.spring.io/spring-cloud-commons/reference/spring-cloud-commons/loadbalancer.html
Netflix Ribbon Maintenance Mode Announcement: https://github.com/Netflix/ribbon/issues/1115
Nginx Smooth Weighted Round-Robin Algorithm: https://github.com/phusion/nginx/commit/tengine
Consistent Hashing Paper: Karger et al., "Consistent Hashing and Random Trees"
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.
CodeSmart Hoops
A working programmer who loves coding and basketball. By day I debug code; by night I dissect tactics. I write articles to document my journey, focusing on Java, AI, Python and other programming topics, with occasional posts about basketball, English, and books. Hope it's helpful—thanks for following and support.
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.
