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.

CodeSmart Hoops
CodeSmart Hoops
CodeSmart Hoops
Spring Cloud LoadBalancer: 8 Classic Interview Questions with Deep-Dive Answers

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: ServerListServiceInstanceListSupplier, IPingHealthCheckServiceInstanceListSupplier, IRuleReactorServiceInstanceLoadBalancer, ServerServiceInstance.

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.

OpenFeign call chain diagram
OpenFeign call chain diagram

Reference : [Section 9: OpenFeign Integration]

Q4: LoadBalancerClientFactory — why maintain an independent child context per service? What benefits?

LoadBalancerClientFactory

extends 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.

ServiceInstanceListSupplier

is 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
           └── NacosDiscoveryClient

Layer 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:

ServiceInstanceListSupplier chain diagram
ServiceInstanceListSupplier chain diagram

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: 5s

Enable health check :

spring:
  cloud:
    loadbalancer:
      health-check:
        interval: 25s
        path:
          default: actuator/health

Graceful 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"

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.

Nacostroubleshootinginterview questionsOpenFeignconsistent hashingRibbonsmooth weighted round-robinload balancing algorithmsclient-side load balancingSpring Cloud LoadBalancer
CodeSmart Hoops
Written by

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.

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.