How to create Client Load Balancing with Spring Cloud Ribbon + Spring Boot

How to create Client Load Balancing with Spring Cloud Ribbon + Spring Boot

In the tutorial, JavaSampleApproach will show you the steps of creating a Client Load Balancing with Spring Cloud Ribbon.

Related articles:
How to configure SpringBoot Zuul – Routing and Filtering
How to start with Spring Cloud Centralized Configuration


I. Technologies

– Java 1.8
– Maven 3.3.9
– Spring Tool Suite – Version 3.8.1.RELEASE
– Spring Boot: 1.5.1.RELEASE

II. Load Balancer

1. Proxy Load Balancer

spring cloud ribbon - proxy load balancer

Advantage & Disadvantage:
– Centralized Load Balancing
– Can be bottle neck
– Single point of failure

2. Client Load Balancer

Spring Cloud Ribbon is a solution for Client Load Balancing.

spring cloud ribbon - ribbon load balancer

Advantage & Disadvantage:
– Decentralized Load Balancing
– No bottle neck
– Resilent
– Data can be inconsistent

III. Practice

Create 2 projects:
– a Spring Boot project for simple restful services, running at addresses: {localhost:8090,localhost:8091,localhost:8092}
– a Spring Boot project for Load BalancingRibbon Client, running at address: localhost:8080

Step to do:
– Create Spring Boot project for simple Restful service
– Create Spring Boot project for Ribbon client
– Config Ribbon client
– Create a Load Balancing Rest controller
– Run & check results

1. Create Spring Boot project for simple Restful service

– Using SpringToolSuite, create a SpringBoot project. Then add web dependency:


<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

– Create a simple RestController with 2 requestmappings:


package com.javasampleapproach.restservice.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class WebController {

	@Value("${server.port}")
	String port;

	@RequestMapping(value = "/")
	public String home() {
		return "Okay!";
	}

	@RequestMapping("/greeting")
	public String hello() {
		return "Hello from a service running at port: " + port + "!";
	}
}

Why having @RequestMapping(value = "/")?
-> The requestmapping is used by Ribbon client to detect alive servers. If not having, we may meet an exception – No instances available:


java.lang.IllegalStateException: No instances available for helloworld
	at org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute(RibbonLoadBalancerClient.java:90) ~[spring-cloud-netflix-core-1.2.5.RELEASE.jar:1.2.5.RELEASE]
	at org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor$1.doWithRetry(RetryLoadBalancerInterceptor.java:88) ~[spring-cloud-commons-1.1.7.RELEASE.jar:1.1.7.RELEASE]
	at org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor$1.doWithRetry(RetryLoadBalancerInterceptor.java:76) ~[spring-cloud-commons-1.1.7.RELEASE.jar:1.1.7.RELEASE]
	at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:286) ~[spring-retry-1.2.0.RELEASE.jar:na]

2. Create Spring Boot project for Ribbon client

– Using SpringToolSuite, create a SpringBoot project. Then add ribbon & web dependencies:

<dependencies>
    ....
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-ribbon</artifactId>
	</dependency>
	
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
    ...
</dependencies>

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>Camden.SR5</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

3. Config Ribbon client

Open application.properties file, configure Ribbon client:


spring:
  application:
    name: Ribbon-Client
    
helloworld:
  ribbon:
    eureka:
      enabled: false
    listOfServers: localhost:8090,localhost:8091,localhost:8092
    ServerListRefreshInterval: 1000
    
server:
  port: 8080

listOfServers defines a list of servers to load balancing.
ServerListRefreshInterval is the interval, between refreshes of Ribbon’s service list.

Create a Configuration class:


package com.javasampleapproach.ribbon.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AvailabilityFilteringRule;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.PingUrl;

public class Configuration {
	@Autowired
	IClientConfig ribbonClientConfig;

	@Bean
	public IPing ribbonPing(IClientConfig config) {
		return new PingUrl();
	}

	@Bean
	public IRule ribbonRule(IClientConfig config) {
		return new AvailabilityFilteringRule();
	}
}

IClientConfig is used to store client configuration.
IRule is used to define load balancing strategy
IPing is used to defines the way to ping a server to check if its alive.

In the main Spring Boot class, configure Ribbon client by @RibbonClient annotation:


package com.javasampleapproach.ribbon;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

import com.javasampleapproach.ribbon.config.Configuration;

@SpringBootApplication
@RibbonClient(name = "helloworld", configuration = Configuration.class)
public class SpringClientSideRibbonApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringClientSideRibbonApplication.class, args);
	}
}

4. Create a Load Balancing RestController

