Spring Cloud Netflix: Load Balancer with Ribbon/Feign
The idea in this post is to show some concepts about load balancing, Ribbon and Feign, and step by step process for working with Ribbon and Feign.
Join the DZone community and get the full member experience.
Join For FreeYou can use Spring Cloud Netflix to provide client-side load balancing in calls to another microservice.
The idea in this post is to show some concepts about load balancing, Ribbon and Feign, and step by step process for working with Ribbon and Feign.
Load Balancing
Load Balancing automatically distributes incoming application traffic between two or more computers. It enables you to achieve fault tolerance in your applications, seamlessly providing the required amount of load balancing capacity needed to route application traffic. Load balancing aims to optimize resource use, maximize throughput, minimize response time, and avoid overload of any single resource. Using multiple components with load balancing instead of a single component may increase reliability and availability through redundancy.
Ribbon: Load Balancer Without Eureka
Ribbon is a client-side load balancer, which gives you a lot of control over the behavior of HTTP and TCP clients. Ribbon's Client component offers a good set of configuration options such as connection timeouts, retries, retry algorithm (exponential, bounded back off) etc. Ribbon comes built in with a pluggable and customizable Load Balancing component.
Some of the load balancing strategies offered are listed below:
- Simple Round Robin LB
- Weighted Response Time LB
- Zone Aware Round Robin LB
- Random LB
Client-Side Load Balancing
An approach to load balancing is to deliver a list of server IPs to the client, and then to have client randomly select the IP from the list on each connection. It has been claimed that client-side random load balancing tends to provide better load distribution than round-robin DNS. With this approach, the method of delivery of lists of IPs to the client can vary, and may be implemented as a DNS list (delivered to all the clients without any round-robin), or via hardcoding it to the list. If a "smart client" is used, detecting that randomly selected server is down and connecting randomly again, it also provides fault tolerance.
Ribbon provides the following features:
- Load balancing
- Fault tolerance
- Multiple protocols (HTTP, TCP, UDP) support in an asynchronous and reactive model
- Caching and batching
A central concept in Ribbon is that of the named client. Each load balancer is part of an ensemble of components that work together to contact a remote server on demand, and the ensemble has a name that you give it as an application developer.
Using Ribbon - Code:
First Service is hello-service
This service provides some aleatory greeting when accessing http://localhost:8090/greeting
@RestController
@SpringBootApplication
public class HelloApplication {
private static Logger log = LoggerFactory.getLogger(HelloApplication.class);
@RequestMapping(value = "/greeting")
public String greet() {
log.info("Access /greeting");
List<String> greetings = Arrays.asList("Hi there", "Greetings", "Salutations");
Random rand = new Random();
int randomNum = rand.nextInt(greetings.size());
return greetings.get(randomNum);
}
@RequestMapping(value = "/")
public String home() {
log.info("Access /");
return "Hi!";
}
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}
File: application.yml
spring:
application:
name: hello-service
server:
port: 8090
OBS: should create two more hello-service in Port 9092, 9999
Second Service is user-service
This service call for hello-service, operation greeting, with ribbon configured, this service call hello-service based on algorithm round-robin.
@SpringBootApplication
@RestController
@RibbonClient(name = "hello-service", configuration = HelloServiceConfiguration.class)
public class UserApplication {
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
@Autowired
RestTemplate restTemplate;
@RequestMapping("/hi")
public String hi(@RequestParam(value = "name", defaultValue = "Rafael") String name) {
String greeting = this.restTemplate.getForObject("http://hello-service/greeting", String.class);
return String.format("%s, %s!", greeting, name);
}
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
public class HelloServiceConfiguration {
@Autowired
IClientConfig ribbonClientConfig;
@Bean
public IPing ribbonPing(IClientConfig config) {
return new PingUrl();
}
@Bean
public IRule ribbonRule(IClientConfig config) {
return new AvailabilityFilteringRule();
}
}
File: application.yml
In Ribbon, configuration lists all services available.
spring:
application:
name: user-service
server:
port: 8888
hello-service:
ribbon:
eureka:
enabled: false
listOfServers: localhost:8090,localhost:9092,localhost:9999
ServerListRefreshInterval: 15000
When we call the service on the URL http://localhost:8090/greeting on console, it is possible to see each service it is called, for example, in the fist call any host configured on the list of servers that could be used.
Feign - Load Balancer using Eureka
Feign is a declarative web service client, or declarative REST client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web.
Spring Cloud integrates Ribbon and Eureka to provide a load balanced http client when using Feign.
I talk more about Eureka here.
Using Feign - Code:
Eureka server Project: http://localhost:8761
@SpringBootApplication
@EnableEurekaServer
public class ApplicationEurekaServer {
public static void main(String[] args) {
new SpringApplicationBuilder(ApplicationEurekaServer.class)
.web(true)
.run(args);
}
}
File: application.yml
#Server Specifics
server:
port: 8761
error:
whitelabel:
enabled: false
spring:
application:
name: eureka-server
#Eureka Specifics
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
Service is hello-service http://localhost:7111/greeting
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class HelloApplication {
@Autowired
DiscoveryClient client;
@RequestMapping("/")
public String hello() {
ServiceInstance localInstance = client.getLocalServiceInstance();
return "Hello World: "+ localInstance.getServiceId()+":"+localInstance.getHost()+":"+localInstance.getPort();
}
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}
File: application.yml
spring:
application:
name: hello-service
server:
port: 7111
eureka:
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
Should create other hello-service on Port 7112 http://localhost:7112/greeting
And the service that will consume these hello-service is user-service.
@SpringBootApplication
@EnableDiscoveryClient
@RestController
@EnableFeignClients
public class UserApplication {
@Autowired
HelloClient client;
@RequestMapping("/")
public String hello() {
return client.hello();
}
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
@FeignClient("hello-service")
interface HelloClient {
@RequestMapping(value = "/", method = GET)
String hello();
}
}
File: application.yml
spring:
application:
name: user-service
server:
port: 7211
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
leaseRenewalIntervalInSeconds: 10
metadataMap:
instanceId: ${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}}
endpoints:
restart:
enabled: true
shutdown:
enabled: true
health:
sensitive: false
This service will be available on http://localhost:7211
user-service will ask to Eureka the hello-service address, and will receive some address that is registered.
If everything OK this service will be registered on Eureka:
When we call http://localhost:7211 one of the following messages will be shown:
Hello World: hello-service:192.168.0.14:7112
Hello World: hello-service:192.168.0.14:7111
Opinions expressed by DZone contributors are their own.
Comments