Use @LoadBalanced annotation to configure RestTemplate bean as a LoadBalancerClient.


package com.javasampleapproach.ribbon.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

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

	@Autowired
	RestTemplate restTemplate;

	@RequestMapping("/helloworld")
	public String home() {
		return this.restTemplate.getForObject("http://helloworld/greeting", String.class);
	}
}

5. Run & check results

Build the projects with SpringBoot App mode.
– Run Restful Services at: 8090, 8091, 8092:
mvn spring-boot:run -Dserver.port=8090
mvn spring-boot:run -Dserver.port=8091
mvn spring-boot:run -Dserver.port=8092

– Then run Ribbon Client.

-> Ribbon Client starts successfully with Log:


2017-05-05 16:25:19.915  INFO 5212 --- [nio-8080-exec-1] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client helloworld initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=helloworld,current list of Servers=[localhost:8090, localhost:8091, localhost:8092],Load balancer stats=Zone stats: {unknown=[Zone:unknown;	Instance count:3;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
},Server stats: [[Server:localhost:8090;	Zone:UNKNOWN;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 07:00:00 ICT 1970;	First connection made: Thu Jan 01 07:00:00 ICT 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
, [Server:localhost:8092;	Zone:UNKNOWN;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 07:00:00 ICT 1970;	First connection made: Thu Jan 01 07:00:00 ICT 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
, [Server:localhost:8091;	Zone:UNKNOWN;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 07:00:00 ICT 1970;	First connection made: Thu Jan 01 07:00:00 ICT 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:com.netflix.loadbalancer.ConfigurationBasedServerList@473fcfc6

– Make some GET requests: http://localhost:8080/helloworld. We can see sequence messages from services at {8090, 8091, 8092} by Round Robin rule for load balancing:

spring cloud ribbon - 8090

spring cloud ribbon - 8091

spring cloud ribbon - 8092

Some common exceptions you may meet:
-> If you turn down a restful server (they are running at ports {8090, 8091, 8092}), you will meet a connection refused exception:


java.net.ConnectException: Connection refused: connect
	at java.net.DualStackPlainSocketImpl.connect0(Native Method)
	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)

-> If you turn down all servers, you will meet a No up servers available exception:


2017-05-05 16:18:39.150  WARN 10360 --- [nio-8080-exec-1] com.netflix.loadbalancer.RoundRobinRule  : No up servers available from load balancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=helloworld,current list of Servers=[localhost:8090, localhost:8091, localhost:8092],Load balancer stats=Zone stats: {unknown=[Zone:unknown;	Instance count:3;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
},Server stats: [[Server:localhost:8090;	Zone:UNKNOWN;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 07:00:00 ICT 1970;	First connection made: Thu Jan 01 07:00:00 ICT 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
, [Server:localhost:8092;	Zone:UNKNOWN;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 07:00:00 ICT 1970;	First connection made: Thu Jan 01 07:00:00 ICT 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
, [Server:localhost:8091;	Zone:UNKNOWN;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 07:00:00 ICT 1970;	First connection made: Thu Jan 01 07:00:00 ICT 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:com.netflix.loadbalancer.ConfigurationBasedServerList@209790e
2017-05-05 16:18:39.159 ERROR 10360 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: No instances available for helloworld] with root cause

java.lang.IllegalStateException: No instances available for helloworld
	at org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute(RibbonLoadBalancerClient.java:90) ~[spring-cloud-netflix-core-1.2.5.RELEASE.jar:1.2.5.RELEASE]
	at org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor$1.doWithRetry(RetryLoadBalancerInterceptor.java:88) ~[spring-cloud-commons-1.1.7.RELEASE.jar:1.1.7.RELEASE]
	at org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor$1.doWithRetry(RetryLoadBalancerInterceptor.java:76) ~[spring-cloud-commons-1.1.7.RELEASE.jar:1.1.7.RELEASE]
	at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:286) ~[spring-retry-1.2.0.RELEASE.jar:na]

IV. SourceCode

RestfulService
SpringCloudRibbon



By grokonez | May 5, 2017.

Last updated on April 22, 2021.



Related Posts


5 thoughts on “How to create Client Load Balancing with Spring Cloud Ribbon + Spring Boot”

  1. Hello,
    How exactly Configuration class is getting used here? ribbonPing or ribbonRule methods are getting called?
    Thanks

    1. Hello Deepika Vashishtha,

      IClientConfig is used to store client configuration.
      IRule is used to define load balancing strategy
      IPing is used to defines the way to ping a server to check if its alive.

      Regards,
      JSA

Got Something To Say:

Your email address will not be published. Required fields are marked *

*